Haskell implementation of Mustache templates
cabal update
cabal install hastache
Read Mustache documentation for template syntax.
import Text.Hastache
import Text.Hastache.Context
import qualified Data.ByteString.Lazy.Char8 as LZ
main = hastacheStr defaultConfig (encodeStr template) (mkStrContext context)
>>= LZ.putStrLn
template = "Hello, {{name}}!\n\nYou have {{unread}} unread messages."
context "name" = MuVariable "Haskell"
context "unread" = MuVariable (100 :: Int)
Hello, Haskell!
You have 100 unread messages.
With Generics
{-# LANGUAGE DeriveDataTypeable #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.ByteString.Lazy as LZ
import Data.Data
import Data.Generics
main = hastacheStr defaultConfig (encodeStr template) context
>>= LZ.putStrLn
data Info = Info {
name :: String,
unread :: Int
} deriving (Data, Typeable)
template = "Hello, {{name}}!\n\nYou have {{unread}} unread messages."
context = mkGenericContext $ Info "Haskell" 100
template = concat [
"{{#heroes}}\n",
"* {{name}} \n",
"{{/heroes}}\n"]
context "heroes" = MuList $ map (mkStrContext . mkListContext)
["Nameless","Long Sky","Flying Snow","Broken Sword","Qin Shi Huang"]
where
mkListContext name = \"name" -> MuVariable name
* Nameless
* Long Sky
* Flying Snow
* Broken Sword
* Qin Shi Huang
With Generics
data Hero = Hero { name :: String } deriving (Data, Typeable)
data Heroes = Heroes { heroes :: [Hero] } deriving (Data, Typeable)
template = concat [
"{{#heroes}}\n",
"* {{name}} \n",
"{{/heroes}}\n"]
context = mkGenericContext $ Heroes $ map Hero ["Nameless","Long Sky",
"Flying Snow","Broken Sword","Qin Shi Huang"]
Another Generics version
data Heroes = Heroes { heroes :: [String] } deriving (Data, Typeable)
template = concat [
"{{#heroes}}\n",
"* {{.}} \n",
"{{/heroes}}\n"]
context = mkGenericContext $ Heroes ["Nameless","Long Sky","Flying Snow",
"Broken Sword","Qin Shi Huang"]
List item by index
main = mapM_ (\(template,context) ->
hastacheStr defaultConfig (encodeStr template) context >>= LZ.putStrLn)
[(template1, mkStrContext context1),
(template1, context2),
(template3, context3)]
names = ["Nameless","Long Sky","Flying Snow","Broken Sword","Qin Shi Huang"]
template1 = concat [
"{{heroes.1.name}}\n",
"{{heroes.0.name}}\n"]
-- Context as function
context1 "heroes" = MuList $ map (mkStrContext . mkListContext) names
where
mkListContext name = \"name" -> MuVariable name
context1 _ = MuNothing
-- With Generics
data Hero = Hero { name :: String } deriving (Data, Typeable)
data Heroes = Heroes { heroes :: [Hero] } deriving (Data, Typeable)
context2 = mkGenericContext $ Heroes $ map Hero names
-- With Generics (another way)
template3 = concat [
"{{heroName.3}}\n",
"{{heroName.2}}\n"]
data HeroesStr = HeroesStr { heroName :: [String] } deriving (Data, Typeable)
context3 = mkGenericContext $ HeroesStr names
Long Sky
Nameless
Long Sky
Nameless
Broken Sword
Flying Snow
Boolean
template = "{{#boolean}}true{{/boolean}}{{^boolean}}false{{/boolean}}"
context "boolean" = MuBool False
false
List
template = "{{^messages}}No new messages{{/messages}}"
context "messages" = MuList []
No new messages
Number
main = mapM_ (\ctx ->
hastacheStr defaultConfig (encodeStr template) (mkStrContext ctx)
>>= LZ.putStrLn) [context1,context2]
template = "{{#msg}}{{msg}}{{/msg}}{{^msg}}No{{/msg}} new messages."
context1 "msg" = MuVariable (100 :: Int)
context2 "msg" = MuVariable (0 :: Int)
100 new messages.
No new messages.
template = "Hello, {{#reverse}}world{{/reverse}}!"
context "reverse" = MuLambda (reverse . decodeStr)
Hello, dlrow!
{-# LANGUAGE FlexibleContexts #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.ByteString.Lazy.Char8 as LZ
import Control.Monad.State
main = run >>= LZ.putStrLn
run = evalStateT stateFunc ""
stateFunc :: StateT String IO LZ.ByteString
stateFunc =
hastacheStr defaultConfig (encodeStr template) (mkStrContext context)
template = "{{#arg}}aaa{{/arg}} {{#arg}}bbb{{/arg}} {{#arg}}ccc{{/arg}}"
context "arg" = MuLambdaM $ arg . decodeStr
arg :: MonadState String m => String -> m String
arg a = do
v <- get
let nv = v ++ a
put nv
return nv
aaa aaabbb aaabbbccc
data Book = Book {
title :: String,
publicationYear :: Integer
} deriving (Data, Typeable)
data Life = Life {
born :: Integer,
died :: Integer
} deriving (Data, Typeable)
data Writer = Writer {
name :: String,
life :: Life,
books :: [Book]
} deriving (Data, Typeable)
template = concat [
"Name: {{name}} ({{life.born}} - {{life.died}})\n",
"{{#life}}\n",
"Born: {{born}}\n",
"Died: {{died}}\n",
"{{/life}}\n",
"Bibliography:\n",
"{{#books}}\n",
" {{title}} ({{publicationYear}})\n",
"{{/books}}\n"
]
context = mkGenericContext Writer {
name = "Mikhail Bulgakov",
life = Life 1891 1940,
books = [
Book "Heart of a Dog" 1987,
Book "Notes of a country doctor" 1926,
Book "The Master and Margarita" 1967]
}
Name: Mikhail Bulgakov (1891 - 1940)
Born: 1891
Died: 1940
Bibliography:
Heart of a Dog (1987)
Notes of a country doctor (1926)
The Master and Margarita (1967)
- Hastache test
- Real world example: README.md file generator