This library has been deprecated in favour of mustache: https://hackage.haskell.org/package/mustache
Haskell implementation of Mustache templates
Installation
cabal update
cabal install hastache
Usage
Read Mustache documentation for template syntax.
Examples
Variables
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" 100Lists
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
Conditional evaluation
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
Functions
template = "Hello, {{#reverse}}world{{/reverse}}!" context "reverse" = MuLambda (reverse . decodeStr)
Hello, dlrow!
Monadic functions
{-# 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 nvaaa aaabbb aaabbbccc
Custom queries and field renaming
{-# 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.putStrLnAn int: 1
A decimal number: 1.500
An int: 2
An infinity: +inf
Generics big example
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)
More examples
- Hastache test
- Real world example: README.md file generator
- examples/ directory