This library has been deprecated in favour of mustache
: https://hackage.haskell.org/package/mustache
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.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)
Hello, Haskell!
You have 100 unread messages.
With Generics
{-# LANGUAGE DeriveDataTypeable #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.Text.Lazy.IO as TL
import Data.Data
import Data.Generics
main = hastacheStr defaultConfig (encodeStr template) context
>>= TL.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 >>= TL.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)
>>= TL.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.
Multiple constructors (in generic context)
#!/usr/local/bin/runhaskell
-- | Multiple constructors in generic contexts
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Data
import Data.Monoid
import Data.Typeable ()
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import Text.Hastache
import Text.Hastache.Context
data Hero = SuperHero { name :: String
, powers :: [String]
, companion :: String
}
| EvilHero { name :: String
, minion :: String
}
deriving (Show, Data, Typeable)
template :: String
template = mconcat [
"{{#SuperHero}}\n",
"Hero: {{name}}\n",
" * Powers: {{#powers}}\n",
"\n - {{.}}{{/powers}} \n",
" * Companion: {{companion}}\n",
"{{/SuperHero}}\n",
"{{#EvilHero}}\n",
"Evil hero: {{name}}\n",
" * Minion: {{minion}}\n",
"{{/EvilHero}}"]
render :: Hero -> IO TL.Text
render = hastacheStr defaultConfig (encodeStr template)
. mkGenericContext
main :: IO ()
main = do let batman = SuperHero "Batman" ["ht","ht"] "Robin"
let doctorEvil = EvilHero "Doctor Evil" "Mini-Me"
render batman >>= TL.putStrLn
render doctorEvil >>= TL.putStrLn
Hero: Batman
* Powers:
- ht
- ht
* Companion: Robin
Evil hero: Doctor Evil
* Minion: Mini-Me
template = "Hello, {{#reverse}}world{{/reverse}}!"
context "reverse" = MuLambda (reverse . decodeStr)
Hello, dlrow!
{-# LANGUAGE FlexibleContexts #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import Control.Monad.State
main = run >>= TL.putStrLn
run = evalStateT stateFunc ""
stateFunc :: StateT String IO TL.Text
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
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeSynonymInstances #-}
-- 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
An int: 1
A decimal number: 1.500
An int: 2
An infinity: +inf
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
- examples/ directory