heist 0.4.0.1 → 0.5.0.0
raw patch · 13 files changed
+679/−1328 lines, 13 filesdep +attoparsec-textdep +blaze-builderdep +textdep −hexpatPVP ok
version bump matches the API change (PVP)
Dependencies added: attoparsec-text, blaze-builder, text, xmlhtml
Dependencies removed: hexpat
API changes (from Hackage documentation)
- Text.Templating.Heist: parseDoc :: ByteString -> IO (Either String (Maybe ByteString, [Node]))
- Text.Templating.Heist: type Node = Node ByteString ByteString
+ Text.Templating.Heist: addXMLTemplate :: Monad m => ByteString -> Template -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: getXMLDoc :: String -> IO (Either String Document)
+ Text.Templating.Heist: type MIMEType = ByteString
- Text.Templating.Heist: bindSplice :: Monad m => ByteString -> Splice m -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindSplice :: Monad m => Text -> Splice m -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindSplices :: Monad m => [(ByteString, Splice m)] -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindSplices :: Monad m => [(Text, Splice m)] -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindString :: Monad m => ByteString -> ByteString -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindString :: Monad m => Text -> Text -> TemplateState m -> TemplateState m
- Text.Templating.Heist: bindStrings :: Monad m => [(ByteString, ByteString)] -> TemplateState m -> TemplateState m
+ Text.Templating.Heist: bindStrings :: Monad m => [(Text, Text)] -> TemplateState m -> TemplateState m
- Text.Templating.Heist: callTemplate :: Monad m => ByteString -> [(ByteString, ByteString)] -> TemplateMonad m (Maybe Template)
+ Text.Templating.Heist: callTemplate :: Monad m => ByteString -> [(Text, Text)] -> TemplateMonad m (Maybe Template)
- Text.Templating.Heist: getDoc :: String -> IO (Either String InternalTemplate)
+ Text.Templating.Heist: getDoc :: String -> IO (Either String Document)
- Text.Templating.Heist: lookupSplice :: Monad m => ByteString -> TemplateState m -> Maybe (Splice m)
+ Text.Templating.Heist: lookupSplice :: Monad m => Text -> TemplateState m -> Maybe (Splice m)
- Text.Templating.Heist: renderTemplate :: Monad m => TemplateState m -> ByteString -> m (Maybe ByteString)
+ Text.Templating.Heist: renderTemplate :: Monad m => TemplateState m -> ByteString -> m (Maybe (Builder, MIMEType))
- Text.Templating.Heist: renderWithArgs :: Monad m => [(ByteString, ByteString)] -> TemplateState m -> ByteString -> m (Maybe ByteString)
+ Text.Templating.Heist: renderWithArgs :: Monad m => [(Text, Text)] -> TemplateState m -> ByteString -> m (Maybe (Builder, MIMEType))
- Text.Templating.Heist.Splices.Apply: applyAttr :: ByteString
+ Text.Templating.Heist.Splices.Apply: applyAttr :: Text
- Text.Templating.Heist.Splices.Apply: applyTag :: ByteString
+ Text.Templating.Heist.Splices.Apply: applyTag :: Text
- Text.Templating.Heist.Splices.Bind: bindAttr :: ByteString
+ Text.Templating.Heist.Splices.Bind: bindAttr :: Text
- Text.Templating.Heist.Splices.Bind: bindTag :: ByteString
+ Text.Templating.Heist.Splices.Bind: bindTag :: Text
- Text.Templating.Heist.Splices.Ignore: ignoreTag :: ByteString
+ Text.Templating.Heist.Splices.Ignore: ignoreTag :: Text
- Text.Templating.Heist.Splices.Markdown: markdownTag :: ByteString
+ Text.Templating.Heist.Splices.Markdown: markdownTag :: Text
Files
- docs/out/templates.html +365/−748
- heist.cabal +6/−4
- src/Text/Templating/Heist.hs +17/−14
- src/Text/Templating/Heist/Constants.hs +0/−268
- src/Text/Templating/Heist/Internal.hs +144/−155
- src/Text/Templating/Heist/Splices/Apply.hs +13/−11
- src/Text/Templating/Heist/Splices/Bind.hs +6/−6
- src/Text/Templating/Heist/Splices/Ignore.hs +2/−2
- src/Text/Templating/Heist/Splices/Markdown.hs +19/−13
- src/Text/Templating/Heist/Splices/Static.hs +12/−12
- src/Text/Templating/Heist/Types.hs +13/−25
- test/heist-testsuite.cabal +4/−1
- test/suite/Text/Templating/Heist/Tests.hs +78/−69
docs/out/templates.html view
@@ -4,751 +4,368 @@ <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="pandoc" />- <style type="text/css">-table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre - { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }-td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }-td.sourceCode { padding-left: 5px; }-pre.sourceCode span.kw { color: #007020; font-weight: bold; } -pre.sourceCode span.dt { color: #902000; }-pre.sourceCode span.dv { color: #40a070; }-pre.sourceCode span.bn { color: #40a070; }-pre.sourceCode span.fl { color: #40a070; }-pre.sourceCode span.ch { color: #4070a0; }-pre.sourceCode span.st { color: #4070a0; }-pre.sourceCode span.co { color: #60a0b0; font-style: italic; }-pre.sourceCode span.ot { color: #007020; }-pre.sourceCode span.al { color: red; font-weight: bold; }-pre.sourceCode span.fu { color: #06287e; }-pre.sourceCode span.re { }-pre.sourceCode span.er { color: red; font-weight: bold; }- </style>- <link rel="stylesheet" href="templates.css" type="text/css" />-</head>-<body>-<h2 id="heist-templates"->Heist Templates</h2-><p->Heist templates serve two primary design goals. First, they facilitate the separation of the view from the other aspects of your application. Second, they provide abstraction capabilities that allow you to avoid repeated template code. This allows you to follow the DRY principle (Don't Repeat Yourself) in the development of your application views.</p-><p->Heist has two primary template abstraction constructs: bind and apply. They are implemented as specialized XML tags.</p-><h3 id="the-bind-...-tag"->The <code- ><bind ...></code- > tag</h3-><p->The <code- >bind</code- > tag allows you to bind XML content to a single tag. Whenever the bound tag is used, the template engine will substitute the 'bind' tag's child nodes in its place. This allows you to essentially create your own higher-level markup language that Heist transforms into whatever XML-based markup language is native to your application.</p-><h4 id="attributes"->Attributes</h4-><p->The <code- >bind</code- > tag has a single required attribute called <code- >tag</code- > specifying the name of the bound tag. If this attribute is not present, then the <code- >bind</code- > tag has no effect.</p-><h4 id="example"->Example</h4-><p->Here's a simple example demonstrating the use of bind.</p-><pre class="sourceCode html"-><code- > <span class="kw"- ><bind</span- ><span class="ot"- > tag=</span- ><span class="st"- >"longname"</span- ><span class="kw"- >></span- ><br- /> Einstein, Feynman, Heisenberg, and Newton Reasearch Corporation<br- />Ltd.<span class="kw"- ><sup></span- >TM<span class="kw"- ></sup></span- ><br- /> <span class="kw"- ></bind></span- ><br- /> We at <span class="kw"- ><longname/></span- > have research expertise in many areas of physics.<br- />Employment at <span class="kw"- ><longname/></span- > carries significant prestige. The rigorous<br- />hiring process developed by <span class="kw"- ><longname/></span- > is leading the industry.<br- /></code- ></pre-><p->The full company name will be substituted at every occurrance of the <code- ><longname/></code- > tag. This eliminates repetition and makes it easier to make changes.</p-><h3 id="the-apply-...-tag"->The <code- ><apply ...></code- > tag</h3-><p->The <code- >apply</code- > tag loads one of your application templates and inserts it into the current template's XML tree. If the target template does not have any special tags, then the contents of the <code- >apply</code- > tag are ignored.</p-><h4 id="attributes-1"->Attributes</h4-><p->The <code- >apply</code- > tag has one required attribute called <code- >template</code- >. This attribute specifies the name of the template being applied. Heist template names work a little differently from traditional paths and filenames.</p-><p->If the template name contains a '/' character, then it will behave like traditional relative and absolute paths. The root directory will be the root of your template directory tree, and the current directory will be the directory containing whatever template is currently being processed. Absolute template path names start at the root directory. Relative template path names start at the current directory.</p-><p->If the template name does not have any '/' characters, then Heist searches in the current directory for a template with that name. If it finds one, then Heist applies the template just like you would expect. The different behavior is that if the named template is not found in the current directory, Heist recursively searches up the directory hierarchy looking for the name. Heist uses the first template it finds on the way up that has that name. If no template is found, then you'll get an error.</p-><p->This cascading behavior allows you to put site-wide templates in the top-level directory and selectively override them in subdirectories for certain parts of your site.</p-><h4 id="example-1"->Example</h4-><p->Let's look at a simple example to demonstrate the most basic use of the <code- >apply</code- > tag. Say you have a navigation menu that is used on many different pages of your site. You want to avoid duplicating the HTML code in multiple different page templates, so you might put it in a template file by itself called <code- >nav.tpl</code- > that looks like this:</p-><pre class="sourceCode html"-><code- > <span class="kw"- ><ul></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/"</span- ><span class="kw"- >></span- >Home<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/faq"</span- ><span class="kw"- >></span- >FAQ<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/contact"</span- ><span class="kw"- >></span- >Contact<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ></ul></span- ><br- /></code- ></pre-><p->Then to include this nav template in your front page template, you would use the <code- >apply</code- > tag. Here is what a simple home page template <code- >home.tpl</code- > might look like:</p-><pre class="sourceCode html"-><code- > <span class="kw"- ><html></span- ><br- /> <span class="kw"- ><head></span- ><br- /> <span class="kw"- ><title></span- >Home Page<span class="kw"- ></title></span- ><br- /> <span class="kw"- ></head></span- ><br- /> <span class="kw"- ><body></span- ><br- /> <span class="kw"- ><h1></span- >Home Page<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ><apply</span- ><span class="ot"- > template=</span- ><span class="st"- >"nav"</span- ><span class="kw"- >/></span- ><br- /> <span class="kw"- ><p></span- >Welcome to our home page<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></body></span- ><br- /> <span class="kw"- ></html></span- ><br- /></code- ></pre-><p->When a user requests the <code- >/home</code- > URL, Heist would serve <code- >home.tpl</code- >, and the nav template would automatically be inserted into the page. Here is what the HTML will look like after Heist processes the template:</p-><pre class="sourceCode html"-><code- > <span class="kw"- ><html></span- ><br- /> <span class="kw"- ><head></span- ><br- /> <span class="kw"- ><title></span- >Home Page<span class="kw"- ></title></span- ><br- /> <span class="kw"- ></head></span- ><br- /> <span class="kw"- ><body></span- ><br- /> <span class="kw"- ><h1></span- >Home Page<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ><ul></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/"</span- ><span class="kw"- >></span- >Home<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/faq"</span- ><span class="kw"- >></span- >FAQ<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ><li><a</span- ><span class="ot"- > href=</span- ><span class="st"- >"/contact"</span- ><span class="kw"- >></span- >Contact<span class="kw"- ></a></li></span- ><br- /> <span class="kw"- ></ul></span- ><br- /> <span class="kw"- ><p></span- >Welcome to our home page<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></body></span- ><br- /> <span class="kw"- ></html></span- ><br- /></code- ></pre-><h3 id="the-content-tag"->The <code- ><content></code- > tag</h3-><p->Sometimes it is useful to pass information (usually in the form of XML data) into the template when it is applied so the template can insert it in useful places. This allows you to build page templates that are not just static blocks of code. If you are a programmer, you can think of a template as if it was a function that could have any number of parameters.</p-><p->In our previous example, we did not pass any parameters to the <code- >nav</code- > template when it was applied, so the <code- ><apply></code- > tag was empty. If we include data inside the body of the <code- ><apply></code- > tag, the template being called can access this data with the <code- ><content></code- > tag. The following simple example illustrates this concept. We create a site template called <code- >default.tpl</code- >:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><html></span- ><br- /> <span class="kw"- ><head></span- ><br- /> <span class="kw"- ><title></span- >Home Page<span class="kw"- ></title></span- ><br- /> <span class="kw"- ></head></span- ><br- /> <span class="kw"- ><body></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"header"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >XYZ Inc.<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"content"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><content</span- > <span class="kw"- >/></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"footer"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><p></span- >Copyright XYZ Inc<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ></body></span- ><br- /><span class="kw"- ></html></span- ><br- /></code- ></pre-><p->The <code- ><content></code- > tag "pulls in" the page content from the calling template and inserts it into the content <code- ><div></code- >.</p-><p->Now we have a template for our home page called home.tpl:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><apply</span- ><span class="ot"- > template=</span- ><span class="st"- >"default"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >Home Page<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ><p></span- >Welcome to XYZ Inc<span class="kw"- ></p></span- ><br- /><span class="kw"- ></apply></span- ><br- /></code- ></pre-><p->And when Heist receives a request to <code- >/home</code- >, it will serve the following:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><html></span- ><br- /> <span class="kw"- ><head></span- ><br- /> <span class="kw"- ><title></span- >Home Page<span class="kw"- ></title></span- ><br- /> <span class="kw"- ></head></span- ><br- /> <span class="kw"- ><body></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"header"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >XYZ Inc.<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"content"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >Home Page<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ><p></span- >Welcome to XYZ Inc<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"footer"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><p></span- >Copyright XYZ Inc<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ></body></span- ><br- /><span class="kw"- ></html></span- ><br- /></code- ></pre-><p->The two lines from inside the <code- ><apply></code- > tag have been substituted into the content div in <code- >default.tpl</code- >. Notice the difference between these two examples. In the first example we pulled in a template (<code- >nav.tpl</code- >) that went inside the page being served (<code- >home.tpl</code- >). In the second example, <code- >home.tpl</code- > is still the intended target of requests, but the <code- >default.tpl</code- > template surrounds the content that home.tpl supplies as an argument. This seems like different behavior, but it is just a different use of the same <code- >apply</code- > tag. This illustrates the power of a simple concept like <code- >apply</code- >.</p-><h3 id="using-bind-and-apply"->Using Bind and Apply</h3-><p->What if, in the above example, we decided that the contents of the header div should be different for different pages? To do this, we need a way to pass multiple parameters into a template. Heist provides this capability with the <code- ><bind></code- > tag. Inside the body of a <code- ><apply></code- > tag, you can have multiple bind tags surrounding data to be passed as separate parameters. Each <code- ><bind></code- > tag must have a <code- >tag</code- > attribute that provides a name for its contents just as described above. Then, inside the template, those tags will be substituted with the appropriate data.</p-><p->The previous example only needs a few modifications to <code- >default.tpl</code- > to allow multiple parameters.</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><html></span- ><br- /> <span class="kw"- ><head></span- ><br- /> <span class="kw"- ><title></span- >Home Page<span class="kw"- ></title></span- ><br- /> <span class="kw"- ></head></span- ><br- /> <span class="kw"- ><body></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"header"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><header/></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"content"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><main/></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ><div</span- ><span class="ot"- > id=</span- ><span class="st"- >"footer"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><p></span- >Copyright XYZ Inc<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></div></span- ><br- /> <span class="kw"- ></body></span- ><br- /><span class="kw"- ></html></span- ><br- /></code- ></pre-><p->And <code- >home.tpl</code- > uses the <code- ><bind></code- > tag with a name attribute to define values for the <code- ><header/></code- > and <code- ><main/></code- > tags:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><apply</span- ><span class="ot"- > template=</span- ><span class="st"- >"default"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><bind</span- ><span class="ot"- > tag=</span- ><span class="st"- >"header"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >XYZ Inc.<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ></bind></span- ><br- /> Some in-between text.<br- /> <span class="kw"- ><bind</span- ><span class="ot"- > tag=</span- ><span class="st"- >"main"</span- ><span class="kw"- >></span- ><br- /> <span class="kw"- ><h1></span- >Home Page<span class="kw"- ></h1></span- ><br- /> <span class="kw"- ><p></span- >Welcome to XYZ Inc<span class="kw"- ></p></span- ><br- /> <span class="kw"- ></bind></span- ><br- /><span class="kw"- ></apply></span- ><br- /></code- ></pre-><p->The result template for this example is the same as the previous example.</p-><p->NOTE: In this example the <code- ><content/></code- > tag is still bound as described above. The <code- ><content/></code- > tag is always bound to the complete contents of the calling <code- >apply</code- > tag. However, any <code- >bind</code- > tags inside the apply will disappear. If we changed <code- >default.tpl</code- > to the following:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><foo></span- ><br- /> <span class="kw"- ><content/></span- ><br- /><span class="kw"- ></foo></span- ><br- /></code- ></pre-><p->Then the above <code- >home.tpl</code- > template would render like this:</p-><pre class="sourceCode html"-><code- ><span class="kw"- ><foo></span- ><br- /> Some in-between text.<br- /><span class="kw"- ></foo></span- ><br- /></code- ></pre-><h3 id="the-ignore-tag"->The <code- ><ignore></code- > tag</h3-><p->In some cases you may want to include example data in a Heist template that should not be rendered when the site is active. Heist provides the <code- ><ignore></code- > tag for this purpose. All <code- ><ignore></code- > tags and their contents will be eliminated in a template's output.</p-><h3 id="the-children-tag"->The <code- ><children></code- > tag</h3-><p->XML requires that well-formed documents have a single root element. Sometimes you might want to make templates that don't have a single root element. In these situations the <code- ><children></code- > tag is just what you want. When the children tag is rendered, it strips itself off and just returns its child nodes. This allows you to have a single root element where necessary, but have that tag disappear in the rendered output.</p->-</body>-</html>+ <meta name="date" content="" />+ <link rel="stylesheet" href="templates.css" type="text/css" />+</head>+<body>+<div id="heist-templates"+><h2+ >Heist Templates</h2+ ><p+ >Heist templates serve two primary design goals. First, they facilitate the separation of the view from the other aspects of your application. Second, they provide abstraction capabilities that allow you to avoid repeated template code. This allows you to follow the DRY principle (Don't Repeat Yourself) in the development of your application views.</p+ ><p+ >Heist has two primary template abstraction constructs: bind and apply. They are implemented as specialized XML tags.</p+ ><div id="the-bind-...-tag"+ ><h3+ >The <code+ ><bind ...></code+ > tag</h3+ ><p+ >The <code+ >bind</code+ > tag allows you to bind XML content to a single tag. Whenever the bound tag is used, the template engine will substitute the 'bind' tag's child nodes in its place. This allows you to essentially create your own higher-level markup language that Heist transforms into whatever XML-based markup language is native to your application.</p+ ><div id="attributes"+ ><h4+ >Attributes</h4+ ><p+ >The <code+ >bind</code+ > tag has a single required attribute called <code+ >tag</code+ > specifying the name of the bound tag. If this attribute is not present, then the <code+ >bind</code+ > tag has no effect.</p+ ></div+ ><div id="example"+ ><h4+ >Example</h4+ ><p+ >Here's a simple example demonstrating the use of bind.</p+ ><pre class="html"+ ><code+ > <bind tag="longname">+ Einstein, Feynman, Heisenberg, and Newton Reasearch Corporation+Ltd.<sup>TM</sup>+ </bind>+ We at <longname/> have research expertise in many areas of physics.+Employment at <longname/> carries significant prestige. The rigorous+hiring process developed by <longname/> is leading the industry.+</code+ ></pre+ ><p+ >The full company name will be substituted at every occurrance of the <code+ ><longname/></code+ > tag. This eliminates repetition and makes it easier to make changes.</p+ ></div+ ></div+ ><div id="the-apply-...-tag"+ ><h3+ >The <code+ ><apply ...></code+ > tag</h3+ ><p+ >The <code+ >apply</code+ > tag loads one of your application templates and inserts it into the current template's XML tree. If the target template does not have any special tags, then the contents of the <code+ >apply</code+ > tag are ignored.</p+ ><div id="attributes-1"+ ><h4+ >Attributes</h4+ ><p+ >The <code+ >apply</code+ > tag has one required attribute called <code+ >template</code+ >. This attribute specifies the name of the template being applied. Heist template names work a little differently from traditional paths and filenames.</p+ ><p+ >If the template name contains a '/' character, then it will behave like traditional relative and absolute paths. The root directory will be the root of your template directory tree, and the current directory will be the directory containing whatever template is currently being processed. Absolute template path names start at the root directory. Relative template path names start at the current directory.</p+ ><p+ >If the template name does not have any '/' characters, then Heist searches in the current directory for a template with that name. If it finds one, then Heist applies the template just like you would expect. The different behavior is that if the named template is not found in the current directory, Heist recursively searches up the directory hierarchy looking for the name. Heist uses the first template it finds on the way up that has that name. If no template is found, then you'll get an error.</p+ ><p+ >This cascading behavior allows you to put site-wide templates in the top-level directory and selectively override them in subdirectories for certain parts of your site.</p+ ></div+ ><div id="example-1"+ ><h4+ >Example</h4+ ><p+ >Let's look at a simple example to demonstrate the most basic use of the <code+ >apply</code+ > tag. Say you have a navigation menu that is used on many different pages of your site. You want to avoid duplicating the HTML code in multiple different page templates, so you might put it in a template file by itself called <code+ >nav.tpl</code+ > that looks like this:</p+ ><pre class="html"+ ><code+ > <ul>+ <li><a href="/">Home</a></li>+ <li><a href="/faq">FAQ</a></li>+ <li><a href="/contact">Contact</a></li>+ </ul>+</code+ ></pre+ ><p+ >Then to include this nav template in your front page template, you would use the <code+ >apply</code+ > tag. Here is what a simple home page template <code+ >home.tpl</code+ > might look like:</p+ ><pre class="html"+ ><code+ > <html>+ <head>+ <title>Home Page</title>+ </head>+ <body>+ <h1>Home Page</h1>+ <apply template="nav"/>+ <p>Welcome to our home page</p>+ </body>+ </html>+</code+ ></pre+ ><p+ >When a user requests the <code+ >/home</code+ > URL, Heist would serve <code+ >home.tpl</code+ >, and the nav template would automatically be inserted into the page. Here is what the HTML will look like after Heist processes the template:</p+ ><pre class="html"+ ><code+ > <html>+ <head>+ <title>Home Page</title>+ </head>+ <body>+ <h1>Home Page</h1>+ <ul>+ <li><a href="/">Home</a></li>+ <li><a href="/faq">FAQ</a></li>+ <li><a href="/contact">Contact</a></li>+ </ul>+ <p>Welcome to our home page</p>+ </body>+ </html>+</code+ ></pre+ ></div+ ></div+ ><div id="the-content-tag"+ ><h3+ >The <code+ ><content></code+ > tag</h3+ ><p+ >Sometimes it is useful to pass information (usually in the form of XML data) into the template when it is applied so the template can insert it in useful places. This allows you to build page templates that are not just static blocks of code. If you are a programmer, you can think of a template as if it was a function that could have any number of parameters.</p+ ><p+ >In our previous example, we did not pass any parameters to the <code+ >nav</code+ > template when it was applied, so the <code+ ><apply></code+ > tag was empty. If we include data inside the body of the <code+ ><apply></code+ > tag, the template being called can access this data with the <code+ ><content></code+ > tag. The following simple example illustrates this concept. We create a site template called <code+ >default.tpl</code+ >:</p+ ><pre class="html"+ ><code+ ><html>+ <head>+ <title>Home Page</title>+ </head>+ <body>+ <div id="header">+ <h1>XYZ Inc.</h1>+ </div>+ <div id="content">+ <content />+ </div>+ <div id="footer">+ <p>Copyright XYZ Inc</p>+ </div>+ </body>+</html>+</code+ ></pre+ ><p+ >The <code+ ><content></code+ > tag "pulls in" the page content from the calling template and inserts it into the content <code+ ><div></code+ >.</p+ ><p+ >Now we have a template for our home page called home.tpl:</p+ ><pre class="html"+ ><code+ ><apply template="default">+ <h1>Home Page</h1>+ <p>Welcome to XYZ Inc</p>+</apply>+</code+ ></pre+ ><p+ >And when Heist receives a request to <code+ >/home</code+ >, it will serve the following:</p+ ><pre class="html"+ ><code+ ><html>+ <head>+ <title>Home Page</title>+ </head>+ <body>+ <div id="header">+ <h1>XYZ Inc.</h1>+ </div>+ <div id="content">+ <h1>Home Page</h1>+ <p>Welcome to XYZ Inc</p>+ </div>+ <div id="footer">+ <p>Copyright XYZ Inc</p>+ </div>+ </body>+</html>+</code+ ></pre+ ><p+ >The two lines from inside the <code+ ><apply></code+ > tag have been substituted into the content div in <code+ >default.tpl</code+ >. Notice the difference between these two examples. In the first example we pulled in a template (<code+ >nav.tpl</code+ >) that went inside the page being served (<code+ >home.tpl</code+ >). In the second example, <code+ >home.tpl</code+ > is still the intended target of requests, but the <code+ >default.tpl</code+ > template surrounds the content that home.tpl supplies as an argument. This seems like different behavior, but it is just a different use of the same <code+ >apply</code+ > tag. This illustrates the power of a simple concept like <code+ >apply</code+ >.</p+ ></div+ ><div id="using-bind-and-apply"+ ><h3+ >Using Bind and Apply</h3+ ><p+ >What if, in the above example, we decided that the contents of the header div should be different for different pages? To do this, we need a way to pass multiple parameters into a template. Heist provides this capability with the <code+ ><bind></code+ > tag. Inside the body of a <code+ ><apply></code+ > tag, you can have multiple bind tags surrounding data to be passed as separate parameters. Each <code+ ><bind></code+ > tag must have a <code+ >tag</code+ > attribute that provides a name for its contents just as described above. Then, inside the template, those tags will be substituted with the appropriate data.</p+ ><p+ >The previous example only needs a few modifications to <code+ >default.tpl</code+ > to allow multiple parameters.</p+ ><pre class="html"+ ><code+ ><html>+ <head>+ <title>Home Page</title>+ </head>+ <body>+ <div id="header">+ <header/>+ </div>+ <div id="content">+ <main/>+ </div>+ <div id="footer">+ <p>Copyright XYZ Inc</p>+ </div>+ </body>+</html>+</code+ ></pre+ ><p+ >And <code+ >home.tpl</code+ > uses the <code+ ><bind></code+ > tag with a name attribute to define values for the <code+ ><header/></code+ > and <code+ ><main/></code+ > tags:</p+ ><pre class="html"+ ><code+ ><apply template="default">+ <bind tag="header">+ <h1>XYZ Inc.</h1>+ </bind>+ Some in-between text.+ <bind tag="main">+ <h1>Home Page</h1>+ <p>Welcome to XYZ Inc</p>+ </bind>+</apply>+</code+ ></pre+ ><p+ >The result template for this example is the same as the previous example.</p+ ><p+ >NOTE: In this example the <code+ ><content/></code+ > tag is still bound as described above. The <code+ ><content/></code+ > tag is always bound to the complete contents of the calling <code+ >apply</code+ > tag. However, any <code+ >bind</code+ > tags inside the apply will disappear. If we changed <code+ >default.tpl</code+ > to the following:</p+ ><pre class="html"+ ><code+ ><foo>+ <content/>+</foo>+</code+ ></pre+ ><p+ >Then the above <code+ >home.tpl</code+ > template would render like this:</p+ ><pre class="html"+ ><code+ ><foo>+ Some in-between text.+</foo>+</code+ ></pre+ ></div+ ><div id="the-ignore-tag"+ ><h3+ >The <code+ ><ignore></code+ > tag</h3+ ><p+ >In some cases you may want to include example data in a Heist template that should not be rendered when the site is active. Heist provides the <code+ ><ignore></code+ > tag for this purpose. All <code+ ><ignore></code+ > tags and their contents will be eliminated in a template's output.</p+ ></div+ ><div id="the-children-tag"+ ><h3+ >The <code+ ><children></code+ > tag</h3+ ><p+ >XML requires that well-formed documents have a single root element. Sometimes you might want to make templates that don't have a single root element. In these situations the <code+ ><children></code+ > tag is just what you want. When the children tag is rendered, it strips itself off and just returns its child nodes. This allows you to have a single root element where necessary, but have that tag disappear in the rendered output.</p+ ></div+ ></div+>+</body>+</html>+
heist.cabal view
@@ -1,5 +1,5 @@ name: heist-version: 0.4.0.1+version: 0.5.0.0 synopsis: An xhtml templating system description: An xhtml templating system license: BSD3@@ -85,23 +85,25 @@ other-modules: Text.Templating.Heist.Internal,- Text.Templating.Heist.Constants, Text.Templating.Heist.Types build-depends: attoparsec >= 0.8.1 && < 0.9,+ attoparsec-text >= 0.8 && < 0.9, base >= 4 && < 5,+ blaze-builder == 0.2.*, bytestring, containers, directory, directory-tree, filepath,- hexpat >= 0.19.5 && <0.20, MonadCatchIO-transformers >= 0.2.1 && < 0.3, mtl >= 2.0 && < 2.1, process, random,- transformers+ text >= 0.10 && < 0.12,+ transformers,+ xmlhtml == 0.1.* if impl(ghc >= 6.12.0) ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
src/Text/Templating/Heist.hs view
@@ -4,10 +4,10 @@ This module contains the core definitions for the Heist template system. - The Heist template system is based on XML\/xhtml. It allows you to build- custom XML-based markup languages. With Heist you can define your own- domain-specific XML tags implemented with Haskell and use them in your- templates. + The Heist template system is based on HTML and XML. It allows you to build+ custom HTML and XML based markup languages. With Heist you can define your+ own domain-specific HTML and XML tags implemented with Haskell and use them+ in your templates. The most important concept in Heist is the 'Splice'. Splices can be thought of as functions that transform a node into a list of nodes. Heist then@@ -19,23 +19,25 @@ Suppose you have a place on your page where you want to display a link with the text \"Logout username\" if the user is currently logged in or a link to the login page if no user is logged in. Assume you have a function- @getUser :: MyAppMonad (Maybe ByteString)@ that gets the current user.+ @getUser :: MyAppMonad (Maybe Text)@ that gets the current user. You can implement this functionality with a 'Splice' as follows: > import Data.ByteString.Char8 (ByteString) > import qualified Data.ByteString.Char8 as B- > import qualified Text.XML.Expat.Tree as X+ > import Data.Text (Text)+ > import qualified Data.Text as T+ > import qualified Text.XmlHtml as X > > import Text.Templating.Heist >- > link :: ByteString -> ByteString -> Node- > link target text = X.Element "a" [("href", target)] [X.Text text]+ > link :: Text -> Text -> Node+ > link target text = X.Element "a" [("href", target)] [X.TextNode text] > > loginLink :: Node > loginLink = link "/login" "Login" > - > logoutLink :: ByteString -> Node- > logoutLink user = link "/logout" (B.append "Logout " user)+ > logoutLink :: Text -> Node+ > logoutLink user = link "/logout" (T.append "Logout " user) > > loginLogoutSplice :: Splice MyAppMonad > loginLogoutSplice = do@@ -43,7 +45,7 @@ > return $ [maybe loginLink logoutLink user] > - Next, you need to bind that splice to an XML tag. Heist stores information+ Next, you need to bind that splice to a tag. Heist stores information about splices and templates in the 'TemplateState' data structure. The following code demonstrates how this splice would be used. @@ -66,14 +68,15 @@ module Text.Templating.Heist ( -- * Types- Node- , Template+ Template+ , MIMEType , Splice , TemplateMonad , TemplateState -- * Functions and declarations on TemplateState values , addTemplate+ , addXMLTemplate , emptyTemplateState , bindSplice , bindSplices@@ -111,7 +114,7 @@ -- * Misc functions , getDoc- , parseDoc+ , getXMLDoc , bindStaticTag ) where
− src/Text/Templating/Heist/Constants.hs
@@ -1,268 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Text.Templating.Heist.Constants where--import Data.ByteString.Char8 (ByteString)-import qualified Data.Map as Map-import Data.Map (Map)-import Text.XML.Expat.Tree------------------------------------------------------------------------------------ | Options passed to hexpat for XML parsing.-heistExpatOptions :: ParseOptions ByteString ByteString-heistExpatOptions =- defaultParseOptions {- overrideEncoding = Just UTF8- , entityDecoder = Just (\k -> Map.lookup k htmlEntityLookupTable)- }----------------------------------------------------------------------------------- | Character entity references for HTML.-htmlEntityLookupTable :: Map ByteString ByteString-htmlEntityLookupTable = Map.fromList [- ("acute" , "\xc2\xb4")- , ("cedil" , "\xc2\xb8")- , ("circ" , "\xcb\x86")- , ("macr" , "\xc2\xaf")- , ("middot" , "\xc2\xb7")- , ("tilde" , "\xcb\x9c")- , ("uml" , "\xc2\xa8")- , ("Aacute" , "\xc3\x81")- , ("aacute" , "\xc3\xa1")- , ("Acirc" , "\xc3\x82")- , ("acirc" , "\xc3\xa2")- , ("AElig" , "\xc3\x86")- , ("aelig" , "\xc3\xa6")- , ("Agrave" , "\xc3\x80")- , ("agrave" , "\xc3\xa0")- , ("Aring" , "\xc3\x85")- , ("aring" , "\xc3\xa5")- , ("Atilde" , "\xc3\x83")- , ("atilde" , "\xc3\xa3")- , ("Auml" , "\xc3\x84")- , ("auml" , "\xc3\xa4")- , ("Ccedil" , "\xc3\x87")- , ("ccedil" , "\xc3\xa7")- , ("Eacute" , "\xc3\x89")- , ("eacute" , "\xc3\xa9")- , ("Ecirc" , "\xc3\x8a")- , ("ecirc" , "\xc3\xaa")- , ("Egrave" , "\xc3\x88")- , ("egrave" , "\xc3\xa8")- , ("ETH" , "\xc3\x90")- , ("eth" , "\xc3\xb0")- , ("Euml" , "\xc3\x8b")- , ("euml" , "\xc3\xab")- , ("Iacute" , "\xc3\x8d")- , ("iacute" , "\xc3\xad")- , ("Icirc" , "\xc3\x8e")- , ("icirc" , "\xc3\xae")- , ("Igrave" , "\xc3\x8c")- , ("igrave" , "\xc3\xac")- , ("Iuml" , "\xc3\x8f")- , ("iuml" , "\xc3\xaf")- , ("Ntilde" , "\xc3\x91")- , ("ntilde" , "\xc3\xb1")- , ("Oacute" , "\xc3\x93")- , ("oacute" , "\xc3\xb3")- , ("Ocirc" , "\xc3\x94")- , ("ocirc" , "\xc3\xb4")- , ("OElig" , "\xc5\x92")- , ("oelig" , "\xc5\x93")- , ("Ograve" , "\xc3\x92")- , ("ograve" , "\xc3\xb2")- , ("Oslash" , "\xc3\x98")- , ("oslash" , "\xc3\xb8")- , ("Otilde" , "\xc3\x95")- , ("otilde" , "\xc3\xb5")- , ("Ouml" , "\xc3\x96")- , ("ouml" , "\xc3\xb6")- , ("Scaron" , "\xc5\xa0")- , ("scaron" , "\xc5\xa1")- , ("szlig" , "\xc3\x9f")- , ("THORN" , "\xc3\x9e")- , ("thorn" , "\xc3\xbe")- , ("Uacute" , "\xc3\x9a")- , ("uacute" , "\xc3\xba")- , ("Ucirc" , "\xc3\x9b")- , ("ucirc" , "\xc3\xbb")- , ("Ugrave" , "\xc3\x99")- , ("ugrave" , "\xc3\xb9")- , ("Uuml" , "\xc3\x9c")- , ("uuml" , "\xc3\xbc")- , ("Yacute" , "\xc3\x9d")- , ("yacute" , "\xc3\xbd")- , ("yuml" , "\xc3\xbf")- , ("Yuml" , "\xc5\xb8")- , ("cent" , "\xc2\xa2")- , ("curren" , "\xc2\xa4")- , ("euro" , "\xe2\x82\xac")- , ("pound" , "\xc2\xa3")- , ("yen" , "\xc2\xa5")- , ("brvbar" , "\xc2\xa6")- , ("bull" , "\xe2\x80\xa2")- , ("copy" , "\xc2\xa9")- , ("dagger" , "\xe2\x80\xa0")- , ("Dagger" , "\xe2\x80\xa1")- , ("frasl" , "\xe2\x81\x84")- , ("hellip" , "\xe2\x80\xa6")- , ("iexcl" , "\xc2\xa1")- , ("image" , "\xe2\x84\x91")- , ("iquest" , "\xc2\xbf")- , ("mdash" , "\xe2\x80\x94")- , ("ndash" , "\xe2\x80\x93")- , ("not" , "\xc2\xac")- , ("oline" , "\xe2\x80\xbe")- , ("ordf" , "\xc2\xaa")- , ("ordm" , "\xc2\xba")- , ("para" , "\xc2\xb6")- , ("permil" , "\xe2\x80\xb0")- , ("prime" , "\xe2\x80\xb2")- , ("Prime" , "\xe2\x80\xb3")- , ("real" , "\xe2\x84\x9c")- , ("reg" , "\xc2\xae")- , ("sect" , "\xc2\xa7")- , ("shy" , "\173")- , ("sup1" , "\xc2\xb9")- , ("trade" , "\xe2\x84\xa2")- , ("weierp" , "\xe2\x84\x98")- , ("bdquo" , "\xe2\x80\x9e")- , ("laquo" , "\xc2\xab")- , ("ldquo" , "\xe2\x80\x9c")- , ("lsaquo" , "\xe2\x80\xb9")- , ("lsquo" , "\xe2\x80\x98")- , ("raquo" , "\xc2\xbb")- , ("rdquo" , "\xe2\x80\x9d")- , ("rsaquo" , "\xe2\x80\xba")- , ("rsquo" , "\xe2\x80\x99")- , ("sbquo" , "\xe2\x80\x9a")- , ("emsp" , "\xe2\x80\x83")- , ("ensp" , "\xe2\x80\x82")- , ("nbsp" , "\x20")- , ("thinsp" , "\xe2\x80\x89")- , ("zwj" , "\xe2\x80\x8d")- , ("zwnj" , "\xe2\x80\x8c")- , ("deg" , "\xc2\xb0")- , ("divide" , "\xc3\xb7")- , ("frac12" , "\xc2\xbd")- , ("frac14" , "\xc2\xbc")- , ("frac34" , "\xc2\xbe")- , ("ge" , "\xe2\x89\xa5")- , ("le" , "\xe2\x89\xa4")- , ("minus" , "\xe2\x88\x92")- , ("sup2" , "\xc2\xb2")- , ("sup3" , "\xc2\xb3")- , ("times" , "\xc3\x97")- , ("alefsym" , "\xe2\x84\xb5")- , ("and" , "\xe2\x88\xa7")- , ("ang" , "\xe2\x88\xa0")- , ("asymp" , "\xe2\x89\x88")- , ("cap" , "\xe2\x88\xa9")- , ("cong" , "\xe2\x89\x85")- , ("cup" , "\xe2\x88\xaa")- , ("empty" , "\xe2\x88\x85")- , ("equiv" , "\xe2\x89\xa1")- , ("exist" , "\xe2\x88\x83")- , ("fnof" , "\xc6\x92")- , ("forall" , "\xe2\x88\x80")- , ("infin" , "\xe2\x88\x9e")- , ("int" , "\xe2\x88\xab")- , ("isin" , "\xe2\x88\x88")- , ("lang" , "\xe3\x80\x88")- , ("lceil" , "\xe2\x8c\x88")- , ("lfloor" , "\xe2\x8c\x8a")- , ("lowast" , "\xe2\x88\x97")- , ("micro" , "\xc2\xb5")- , ("nabla" , "\xe2\x88\x87")- , ("ne" , "\xe2\x89\xa0")- , ("ni" , "\xe2\x88\x8b")- , ("notin" , "\xe2\x88\x89")- , ("nsub" , "\xe2\x8a\x84")- , ("oplus" , "\xe2\x8a\x95")- , ("or" , "\xe2\x88\xa8")- , ("otimes" , "\xe2\x8a\x97")- , ("part" , "\xe2\x88\x82")- , ("perp" , "\xe2\x8a\xa5")- , ("plusmn" , "\xc2\xb1")- , ("prod" , "\xe2\x88\x8f")- , ("prop" , "\xe2\x88\x9d")- , ("radic" , "\xe2\x88\x9a")- , ("rang" , "\xe3\x80\x89")- , ("rceil" , "\xe2\x8c\x89")- , ("rfloor" , "\xe2\x8c\x8b")- , ("sdot" , "\xe2\x8b\x85")- , ("sim" , "\xe2\x88\xbc")- , ("sub" , "\xe2\x8a\x82")- , ("sube" , "\xe2\x8a\x86")- , ("sum" , "\xe2\x88\x91")- , ("sup" , "\xe2\x8a\x83")- , ("supe" , "\xe2\x8a\x87")- , ("there4" , "\xe2\x88\xb4")- , ("Alpha" , "\xce\x91")- , ("alpha" , "\xce\xb1")- , ("Beta" , "\xce\x92")- , ("beta" , "\xce\xb2")- , ("Chi" , "\xce\xa7")- , ("chi" , "\xcf\x87")- , ("Delta" , "\xce\x94")- , ("delta" , "\xce\xb4")- , ("Epsilon" , "\xce\x95")- , ("epsilon" , "\xce\xb5")- , ("Eta" , "\xce\x97")- , ("eta" , "\xce\xb7")- , ("Gamma" , "\xce\x93")- , ("gamma" , "\xce\xb3")- , ("Iota" , "\xce\x99")- , ("iota" , "\xce\xb9")- , ("Kappa" , "\xce\x9a")- , ("kappa" , "\xce\xba")- , ("Lambda" , "\xce\x9b")- , ("lambda" , "\xce\xbb")- , ("Mu" , "\xce\x9c")- , ("mu" , "\xce\xbc")- , ("Nu" , "\xce\x9d")- , ("nu" , "\xce\xbd")- , ("Omega" , "\xce\xa9")- , ("omega" , "\xcf\x89")- , ("Omicron" , "\xce\x9f")- , ("omicron" , "\xce\xbf")- , ("Phi" , "\xce\xa6")- , ("phi" , "\xcf\x86")- , ("Pi" , "\xce\xa0")- , ("pi" , "\xcf\x80")- , ("piv" , "\xcf\x96")- , ("Psi" , "\xce\xa8")- , ("psi" , "\xcf\x88")- , ("Rho" , "\xce\xa1")- , ("rho" , "\xcf\x81")- , ("Sigma" , "\xce\xa3")- , ("sigma" , "\xcf\x83")- , ("sigmaf" , "\xcf\x82")- , ("Tau" , "\xce\xa4")- , ("tau" , "\xcf\x84")- , ("Theta" , "\xce\x98")- , ("theta" , "\xce\xb8")- , ("thetasym" , "\xcf\x91")- , ("upsih" , "\xcf\x92")- , ("Upsilon" , "\xce\xa5")- , ("upsilon" , "\xcf\x85")- , ("Xi" , "\xce\x9e")- , ("xi" , "\xce\xbe")- , ("Zeta" , "\xce\x96")- , ("zeta" , "\xce\xb6")- , ("crarr" , "\xe2\x86\xb5")- , ("darr" , "\xe2\x86\x93")- , ("dArr" , "\xe2\x87\x93")- , ("harr" , "\xe2\x86\x94")- , ("hArr" , "\xe2\x87\x94")- , ("larr" , "\xe2\x86\x90")- , ("lArr" , "\xe2\x87\x90")- , ("rarr" , "\xe2\x86\x92")- , ("rArr" , "\xe2\x87\x92")- , ("uarr" , "\xe2\x86\x91")- , ("uArr" , "\xe2\x87\x91")- , ("clubs" , "\xe2\x99\xa3")- , ("diams" , "\xe2\x99\xa6")- , ("hearts" , "\xe2\x99\xa5")- , ("spades" , "\xe2\x99\xa0")- , ("loz" , "\xe2\x97\x8a") ]
src/Text/Templating/Heist/Internal.hs view
@@ -6,35 +6,36 @@ module Text.Templating.Heist.Internal where ------------------------------------------------------------------------------+import Blaze.ByteString.Builder import Control.Applicative import Control.Exception (SomeException) import Control.Monad import Control.Monad.CatchIO import Control.Monad.Trans-import qualified Data.Attoparsec.Char8 as AP-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString.Lazy as L+import qualified Data.Attoparsec.Text as AP+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC import Data.Either import qualified Data.Foldable as F import Data.List import qualified Data.Map as Map import Data.Maybe import Data.Monoid+import qualified Data.Text as T+import Data.Text (Text) import Prelude hiding (catch) import System.Directory.Tree hiding (name) import System.FilePath-import Text.XML.Expat.Format-import qualified Text.XML.Expat.Tree as X+import qualified Text.XmlHtml as X -------------------------------------------------------------------------------import Text.Templating.Heist.Constants import Text.Templating.Heist.Types ------------------------------------------------------------------------------ -- | Mappends a doctype to the state.-addDoctype :: Monad m => [ByteString] -> TemplateMonad m ()+addDoctype :: Monad m => [X.DocType] -> TemplateMonad m () addDoctype dt = do modifyTS (\s -> s { _doctypes = _doctypes s `mappend` dt }) @@ -74,7 +75,7 @@ ------------------------------------------------------------------------------ -- | Binds a new splice declaration to a tag name within a 'TemplateState'. bindSplice :: Monad m =>- ByteString -- ^ tag name+ Text -- ^ tag name -> Splice m -- ^ splice action -> TemplateState m -- ^ source state -> TemplateState m@@ -84,8 +85,8 @@ ------------------------------------------------------------------------------ -- | Binds a set of new splice declarations within a 'TemplateState'. bindSplices :: Monad m =>- [(ByteString, Splice m)] -- ^ splices to bind- -> TemplateState m -- ^ start state+ [(Text, Splice m)] -- ^ splices to bind+ -> TemplateState m -- ^ start state -> TemplateState m bindSplices ss ts = foldl' (flip id) ts acts where@@ -95,7 +96,7 @@ ------------------------------------------------------------------------------ -- | Convenience function for looking up a splice. lookupSplice :: Monad m =>- ByteString+ Text -> TemplateState m -> Maybe (Splice m) lookupSplice nm ts = Map.lookup nm $ _spliceMap ts@@ -108,9 +109,9 @@ -- -- FIXME @\"..\"@ currently doesn't work in paths, the solution is non-trivial splitPathWith :: Char -> ByteString -> TPath-splitPathWith s p = if B.null p then [] else (reverse $ B.split s path)+splitPathWith s p = if BC.null p then [] else (reverse $ BC.split s path) where- path = if B.head p == s then B.tail p else p+ path = if BC.head p == s then BC.tail p else p -- | Converts a path into an array of the elements in reverse order using the -- path separator of the local operating system. See 'splitPathWith' for more@@ -130,7 +131,7 @@ singleLookup :: TemplateMap -> TPath -> ByteString- -> Maybe (InternalTemplate, TPath)+ -> Maybe (X.Document, TPath) singleLookup tm path name = fmap (\a -> (a,path)) $ Map.lookup (name:path) tm @@ -140,7 +141,7 @@ traversePath :: TemplateMap -> TPath -> ByteString- -> Maybe (InternalTemplate, TPath)+ -> Maybe (X.Document, TPath) traversePath tm [] name = fmap (\a -> (a,[])) (Map.lookup [name] tm) traversePath tm path name = singleLookup tm path name `mplus`@@ -161,14 +162,14 @@ lookupTemplate :: Monad m => ByteString -> TemplateState m- -> Maybe (InternalTemplate, TPath)+ -> Maybe (X.Document, TPath) lookupTemplate nameStr ts = f (_templateMap ts) path name where (name:p) = case splitTemplatePath nameStr of [] -> [""] ps -> ps path = p ++ (_curContext ts)- f = if '/' `B.elem` nameStr+ f = if '/' `BC.elem` nameStr then singleLookup else traversePath @@ -183,7 +184,7 @@ -- | Adds a template to the template state. insertTemplate :: Monad m => TPath- -> InternalTemplate+ -> X.Document -> TemplateState m -> TemplateState m insertTemplate p t st =@@ -191,17 +192,28 @@ --------------------------------------------------------------------------------- | Adds a template to the template state.+-- | Adds an HTML format template to the template state. addTemplate :: Monad m => ByteString -> Template -> TemplateState m -> TemplateState m addTemplate n t st = insertTemplate (splitTemplatePath n)- (InternalTemplate Nothing t) st+ (X.HtmlDocument X.UTF8 Nothing t) st ------------------------------------------------------------------------------+-- | Adds an XML format template to the template state.+addXMLTemplate :: Monad m =>+ ByteString+ -> Template+ -> TemplateState m+ -> TemplateState m+addXMLTemplate n t st = insertTemplate (splitTemplatePath n)+ (X.XmlDocument X.UTF8 Nothing t) st+++------------------------------------------------------------------------------ -- | Stops the recursive processing of splices. Consider the following -- example: --@@ -233,24 +245,23 @@ ------------------------------------------------------------------------------ -- | Performs splice processing on a single node.-runNode :: Monad m => Node -> Splice m-runNode n@(X.Text _) = return [n]+runNode :: Monad m => X.Node -> Splice m runNode (X.Element nm at ch) = do newAtts <- mapM attSubst at let n = X.Element nm newAtts ch s <- liftM (lookupSplice nm) getTS maybe (runChildren newAtts) (recurseSplice n) s- where runChildren newAtts = do newKids <- runNodeList ch return [X.Element nm newAtts newKids]+runNode n = return [n] ------------------------------------------------------------------------------ -- | Helper function for substituting a parsed attribute into an attribute -- tuple.-attSubst :: (Monad m) => (t, ByteString) -> TemplateMonad m (t, ByteString)+attSubst :: (Monad m) => (t, Text) -> TemplateMonad m (t, Text) attSubst (n,v) = do v' <- parseAtt v return (n,v')@@ -259,24 +270,24 @@ ------------------------------------------------------------------------------ -- | Parses an attribute for any identifier expressions and performs -- appropriate substitution.-parseAtt :: (Monad m) => ByteString -> TemplateMonad m ByteString+parseAtt :: (Monad m) => Text -> TemplateMonad m Text parseAtt bs = do let ast = case AP.feed (AP.parse attParser bs) "" of (AP.Fail _ _ _) -> [] (AP.Done _ res) -> res (AP.Partial _) -> [] chunks <- mapM cvt ast- return $ B.concat chunks+ return $ T.concat chunks where cvt (Literal x) = return x- cvt (Ident x) = getAttributeSplice x+ cvt (Ident x) = getAttributeSplice x ------------------------------------------------------------------------------ -- | AST to hold attribute parsing structure. This is necessary because -- attoparsec doesn't support parsers running in another monad.-data AttAST = Literal ByteString |- Ident ByteString+data AttAST = Literal Text |+ Ident Text deriving (Show) @@ -287,14 +298,14 @@ where escChar = (AP.char '\\' *> AP.anyChar) <|> AP.satisfy (AP.notInClass "\\$")- litParser = Literal <$> (B.pack <$> AP.many1 escChar)+ litParser = Literal <$> (T.pack <$> AP.many1 escChar) identParser = AP.string "$(" *> (Ident <$> AP.takeWhile (/=')')) <* AP.string ")" ------------------------------------------------------------------------------ -- | Get's the attribute value. If the splice's result list contains non-text--- nodes, this will translate them into text nodes with textContent and+-- nodes, this will translate them into text nodes with nodeText and -- concatenate them together. -- -- Originally, this only took the first node from the splices's result list,@@ -314,15 +325,15 @@ -- it would be for the former user to accept that -- \"some \<b\>text\<\/b\> foobar\" is being rendered as \"some \" because -- it's \"more intuitive\".-getAttributeSplice :: Monad m => ByteString -> TemplateMonad m ByteString+getAttributeSplice :: Monad m => Text -> TemplateMonad m Text getAttributeSplice name = do s <- liftM (lookupSplice name) getTS nodes <- maybe (return []) id s- return $ B.concat $ map X.textContent nodes+ return $ T.concat $ map X.nodeText nodes ------------------------------------------------------------------------------ -- | Performs splice processing on a list of nodes.-runNodeList :: Monad m => [Node] -> Splice m+runNodeList :: Monad m => [X.Node] -> Splice m runNodeList nodes = liftM concat $ sequence (map runNode nodes) @@ -335,7 +346,7 @@ ------------------------------------------------------------------------------ -- | Checks the recursion flag and recurses accordingly. Does not recurse -- deeper than mAX_RECURSION_DEPTH to avoid infinite loops.-recurseSplice :: Monad m => Node -> Splice m -> Splice m+recurseSplice :: Monad m => X.Node -> Splice m -> Splice m recurseSplice node splice = do result <- localParamNode (const node) splice ts' <- getTS@@ -355,7 +366,7 @@ -- | Looks up a template name runs a TemplateMonad computation on it. lookupAndRun :: Monad m => ByteString- -> ((InternalTemplate, TPath) -> TemplateMonad m (Maybe a))+ -> ((X.Document, TPath) -> TemplateMonad m (Maybe a)) -> TemplateMonad m (Maybe a) lookupAndRun name k = do ts <- getTS@@ -372,32 +383,52 @@ (\(t,ctx) -> do ts <- getTS putTS (ts {_curContext = ctx})- res <- runNodeList $ _itNodes t+ res <- runNodeList $ X.docContent t restoreTS ts return $ Just res) ------------------------------------------------------------------------------+-- | Sets the document type of a 'X.Document' based on the 'TemplateMonad'+-- value.+fixDocType :: Monad m => X.Document -> TemplateMonad m X.Document+fixDocType d = do+ dts <- getsTS _doctypes+ return $ d { X.docType = listToMaybe dts }+++------------------------------------------------------------------------------+-- | Same as evalWithHooks, but returns the entire 'X.Document' rather than+-- just the nodes. This is the right thing to do if we are starting at the+-- top level.+evalWithHooksInternal :: Monad m+ => ByteString+ -> TemplateMonad m (Maybe X.Document)+evalWithHooksInternal name = lookupAndRun name $ \(t,ctx) -> do+ addDoctype $ maybeToList $ X.docType t+ ts <- getTS+ nodes <- lift $ _preRunHook ts $ X.docContent t+ putTS (ts {_curContext = ctx})+ res <- runNodeList nodes+ restoreTS ts+ newNodes <- lift (_postRunHook ts res)+ newDoc <- fixDocType $ t { X.docContent = newNodes }+ return (Just newDoc)+++------------------------------------------------------------------------------ -- | Looks up a template name evaluates it by calling runNodeList. This also -- executes pre- and post-run hooks and adds the doctype. evalWithHooks :: Monad m => ByteString -> TemplateMonad m (Maybe Template)-evalWithHooks name = lookupAndRun name- (\(t,ctx) -> do- addDoctype $ maybeToList $ _itDoctype t- ts <- getTS- nodes <- lift $ _preRunHook ts $ _itNodes t- putTS (ts {_curContext = ctx})- res <- runNodeList nodes- restoreTS ts- return . Just =<< lift (_postRunHook ts res))+evalWithHooks name = liftM (liftM X.docContent) (evalWithHooksInternal name) ------------------------------------------------------------------------------ -- | Binds a list of constant string splices. bindStrings :: Monad m- => [(ByteString, ByteString)]+ => [(Text, Text)] -> TemplateState m -> TemplateState m bindStrings pairs ts = foldr (uncurry bindString) ts pairs@@ -406,11 +437,11 @@ ------------------------------------------------------------------------------ -- | Binds a single constant string splice. bindString :: Monad m- => ByteString- -> ByteString+ => Text+ -> Text -> TemplateState m -> TemplateState m-bindString n v = bindSplice n $ return [X.Text v]+bindString n v = bindSplice n $ return [X.TextNode v] ------------------------------------------------------------------------------@@ -418,9 +449,9 @@ -- to use when you want to "call" a template and pass in parameters from -- inside a splice. callTemplate :: Monad m- => ByteString -- ^ The name of the template- -> [(ByteString, ByteString)] -- ^ Association list of- -- (name,value) parameter pairs+ => ByteString -- ^ The name of the template+ -> [(Text, Text)] -- ^ Association list of+ -- (name,value) parameter pairs -> TemplateMonad m (Maybe Template) callTemplate name params = do modifyTS $ bindStrings params@@ -428,39 +459,34 @@ --------------------------------------------------------------------------------- | Converts a Template to an InternalTemplate. This can only be done inside--- TemplateMonad where the doctype is available.-toInternalTemplate :: Monad m => Template -> TemplateMonad m InternalTemplate-toInternalTemplate t = do- dts <- getsTS _doctypes- return $ InternalTemplate {- _itDoctype = listToMaybe dts,- _itNodes = t- }------------------------------------------------------------------------------------ | Renders an internal template by prepending the appropriate doctype.-renderInternal :: Monad m => InternalTemplate -> TemplateMonad m ByteString-renderInternal (InternalTemplate dt nodes) =- return $ maybe bs (flip B.append bs) dt+-- Gives the MIME type for a 'X.Document'+mimeType :: X.Document -> ByteString+mimeType d = case d of+ (X.HtmlDocument e _ _) -> "text/html;charset=" `BC.append` enc e+ (X.XmlDocument e _ _) -> "text/xml;charset=" `BC.append` enc e where- bs = formatList' nodes+ enc X.UTF8 = "utf-8"+ -- Should not include byte order designation for UTF-16 since+ -- rendering will include a byte order mark. (RFC 2781, Sec. 3.3)+ enc X.UTF16BE = "utf-16"+ enc X.UTF16LE = "utf-16" --------------------------------------------------------------------------------- | Renders a template from the specified TemplateState.+-- | Renders a template from the specified TemplateState to a 'Builder'. The+-- MIME type returned is based on the detected character encoding, and whether+-- the root template was an HTML or XML format template. It will always be+-- @text/html@ or @text/xml@. If a more specific MIME type is needed for a+-- particular XML application, it must be provided by the application. renderTemplate :: Monad m => TemplateState m -> ByteString- -> m (Maybe ByteString)-renderTemplate ts name = do- evalTemplateMonad- (do mt <- evalWithHooks name- maybe (return Nothing)- (\t -> liftM Just $ renderInternal =<< toInternalTemplate t)- mt- ) (X.Text "") ts+ -> m (Maybe (Builder, MIMEType))+renderTemplate ts name = evalTemplateMonad tpl (X.TextNode "") ts+ where tpl = do mt <- evalWithHooksInternal name+ case mt of+ Nothing -> return Nothing+ Just doc -> return $ Just $ (X.render doc, mimeType doc) ------------------------------------------------------------------------------@@ -469,10 +495,10 @@ -- using bindString, bindStrings, or bindSplice to set up the arguments to the -- template. renderWithArgs :: Monad m- => [(ByteString, ByteString)]+ => [(Text, Text)] -> TemplateState m -> ByteString- -> m (Maybe ByteString)+ -> m (Maybe (Builder, MIMEType)) renderWithArgs args ts = renderTemplate (bindStrings args ts) @@ -480,64 +506,35 @@ -- Template loading ------------------------------------------------------------------------------ --- | Turns an in-memory XML/XHTML bytestring into a (doctype,'[Node]') pair.-parseDoc :: ByteString -> IO (Either String (Maybe ByteString,[Node]))-parseDoc bs = do- let (doctype,rest) = extractDoctype bs- let wrap b = B.concat ["<snap:root>\n", b, "\n</snap:root>"] - return $- mapRight (\n -> (doctype,X.getChildren n)) $- mapLeft genErrorMsg $- X.parse' heistExpatOptions (wrap rest)-- where- genErrorMsg (X.XMLParseError str loc) = locMsg loc ++ ": " ++ translate str-- locMsg (X.XMLParseLocation line col _ _) =- "(line " ++ show (line-1) ++ ", col " ++ show col ++ ")"-- translate "junk after document element" = "document must have a single root element"- translate s = s+------------------------------------------------------------------------------+-- | Type synonym for parsers.+type ParserFun = String -> ByteString -> Either String X.Document --- | Reads an XML document from disk.-getDoc :: String -> IO (Either String InternalTemplate)-getDoc f = do+------------------------------------------------------------------------------+-- | Reads an HTML or XML template from disk.+getDocWith :: ParserFun -> String -> IO (Either String X.Document)+getDocWith parser f = do bs <- catch (liftM Right $ B.readFile f) (\(e::SomeException) -> return $ Left $ show e) - d' <- either (return . Left)- parseDoc- bs-- let d = mapLeft (\s -> f ++ " " ++ s) d'-- return $ either Left- (\(doctype, nodes) -> Right $ InternalTemplate {- _itDoctype = doctype,- _itNodes = nodes- })- d+ let d = either Left (parser f) bs+ return $ mapLeft (\s -> f ++ " " ++ s) d --------------------------------------------------------------------------------- | Checks whether the bytestring has a doctype.-hasDoctype :: ByteString -> Bool-hasDoctype bs = "<!DOCTYPE" `B.isPrefixOf` bs+-- | Reads an HTML template from disk.+getDoc :: String -> IO (Either String X.Document)+getDoc = getDocWith X.parseHTML --------------------------------------------------------------------------------- | Converts a ByteString into a tuple containing a possible doctype--- ByteString and the rest of the document.-extractDoctype :: ByteString -> (Maybe ByteString, ByteString)-extractDoctype bs = - if hasDoctype bs- then (Just $ B.snoc (B.takeWhile p bs) '>',B.tail $ B.dropWhile p bs)- else (Nothing, bs)- where- p = (/='>')+-- | Reads an XML template from disk.+getXMLDoc :: String -> IO (Either String X.Document)+getXMLDoc = getDocWith X.parseHTML + ------------------------------------------------------------------------------ mapLeft :: (a -> b) -> Either a c -> Either b c mapLeft g = either (Left . g) Right@@ -547,25 +544,31 @@ ------------------------------------------------------------------------------ -- | Loads a template with the specified path and filename. The--- template is only loaded if it has a ".tpl" extension.+-- template is only loaded if it has a ".tpl" or ".xtpl" extension. loadTemplate :: String -- ^ path of the template root -> String -- ^ full file path (includes the template root)- -> IO [Either String (TPath, InternalTemplate)] --TemplateMap+ -> IO [Either String (TPath, X.Document)] --TemplateMap loadTemplate templateRoot fname- | ".tpl" `isSuffixOf` fname = do+ | isHTMLTemplate = do c <- getDoc fname- return [fmap (\t -> (splitLocalPath $ B.pack tName, t)) c]+ return [fmap (\t -> (splitLocalPath $ BC.pack tName, t)) c]+ | isXMLTemplate = do+ c <- getXMLDoc fname+ return [fmap (\t -> (splitLocalPath $ BC.pack tName, t)) c] | otherwise = return [] where -- tName is path relative to the template root directory+ isHTMLTemplate = ".tpl" `isSuffixOf` fname+ isXMLTemplate = ".xtpl" `isSuffixOf` fname correction = if last templateRoot == '/' then 0 else 1+ extLen = if isHTMLTemplate then 4 else 5 tName = drop ((length templateRoot)+correction) $ -- We're only dropping the template root, not the whole path- take ((length fname) - 4) fname+ take ((length fname) - extLen) fname ------------------------------------------------------------------------------ -- | Traverses the specified directory structure and builds a--- TemplateState by loading all the files with a ".tpl" extension.+-- TemplateState by loading all the files with a ".tpl" or ".xtpl" extension. loadTemplates :: Monad m => FilePath -> TemplateState m -> IO (Either String (TemplateState m)) loadTemplates dir ts = do d <- readDirectoryWith (loadTemplate dir) dir@@ -578,34 +581,20 @@ ------------------------------------------------------------------------------ -- | Reversed list of directories. This holds the path to the template-runHookInternal :: Monad m => (Template -> m Template)- -> InternalTemplate- -> m InternalTemplate-runHookInternal f t = do- n <- f $ _itNodes t- return $ t { _itNodes = n }+runHook :: Monad m => (Template -> m Template)+ -> X.Document+ -> m X.Document+runHook f t = do+ n <- f $ X.docContent t+ return $ t { X.docContent = n } ------------------------------------------------------------------------------ -- | Runs the onLoad hook on the template and returns the `TemplateState` -- with the result inserted.-loadHook :: Monad m => TemplateState m -> (TPath, InternalTemplate) -> IO (TemplateState m)+loadHook :: Monad m => TemplateState m -> (TPath, X.Document) -> IO (TemplateState m) loadHook ts (tp, t) = do- t' <- runHookInternal (_onLoadHook ts) t+ t' <- runHook (_onLoadHook ts) t return $ insertTemplate tp t' ts ----------------------------------------------------------------------------------- These are here until we can get them into hexpat.---------------------------------------------------------------------------------formatList :: (X.GenericXMLString tag, X.GenericXMLString text) =>- [X.Node tag text]- -> L.ByteString-formatList nodes = foldl L.append L.empty $ map formatNode nodes--formatList' :: (X.GenericXMLString tag, X.GenericXMLString text) =>- [X.Node tag text]- -> B.ByteString-formatList' = B.concat . L.toChunks . formatList
src/Text/Templating/Heist/Splices/Apply.hs view
@@ -3,10 +3,11 @@ module Text.Templating.Heist.Splices.Apply where -------------------------------------------------------------------------------import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B import Data.Maybe-import qualified Text.XML.Expat.Tree as X+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Text.XmlHtml as X ------------------------------------------------------------------------------ import Text.Templating.Heist.Internal@@ -14,13 +15,13 @@ ------------------------------------------------------------------------------ -- | Default name for the apply splice.-applyTag :: ByteString+applyTag :: Text applyTag = "apply" ------------------------------------------------------------------------------ -- | Default attribute name for the apply tag.-applyAttr :: ByteString+applyAttr :: Text applyAttr = "template" @@ -29,22 +30,23 @@ applyImpl :: Monad m => Splice m applyImpl = do node <- getParamNode- case X.getAttribute node applyAttr of+ case X.getAttribute applyAttr node of Nothing -> return [] -- TODO: error handling Just attr -> do st <- getTS maybe (return []) -- TODO: error handling (\(t,ctx) -> do- addDoctype $ maybeToList $ _itDoctype t- processedChildren <- runNodeList $ X.getChildren node+ addDoctype $ maybeToList $ X.docType t+ processedChildren <- runNodeList $ X.childNodes node modifyTS (bindSplice "content" $ return processedChildren) setContext ctx- result <- runNodeList $ _itNodes t+ result <- runNodeList $ X.docContent t restoreTS st return result)- (lookupTemplate attr (st {_curContext = nextCtx attr st}))+ (lookupTemplate (T.encodeUtf8 attr)+ (st {_curContext = nextCtx attr st})) where nextCtx name st- | B.isPrefixOf "/" name = []+ | T.isPrefixOf "/" name = [] | otherwise = _curContext st
src/Text/Templating/Heist/Splices/Bind.hs view
@@ -3,21 +3,21 @@ module Text.Templating.Heist.Splices.Bind where -------------------------------------------------------------------------------import Data.ByteString.Char8 (ByteString)-import qualified Text.XML.Expat.Tree as X+import Data.Text (Text)+import qualified Text.XmlHtml as X ------------------------------------------------------------------------------ import Text.Templating.Heist.Internal import Text.Templating.Heist.Types -- | Default name for the bind splice.-bindTag :: ByteString+bindTag :: Text bindTag = "bind" ------------------------------------------------------------------------------ -- | Default attribute name for the bind tag.-bindAttr :: ByteString+bindAttr :: Text bindAttr = "tag" @@ -28,10 +28,10 @@ node <- getParamNode maybe (return ()) (add node)- (X.getAttribute node bindAttr)+ (X.getAttribute bindAttr node) return [] where- add node nm = modifyTS $ bindSplice nm (return $ X.getChildren node)+ add node nm = modifyTS $ bindSplice nm (return $ X.childNodes node)
src/Text/Templating/Heist/Splices/Ignore.hs view
@@ -3,7 +3,7 @@ module Text.Templating.Heist.Splices.Ignore where -------------------------------------------------------------------------------import Data.ByteString.Char8 (ByteString)+import Data.Text (Text) ------------------------------------------------------------------------------ import Text.Templating.Heist.Types@@ -11,7 +11,7 @@ ------------------------------------------------------------------------------ -- | Default name for the ignore splice.-ignoreTag :: ByteString+ignoreTag :: Text ignoreTag = "ignore"
src/Text/Templating/Heist/Splices/Markdown.hs view
@@ -6,6 +6,9 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import Data.Maybe import Control.Concurrent import Control.Exception (throwIO)@@ -19,10 +22,9 @@ import System.FilePath.Posix import System.IO import System.Process-import Text.XML.Expat.Tree hiding (Node)+import Text.XmlHtml -------------------------------------------------------------------------------import Text.Templating.Heist.Constants import Text.Templating.Heist.Types data PandocMissingException = PandocMissingException@@ -47,7 +49,7 @@ ------------------------------------------------------------------------------ -- | Default name for the markdown splice.-markdownTag :: ByteString+markdownTag :: Text markdownTag = "markdown" ------------------------------------------------------------------------------@@ -59,16 +61,20 @@ when (isNothing pdMD) $ liftIO $ throwIO PandocMissingException tree <- getParamNode- markup <- liftIO $- case getAttribute tree "file" of- Just f -> pandoc (fromJust pdMD) templatePath $ BC.unpack f- Nothing -> pandocBS (fromJust pdMD) $ textContent tree+ (source,markup) <- liftIO $+ case getAttribute "file" tree of+ Just f -> do+ m <- pandoc (fromJust pdMD) templatePath $ T.unpack f+ return (T.unpack f,m)+ Nothing -> do+ m <- pandocBS (fromJust pdMD) $ T.encodeUtf8 $ nodeText tree+ return ("inline_splice",m) - let ee = parse' heistExpatOptions markup+ let ee = parseHTML source markup case ee of- (Left e) -> throw $ MarkdownException- $ BC.pack ("Error parsing markdown output: " ++ show e)- (Right n) -> return [n]+ Left e -> throw $ MarkdownException+ $ BC.pack ("Error parsing markdown output: " ++ e)+ Right d -> return (docContent d) pandoc :: FilePath -> FilePath -> FilePath -> IO ByteString@@ -77,8 +83,8 @@ when (isFail ex) $ throw $ MarkdownException serr return $ BC.concat [ "<div class=\"markdown\">\n"- , sout- , "\n</div>" ]+ , sout+ , "\n</div>" ] where isFail ExitSuccess = False
src/Text/Templating/Heist/Splices/Static.hs view
@@ -10,16 +10,16 @@ import Control.Concurrent import Control.Monad import Control.Monad.Trans-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B import Data.IORef import qualified Data.Map as Map import Data.Map (Map) import Data.Maybe import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T import System.Random-import Text.XML.Expat.Cursor-import Text.XML.Expat.Tree hiding (Node)+import Text.XmlHtml.Cursor+import Text.XmlHtml hiding (Node) ------------------------------------------------------------------------------@@ -29,7 +29,7 @@ ------------------------------------------------------------------------------ -- | State for storing static tag information-newtype StaticTagState = STS (MVar (Map ByteString Template))+newtype StaticTagState = STS (MVar (Map Text Template)) ------------------------------------------------------------------------------@@ -48,7 +48,7 @@ -> TemplateMonad m Template staticImpl (STS mv) = do tree <- getParamNode- let i = fromJust $ getAttribute tree "id"+ let i = fromJust $ getAttribute "id" tree mp <- liftIO $ readMVar mv @@ -56,7 +56,7 @@ let mbn = Map.lookup i mp case mbn of Nothing -> do- nodes' <- runNodeList $ getChildren tree+ nodes' <- runNodeList $ childNodes tree return $! (Map.insert i nodes' mp, nodes') (Just n) -> do stopRecursion@@ -89,10 +89,10 @@ assignIds setref = mapM f where- f node = g $ fromTree node+ f node = g $ fromNode node getId = do- i <- liftM (B.pack . show) generateId+ i <- liftM (T.pack . show) generateId st <- readIORef setref if Set.member i st then getId@@ -102,13 +102,13 @@ g curs = do let node = current curs- curs' <- if getName node == "static"+ curs' <- if tagName node == Just "static" then do i <- getId- return $ modifyContent (setAttribute "id" i) curs+ return $ modifyNode (setAttribute "id" i) curs else return curs let mbc = nextDF curs'- maybe (return $ toTree curs') g mbc+ maybe (return $ topNode curs') g mbc
src/Text/Templating/Heist/Types.hs view
@@ -31,34 +31,22 @@ import qualified Data.Map as Map import Data.Map (Map) import Data.Monoid+import Data.Text (Text) import Data.Typeable import Prelude hiding (catch)-import qualified Text.XML.Expat.Tree as X------------------------------------------------------------------------------------ | Heist templates are XML documents. The hexpat library is polymorphic over--- the type of strings, so here we define a 'Node' alias to fix the string--- types of the tag names and tag bodies to 'ByteString'.-type Node = X.Node ByteString ByteString+import qualified Text.XmlHtml as X ------------------------------------------------------------------------------ -- | A 'Template' is a forest of XML nodes. Here we deviate from the "single -- root node" constraint of well-formed XML because we want to allow templates -- to contain fragments of a document that may not have a single root.-type Template = [Node]+type Template = [X.Node] --------------------------------------------------------------------------------- | An 'InternalTemplate' carries a doctype with it that we get from the--- template at load time. The tricks that we're playing so templates don't--- have to have a single root node screw up doctypes, so we have to handle--- them manually.-data InternalTemplate = InternalTemplate {- _itDoctype :: Maybe ByteString,- _itNodes :: [Node]-} deriving (Eq, Show)+-- | MIME Type. The type alias is here to make the API clearer.+type MIMEType = ByteString ------------------------------------------------------------------------------@@ -68,8 +56,8 @@ --------------------------------------------------------------------------------- | All templates are stored in a map.-type TemplateMap = Map TPath InternalTemplate+-- | All documents representing templates are stored in a map.+type TemplateMap = Map TPath X.Document ------------------------------------------------------------------------------@@ -79,7 +67,7 @@ ------------------------------------------------------------------------------ -- | SpliceMap associates a name and a Splice.-type SpliceMap m = Map ByteString (Splice m)+type SpliceMap m = Map Text (Splice m) ------------------------------------------------------------------------------@@ -106,7 +94,7 @@ -- | A hook run on all templates just after they are rendered. , _postRunHook :: Template -> m Template -- | The doctypes encountered during template processing.- , _doctypes :: [ByteString]+ , _doctypes :: [X.DocType] } @@ -149,7 +137,7 @@ -- provides \"passthrough\" instances for many of the monads you might use in -- the inner monad. newtype TemplateMonad m a = TemplateMonad {- runTemplateMonad :: Node+ runTemplateMonad :: X.Node -> TemplateState m -> m (a, TemplateState m) }@@ -159,7 +147,7 @@ -- | Evaluates a template monad as a computation in the underlying monad. evalTemplateMonad :: Monad m => TemplateMonad m a- -> Node+ -> X.Node -> TemplateState m -> m a evalTemplateMonad m r s = do@@ -314,14 +302,14 @@ -- getChildren@ returns a list containing one 'Text' node containing part of -- Hamlet's speech. @getParamNode >>= getAttribute \"author\"@ would return -- @Just "Shakespeare"@.-getParamNode :: Monad m => TemplateMonad m Node+getParamNode :: Monad m => TemplateMonad m X.Node getParamNode = TemplateMonad $ \r s -> return (r,s) ------------------------------------------------------------------------------ -- | TemplateMonad's 'local'. localParamNode :: Monad m- => (Node -> Node)+ => (X.Node -> X.Node) -> TemplateMonad m a -> TemplateMonad m a localParamNode f m = TemplateMonad $ \r s -> runTemplateMonad m (f r) s
test/heist-testsuite.cabal view
@@ -10,14 +10,16 @@ build-depends: QuickCheck >= 2, attoparsec >= 0.8.1 && < 0.9,+ attoparsec-text >= 0.8 && < 0.9, base >= 4 && < 5,+ blaze-builder == 0.2.*, bytestring, containers, directory, directory-tree, process, filepath,- hexpat >= 0.19 && <0.20,+ xmlhtml == 0.1.*, HUnit >= 1.2 && < 2, monads-fd, random,@@ -25,6 +27,7 @@ test-framework >= 0.3.1 && <0.4, test-framework-hunit >= 0.2.5 && < 0.3, test-framework-quickcheck2 >= 0.2.6 && < 0.3,+ text >= 0.10 && < 0.12, transformers ghc-options: -O2 -Wall -fhpc -fwarn-tabs -funbox-strict-fields -threaded
test/suite/Text/Templating/Heist/Tests.hs view
@@ -9,6 +9,7 @@ ) where ------------------------------------------------------------------------------+import Blaze.ByteString.Builder import Control.Monad.State import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B@@ -16,6 +17,9 @@ import qualified Data.Map as Map import Data.Maybe import Data.Monoid+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Text (Text) import System.IO.Unsafe import Test.Framework (Test) import Test.Framework.Providers.HUnit@@ -32,9 +36,8 @@ import Text.Templating.Heist.Splices.Apply import Text.Templating.Heist.Splices.Ignore import Text.Templating.Heist.Splices.Markdown-import Text.XML.Expat.Cursor-import Text.XML.Expat.Format-import qualified Text.XML.Expat.Tree as X+import qualified Text.XmlHtml as X+import qualified Text.XmlHtml.Cursor as X ------------------------------------------------------------------------------@@ -68,7 +71,7 @@ let result = buildResult bind spliceResult <- run $ evalTemplateMonad (runNodeList template)- (X.Text "")+ (X.TextNode "") (emptyTemplateState ".") assert $ result == spliceResult @@ -96,7 +99,7 @@ addTest :: IO () addTest = do H.assertEqual "lookup test" (Just []) $- fmap (_itNodes . fst) $ lookupTemplate "aoeu" ts+ fmap (X.docContent . fst) $ lookupTemplate "aoeu" ts H.assertEqual "splice touched" 0 $ Map.size (_spliceMap ts) @@ -155,7 +158,7 @@ ets <- loadT "templates" either (error "Error loading templates") (\ts -> do t <- renderTemplate ts ""- H.assertBool "renderNoName" $ t == Nothing+ H.assertBool "renderNoName" $ isNothing t ) ets @@ -164,11 +167,14 @@ doctypeTest = do ets <- loadT "templates" let ts = either (error "Error loading templates") id ets- index <- renderTemplate ts "index"- H.assertBool "doctype test index" $ hasDoctype $ fromJust index- ioc <- renderTemplate ts "ioc"- H.assertBool "doctype test ioc" $ hasDoctype $ fromJust ioc-+ Just (indexDoc, indexMIME) <- renderTemplate ts "index"+ H.assertBool "doctype test index" $ isJust $ X.docType $+ fromRight $ (X.parseHTML "index") $ toByteString $ indexDoc+ Just (iocDoc, iocMIME) <- renderTemplate ts "ioc"+ H.assertBool "doctype test ioc" $ isJust $ X.docType $+ fromRight $ (X.parseHTML "index") $ toByteString $ iocDoc+ where fromRight (Right x) = x+ fromRight (Left s) = error s ------------------------------------------------------------------------------ attrSubstTest :: H.Assertion@@ -179,13 +185,13 @@ check ts "pre__post" where- setTs val = bindSplice "foo" (return [X.Text val])+ setTs val = bindSplice "foo" (return [X.TextNode val]) check ts str = do- res <- renderTemplate ts "attrs"- H.assertBool ("attr subst " ++ (show str)) $- not $ B.null $ snd $ B.breakSubstring str $ fromJust res- H.assertBool ("attr subst foo") $- not $ B.null $ snd $ B.breakSubstring "$(foo)" $ fromJust res+ Just (resDoc, resMIME) <- renderTemplate ts "attrs"+ H.assertBool ("attr subst " ++ (show str)) $ not $ B.null $+ snd $ B.breakSubstring str $ toByteString $ resDoc+ H.assertBool ("attr subst foo") $ not $ B.null $+ snd $ B.breakSubstring "$(foo)" $ toByteString $ resDoc ------------------------------------------------------------------------------@@ -193,20 +199,20 @@ bindAttrTest = do ets <- loadT "templates" let ts = either (error "Error loading templates") id ets- check ts "<div id=\"zzzzz\""+ check ts "<div id=\'zzzzz\'" where check ts str = do- res <- renderTemplate ts "bind-attrs"- H.assertBool ("attr subst " ++ (show str)) $- not $ B.null $ snd $ B.breakSubstring str $ fromJust res- H.assertBool ("attr subst bar") $- B.null $ snd $ B.breakSubstring "$(bar)" $ fromJust res+ Just (resDoc, resMIME) <- renderTemplate ts "bind-attrs"+ H.assertBool ("attr subst " ++ (show str)) $ not $ B.null $+ snd $ B.breakSubstring str $ toByteString $ resDoc+ H.assertBool ("attr subst bar") $ B.null $+ snd $ B.breakSubstring "$(bar)" $ toByteString $ resDoc ------------------------------------------------------------------------------ htmlExpected :: ByteString-htmlExpected = "<div class=\"markdown\"><p>This <em>is</em> a test.</p></div>"+htmlExpected = "<div class=\'markdown\'><p>This <em>is</em> a test.</p></div>" ------------------------------------------------------------------------------@@ -220,9 +226,9 @@ where check ts str = do- result <- liftM (fmap $ B.filter (/= '\n')) $- renderTemplate ts "markdown"- H.assertEqual ("Should match " ++ (show str)) str (fromJust result)+ Just (doc, mime) <- renderTemplate ts "markdown"+ let result = B.filter (/= '\n') (toByteString doc)+ H.assertEqual ("Should match " ++ (show str)) str result ------------------------------------------------------------------------------@@ -230,10 +236,11 @@ markdownTextTest :: H.Assertion markdownTextTest = do result <- evalTemplateMonad (markdownSplice ".")- (X.Text "This *is* a test.")+ (X.TextNode "This *is* a test.") (emptyTemplateState ".") H.assertEqual "Markdown text" htmlExpected - (B.filter (/= '\n') $ formatList' result)+ (B.filter (/= '\n') $ toByteString $+ X.render (X.HtmlDocument X.UTF8 Nothing result)) ------------------------------------------------------------------------------@@ -252,7 +259,7 @@ let es = (emptyTemplateState ".") :: TemplateState IO res <- evalTemplateMonad ignoreImpl (X.Element "ignore" [("tag", "ignorable")] - [X.Text "This should be ignored"]) es+ [X.TextNode "This should be ignored"]) es H.assertEqual "<ignore> tag" [] res @@ -292,10 +299,10 @@ -------------------------------------------------------------------------------limitedDepth :: Int -> Gen Node-limitedDepth 0 = liftM (X.Text . B.pack) textGen+limitedDepth :: Int -> Gen X.Node+limitedDepth 0 = liftM (X.TextNode . T.pack) textGen limitedDepth n =- oneof [ liftM (X.Text . B.pack) textGen+ oneof [ liftM (X.TextNode . T.pack) textGen , liftM3 X.Element arbitrary (liftM (take 2) arbitrary) (liftM (take 3) $ listOf $ limitedDepth (n - 1))@@ -306,18 +313,18 @@ -- | Returns the number of unique insertion points in the tree. -- If h = insertAt f n g", the following property holds: -- insSize h == (insSize f) + (insSize g) - 1-insSize :: [X.Node tag text] -> Int+insSize :: [X.Node] -> Int insSize ns = 1 + (sum $ map nodeSize ns)- where nodeSize (X.Text _) = 1+ where nodeSize (X.TextNode _) = 1 nodeSize (X.Element _ _ c) = 1 + (insSize c) -------------------------------------------------------------------------------insertAt :: [Node] -> Int -> [Node] -> [Node]+insertAt :: [X.Node] -> Int -> [X.Node] -> [X.Node] insertAt elems 0 ns = elems ++ ns insertAt elems _ [] = elems-insertAt elems n list = maybe [] (toForest . root) $- evalState (processNode elems $ fromJust $ fromForest list) n+insertAt elems n list = maybe [] X.topNodes $+ evalState (processNode elems $ fromJust $ X.fromNodes list) n ------------------------------------------------------------------------------@@ -326,19 +333,19 @@ -------------------------------------------------------------------------------processNode :: [Node] -> Loc -> Insert (Maybe Loc)+processNode :: [X.Node] -> X.Cursor -> Insert (Maybe X.Cursor) processNode elems loc = liftM2 mplus (move >> goDown loc) (move >> goRight loc) where goDown l =- case current l of- X.Text _ -> modify (+1) >> return Nothing- X.Element _ _ _ -> doneCheck (insertManyFirstChild elems)- firstChild+ case X.current l of+ X.TextNode _ -> modify (+1) >> return Nothing+ X.Element _ _ _ -> doneCheck (X.insertManyFirstChild elems)+ X.firstChild l - goRight = doneCheck (Just . insertManyRight elems) right+ goRight = doneCheck (Just . X.insertManyRight elems) X.right doneCheck insertFunc next l = do s <- get@@ -352,35 +359,35 @@ -- template. (Old convenience code.) quickRender :: FilePath -> ByteString -> IO (Maybe ByteString) quickRender baseDir name = do- ts <- loadTS baseDir- renderTemplate ts name+ ts <- loadTS baseDir+ res <- renderTemplate ts name+ return (fmap (toByteString . fst) res) -------------------------------------------------------------------------------newtype Name = Name { unName :: B.ByteString } deriving (Show)+newtype Name = Name { unName :: Text } deriving (Show) instance Arbitrary Name where arbitrary = do x <- elements identStartChar n <- choose (4,10) rest <- vectorOf n $ elements identChar- return $ Name $ B.pack (x:rest)+ return $ Name $ T.pack (x:rest) -instance Arbitrary Node where+instance Arbitrary X.Node where arbitrary = limitedDepth 3- shrink (X.Text _) = []+ shrink (X.TextNode _) = [] shrink (X.Element _ [] []) = [] shrink (X.Element n [] (_:cs)) = [X.Element n [] cs] shrink (X.Element n (_:as) []) = [X.Element n as []] shrink (X.Element n as cs) = [X.Element n as (tail cs), X.Element n (tail as) cs] -instance Arbitrary B.ByteString where+instance Arbitrary T.Text where arbitrary = liftM unName arbitrary -- -- Code for inserting nodes into any point of a tree ---type Loc = Cursor B.ByteString B.ByteString type Insert a = State Int a @@ -390,8 +397,8 @@ -- Data type encapsulating the parameters for a bind operation data Bind = Bind { _bindElemName :: Name- , _bindChildren :: [Node]- , _bindDoc :: [Node]+ , _bindChildren :: [X.Node]+ , _bindDoc :: [X.Node] , _bindPos :: Int , _bindRefPos :: Int } -- deriving (Show)@@ -426,29 +433,31 @@ , "Splice result:" , L.unpack $ L.concat $ map formatNode $ unsafePerformIO $ evalTemplateMonad (runNodeList $ buildBindTemplate b)- (X.Text "") (emptyTemplateState ".")+ (X.TextNode "") (emptyTemplateState ".") , "Template:" , L.unpack $ L.concat $ map formatNode $ buildBindTemplate b ]-+ where+ formatNode n = toLazyByteString $ X.render+ $ X.HtmlDocument X.UTF8 Nothing [n] -------------------------------------------------------------------------------buildNode :: B.ByteString -> B.ByteString -> Bind -> Node+buildNode :: Text -> Text -> Bind -> X.Node buildNode tag attr (Bind s c _ _ _) = X.Element tag [(attr, unName s)] c -------------------------------------------------------------------------------buildBind :: Bind -> Node+buildBind :: Bind -> X.Node buildBind = buildNode "bind" "tag" -------------------------------------------------------------------------------empty :: tag -> X.Node tag text+empty :: Text -> X.Node empty n = X.Element n [] [] -------------------------------------------------------------------------------buildBindTemplate :: Bind -> [Node]+buildBindTemplate :: Bind -> [X.Node] buildBindTemplate s@(Bind n _ d b r) = insertAt [empty $ unName $ n] pos $ withBind where bind = [buildBind s]@@ -458,7 +467,7 @@ -------------------------------------------------------------------------------buildResult :: Bind -> [Node]+buildResult :: Bind -> [X.Node] buildResult (Bind _ c d b r) = insertAt c (b + r) d @@ -467,9 +476,9 @@ data Apply = Apply { _applyName :: Name- , _applyCaller :: [Node]+ , _applyCaller :: [X.Node] , _applyCallee :: Template- , _applyChildren :: [Node]+ , _applyChildren :: [X.Node] , _applyPos :: Int } deriving (Show) @@ -486,24 +495,24 @@ -------------------------------------------------------------------------------buildApplyCaller :: Apply -> [Node]+buildApplyCaller :: Apply -> [X.Node] buildApplyCaller (Apply name caller _ kids pos) = insertAt [X.Element "apply" [("template", unName name)] kids] pos caller -------------------------------------------------------------------------------calcCorrect :: Apply -> [Node]+calcCorrect :: Apply -> [X.Node] calcCorrect (Apply _ caller callee _ pos) = insertAt callee pos caller -------------------------------------------------------------------------------calcResult :: (MonadIO m) => Apply -> m [Node]+calcResult :: (MonadIO m) => Apply -> m [X.Node] calcResult apply@(Apply name _ callee _ _) = evalTemplateMonad (runNodeList $ buildApplyCaller apply)- (X.Text "") ts+ (X.TextNode "") ts - where ts = setTemplates (Map.singleton [unName name]- (InternalTemplate Nothing callee))+ where ts = setTemplates (Map.singleton [T.encodeUtf8 $ unName name]+ (X.HtmlDocument X.UTF8 Nothing callee)) (emptyTemplateState ".")