Announcing: hastache 0.6.1
Happy holidays, everyone!
I would like to announce a new version of the Hastache library, version 0.6.1. Some interesting and useful changes, as well as improvements and bugfixes are included in the release. See below for an extended changelog.
Hastache is a Haskell implementation of the mustache templating system.
Quick start
cabal update
cabal install hastache
A simple example:
import Text.Hastache
import Text.Hastache.Context
import qualified Data.Text.Lazy.IO as TL
main = hastacheStr defaultConfig (encodeStr template) (mkStrContext context)
>>= TL.putStrLn
template = "Hello, {{name}}!\n\nYou have {{unread}} unread messages."
context "name" = MuVariable "Haskell"
context "unread" = MuVariable (100 :: Int)
Read Mustache documentation for template syntax; consult README for more details.
Whats’s new in 0.6.1?
Most of the new features in this release deal with generic contexts.
Context merging
composeCtx is a left-leaning composition of contexts. Given contexts c1 and c2, the behaviour of (c1 <> c2) x is following: if c1 x produces ‘MuNothing’, then the result is c2 x. Otherwise the result is c1 x. Even if c1 x is ‘MuNothing’, the monadic effects of c1 are still to take place.
Generic contexts for more datatypes
The mkGenericContext function now supports additional datatypes like Maybe (with Nothing being an empty/null value) and Either.
Context modifiers and renaming
The new mkGenericContext' is a generalized version of mkGenericContext and it takes two addition arguments. The first one, of type (String -> String) is simply a renaming function, similar to fieldLabelModifier of aeson. To see this feature in action, consider the following example:
{-# LANGUAGE DeriveDataTypeable #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import Data.Data (Data, Typeable)
import Data.Decimal
import Data.Generics.Aliases (extQ)
data Test = Test {f :: Int}
deriving (Data, Typeable)
val1 :: Test
val1 = Test 1
val2 :: Test
val2 = Test 2
r "f" = "foo"
r x = x
example :: Test -> IO TL.Text
example v = hastacheStr defaultConfig
(encodeStr template)
(mkGenericContext' r defaultExt v)
template = "An integer: {{foo}}"
main = do
example val1 >>= TL.putStrLn
example val2 >>= TL.putStrLn
In the example we use the renaming function r to rename a field “f” to “foo”.
The second additional argument is a query extension, of type Ext:
type Ext = forall b. (Data b, Typeable b) => b -> String
A query extension is a way of turning arbitrary datatypes into strings. This might come in very handy, if you want to generate mustache contexts from records/datatypes that contain non-primitive datatypes (from non-base modules) that you want to display. Before 0.6.1, if you had a record that contained, for example, a Decimal field, and you wanted to convert it to a context and access that field, you were simply out of luck. With this release you can basically extend the mkGenericContext' function to support any datatypes you want! Once again, I believe an example is worth a thousand words, so let us consider a slightly modified version of the example above:
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
-- Custom extension function for types that are not supported out of
-- the box in generic contexts
import Text.Hastache
import Text.Hastache.Context
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import Data.Data (Data, Typeable)
import Data.Decimal
import Data.Generics.Aliases (extQ)
data DecimalOrInf = Inf | Dec Decimal deriving (Data, Typeable)
deriving instance Data Decimal
data Test = Test {n::Int, m::DecimalOrInf} deriving (Data, Typeable)
val1 :: Test
val1 = Test 1 (Dec $ Decimal 3 1500)
val2 :: Test
val2 = Test 2 Inf
query :: Ext
query = defaultExt `extQ` f
where f Inf = "+inf"
f (Dec i) = show i
r "m" = "moo"
r x = x
example :: Test -> IO TL.Text
example v = hastacheStr defaultConfig
(encodeStr template)
(mkGenericContext' r query v)
template = concat [
"An int: {{n}}\n",
"{{#moo.Dec}}A decimal number: {{moo.Dec}}{{/moo.Dec}}",
"{{#moo.Inf}}An infinity: {{moo.Inf}}{{/moo.Inf}}"
]
main = do
example val1 >>= TL.putStrLn
example val2 >>= TL.putStrLn
As you can see, the query extensions are combined using the extQ function from Data.Generics, and the “unit” of this whole thing is defaultExt function.
Links
Acknowledgments
This release would not have been possible without Tobias Florek, Edsko de Vries, Janne Hellsten, @clinty on Github, Stefan Kersten, Herbert Valerio Riedel, and other people who were submitting issues, patches, and requests.