Runtime Hamlet template parsing/rendering

Hey all, An often-mentioned "cool feature" for Hamlet would be to support runtime parsing/rendering of templates. Currently, the only supported method is quasi-quoting a template and thereby have it parsed into Haskell code at compile time. In my opinion, this is definitely the preferred way of using Hamlet, as it gives you very solid compile-time guarantees of correct syntax and type safety. Nonetheless, there are some use cases (static site generation via Hakyll, a hamlet-to-html tool, etc) that would really benefit from runtime parsing. It turns out this is pretty simple to add, except for one thing: I can't figure out a good API for passing in variables for a template. Hamlet templates have essentially four different datatypes they recognize: * Html * Some URL data type * That same URL datatype along with a [(String, String)] to represent query-string parameters * A Hamlet template In addition, $forall and $maybe need lists and Maybe values, respectively. Variable lookup is handled by a tree, which allows you to express arbitrary function application. $if requires Bools. So the question is, what should an API look like? The parse function is fairly straight-forward: parseHamletRT :: HamletSettings -> String -> Either HamletException HamletRT However, the render function is more complicated. I've toyed with a few possible ideas: * Use the data-object package with some complicated HamletData datatype. * Just do the complicated HamletData datatype. * Type lookup functions as parameters. * Disallow most of the more complicated features in Hamlet, like URL and subtemplates, and just allow dollar-sign interpolation with $forall and $if. That tree datatype for variable names could be collapsed into a [String]. Thoughts on the matter are welcome, as well as sample use cases you have for such a function. Michael

Great; I like the idea of easy transition to/from hakyll. I'd also use this for "dev mode", loading templates from separate files any time they have changed at runtime. I don't have an answer to your question, but wanted to raise the issue of hamlet's polymorphic url type. I found this quite a bump in the hamlet learning curve; it took me a while to figure out what exactly "a hamlet", the thing I define with [$hamlet|...|], is. Current intuition is "it's a mostly-evaluated template - pass it a url-tranforming function to get the final html". I've learned what types to use to keep it quiet, but I haven't yet learned how to get value from the url polymorphism. Do you use it ?

Well, runtime loading is definitely something I want (and the single biggest
reason I haven't switched to using Hamlet yet), as I'm currently building my
app and then uploading it to my server which is a rather slow process. Being
able to upload a new template (or edit it in place) most of the time rather
and a completely new binary would be a major time saver. As to your
question, I'm not entirely sure, as there needs to be some sort of
information about the expected template available at compile time. It ruins
the symmetry somewhat, but maybe create a compile time version of the
template that essentially specifies what sort of variables are expected, and
so long as the variables remain the same at runtime (and there are no syntax
errors) the templates should continue to function properly. I'm still pretty
new to Haskell, so the kind of reflective black magic necessary to do
something like that is a bit beyond me currently, but perhaps some sort of
new TH to specify name -> type bindings in the template?
Something like:
[$runtimehamlet|
exampleVar -> SomeType
anotherVar -> SomeOtherType
|]
I'm not sure if it would be better to provide some mechanism to specify
which template that's expected to be the args for, or if you should have to
define an "argument set" for each template you plan to use.
-R. Kyle Murphy
--
Curiosity was framed, Ignorance killed the cat.
On Sat, Jul 31, 2010 at 15:34, Michael Snoyman
Hey all,
An often-mentioned "cool feature" for Hamlet would be to support runtime parsing/rendering of templates. Currently, the only supported method is quasi-quoting a template and thereby have it parsed into Haskell code at compile time. In my opinion, this is definitely the preferred way of using Hamlet, as it gives you very solid compile-time guarantees of correct syntax and type safety. Nonetheless, there are some use cases (static site generation via Hakyll, a hamlet-to-html tool, etc) that would really benefit from runtime parsing.
It turns out this is pretty simple to add, except for one thing: I can't figure out a good API for passing in variables for a template. Hamlet templates have essentially four different datatypes they recognize:
* Html * Some URL data type * That same URL datatype along with a [(String, String)] to represent query-string parameters * A Hamlet template
In addition, $forall and $maybe need lists and Maybe values, respectively. Variable lookup is handled by a tree, which allows you to express arbitrary function application. $if requires Bools.
So the question is, what should an API look like? The parse function is fairly straight-forward:
parseHamletRT :: HamletSettings -> String -> Either HamletException HamletRT
However, the render function is more complicated. I've toyed with a few possible ideas:
* Use the data-object package with some complicated HamletData datatype. * Just do the complicated HamletData datatype. * Type lookup functions as parameters. * Disallow most of the more complicated features in Hamlet, like URL and subtemplates, and just allow dollar-sign interpolation with $forall and $if. That tree datatype for variable names could be collapsed into a [String].
Thoughts on the matter are welcome, as well as sample use cases you have for such a function.
Michael
_______________________________________________ web-devel mailing list web-devel@haskell.org http://www.haskell.org/mailman/listinfo/web-devel

On Sat, Jul 31, 2010 at 12:34 PM, Michael Snoyman
Hey all, An often-mentioned "cool feature" for Hamlet would be to support runtime parsing/rendering of templates. Currently, the only supported method is quasi-quoting a template and thereby have it parsed into Haskell code at compile time. In my opinion, this is definitely the preferred way of using Hamlet, as it gives you very solid compile-time guarantees of correct syntax and type safety. Nonetheless, there are some use cases (static site generation via Hakyll, a hamlet-to-html tool, etc) that would really benefit from runtime parsing. It turns out this is pretty simple to add, except for one thing: I can't figure out a good API for passing in variables for a template. Hamlet templates have essentially four different datatypes they recognize: * Html * Some URL data type * That same URL datatype along with a [(String, String)] to represent query-string parameters * A Hamlet template In addition, $forall and $maybe need lists and Maybe values, respectively. Variable lookup is handled by a tree, which allows you to express arbitrary function application. $if requires Bools. So the question is, what should an API look like? The parse function is fairly straight-forward: parseHamletRT :: HamletSettings -> String -> Either HamletException HamletRT However, the render function is more complicated. I've toyed with a few possible ideas: * Use the data-object package with some complicated HamletData datatype. * Just do the complicated HamletData datatype. * Type lookup functions as parameters. * Disallow most of the more complicated features in Hamlet, like URL and subtemplates, and just allow dollar-sign interpolation with $forall and $if. That tree datatype for variable names could be collapsed into a [String]. Thoughts on the matter are welcome, as well as sample use cases you have for such a function. Michael _______________________________________________ web-devel mailing list web-devel@haskell.org http://www.haskell.org/mailman/listinfo/web-devel
My biggest sample use case would be rendering URLs. For a simple blog/CMS type application that I have, I'm using Pandoc to render the posts themselves and Hamlet for templating the rest of the page. The disadvantage is that I have to hard-code URLs inside posts. So it would be good to have some access to the URL rendering function at runtime. Alex

Thanks for the input everyone. I've just pushed the new Hamlet runtime code
to github[1]. Instead of using data-object, I've created a new datatype:
HamletData[2]. Runtime templates support *almost* the entirety of valid
Hamlet documents; the only exception is complicated references, such as:
$(foo.bar).baz$
I don't think this should be a problem in real life; that syntax was only
added to support functions with multiple arguments, and since runtime
templates don't deal with functions, it should work just fine.
I'd appreciate feedback on this before releasing. I'm planning on making
this version 0.4.2. When I *do* make a release, I'll most likely accompany
it with a blog post explaining how to use it, and discuss when you should
use runtime versus quasi-quoted templates.
Michael
[1] http://github.com/snoyberg/hamlet
http://github.com/snoyberg/hamlet[2]
http://github.com/snoyberg/hamlet/blob/master/Text/Hamlet/RT.hs#L21
On Sat, Jul 31, 2010 at 10:34 PM, Michael Snoyman
Hey all,
An often-mentioned "cool feature" for Hamlet would be to support runtime parsing/rendering of templates. Currently, the only supported method is quasi-quoting a template and thereby have it parsed into Haskell code at compile time. In my opinion, this is definitely the preferred way of using Hamlet, as it gives you very solid compile-time guarantees of correct syntax and type safety. Nonetheless, there are some use cases (static site generation via Hakyll, a hamlet-to-html tool, etc) that would really benefit from runtime parsing.
It turns out this is pretty simple to add, except for one thing: I can't figure out a good API for passing in variables for a template. Hamlet templates have essentially four different datatypes they recognize:
* Html * Some URL data type * That same URL datatype along with a [(String, String)] to represent query-string parameters * A Hamlet template
In addition, $forall and $maybe need lists and Maybe values, respectively. Variable lookup is handled by a tree, which allows you to express arbitrary function application. $if requires Bools.
So the question is, what should an API look like? The parse function is fairly straight-forward:
parseHamletRT :: HamletSettings -> String -> Either HamletException HamletRT
However, the render function is more complicated. I've toyed with a few possible ideas:
* Use the data-object package with some complicated HamletData datatype. * Just do the complicated HamletData datatype. * Type lookup functions as parameters. * Disallow most of the more complicated features in Hamlet, like URL and subtemplates, and just allow dollar-sign interpolation with $forall and $if. That tree datatype for variable names could be collapsed into a [String].
Thoughts on the matter are welcome, as well as sample use cases you have for such a function.
Michael

On 8/5/10 12:57 AM, Michael Snoyman wrote:
Thanks for the input everyone. I've just pushed the new Hamlet runtime code to github[1]. Instead of using data-object, I've created a new datatype: HamletData[2]. Runtime templates support *almost* the entirety of valid Hamlet documents;
Excellent! With the following kludgery I was able to change hamlet files and see the effect immediately. A boon for development. Useful parse errors in the browser too. It's not all smooth of course. Including other hamlets and integrating RT and non-RT hamlets seems a lot of work. ... import Yesod hiding (defaultHamletSettings) import Control.Failure import Text.Hamlet.Parse (defaultHamletSettings) import Text.Hamlet.RT stringToHamletRT :: Failure HamletException m => String -> m HamletRT stringToHamletRT s = parseHamletRT defaultHamletSettings s hamletRTToHtml :: Failure HamletException m => HamletData HledgerWebAppRoute -> HamletRT -> m (Html ()) hamletRTToHtml d h = renderHamletRT h d show instance Failure HamletException (Handler HledgerWebApp) where failure = error . show getAddformRT :: Handler HledgerWebApp RepHtml getAddformRT = do ... dir <- appWebdir `fmap` getYesod addform <- liftIO $ readFile $ dir > "addform.hamlet" -- addform uses the transactionfields template -- tfieldstmpl <- liftIO $ readFile $ dir > "addformtransactionfields.hamlet" -- tfields1 <- stringToHamletRT tfieldstmpl >>= hamletRTToHtml -- (HDMap [("label", HDHtml $ string "To account") -- ,("accthelp", HDHtml $ string "eg: expenses:food") -- ,("amtfield", HDHtml $ preEscapedString $ printf "") -- ...urgh -- tfields1 <- hamletToHamletRT $ transactionfields 1 -- tfields2 <- hamletToHamletRT $ transactionfields 2 -- ...urgh tfields1 <- stringToHamletRT "" tfields2 <- stringToHamletRT "" h <- stringToHamletRT addform >>= hamletRTToHtml (HDMap [("date", HDHtml $ string "today") ,("desc", HDHtml $ string "") ,("datehelp", HDHtml $ string "eg: 7/20, 2010/1/1") ,("deschelp", HDHtml $ string "eg: supermarket (optional)") ,("transactionfields1", HDTemplate tfields1) ,("transactionfields2", HDTemplate tfields2) ]) hamletToRepHtml $ pageLayout td [$hamlet|$h$|]

On 8/5/10 12:57 AM, Michael Snoyman wrote:
Thanks for the input everyone. I've just pushed the new Hamlet runtime code to github[1]. Instead of using data-object, I've created a new datatype: HamletData[2]. Runtime templates support *almost* the entirety of valid Hamlet documents;
Excellent! With the following kludgery I was able to change hamlet files and see the effect immediately. A boon for development. Useful parse errors in the browser too. It's not all smooth of course. Including other hamlets and integrating RT and non-RT hamlets seems a lot of work.
It's true that none of this interoperation will be automatic, but it's
On Fri, Aug 6, 2010 at 3:13 AM, Simon Michael

On Fri, Aug 6, 2010 at 2:06 PM, Michael Snoyman
On Fri, Aug 6, 2010 at 3:13 AM, Simon Michael
wrote: On 8/5/10 12:57 AM, Michael Snoyman wrote:
Thanks for the input everyone. I've just pushed the new Hamlet runtime code to github[1]. Instead of using data-object, I've created a new datatype: HamletData[2]. Runtime templates support *almost* the entirety of valid Hamlet documents;
Excellent! With the following kludgery I was able to change hamlet files and see the effect immediately. A boon for development. Useful parse errors in the browser too. It's not all smooth of course. Including other hamlets and integrating RT and non-RT hamlets seems a lot of work.
It's true that none of this interoperation will be automatic, but it's possible. Regarding: including other hamlets, you just need to parse them first and then include them in your HamletData. Regarding integrating RT and non-RT: I would say your best bet it to do that via Html (). In other words, apply the template to the URL rendering function, resulting in a Html () value, and then embed that in the template you desire.
For the moment, RT templates are definitely second-class citizens, but if people have ideas of how to improve them, I'd be happy to hear it.
Michael
I had a sudden inspiration yesterday of how to improve runtime Hamlet templates. This should give you a lot of the benefits of compile-time templates without requiring a recompile for every change. I've written this up in a blog post[1]. Michael [1] http://www.snoyman.com/blog/entry/typesafe-runtime-hamlet/
participants (4)
-
Alexander Dunlap
-
Kyle Murphy
-
Michael Snoyman
-
Simon Michael