{-# LANGUAGE OverloadedStrings #-} module Text.Templater where import Control.Applicative import Data.Attoparsec.Text import Data.Either (partitionEithers) import Data.List (intercalate) import Data.Monoid ((<>)) import Data.Text (Text) import qualified Data.Text as T import Prelude hiding (takeWhile) data TemplateItem = Literal Text | Variable Text deriving (Show, Eq) type Context = Text -> Maybe Text template :: Text -> Context -> Either String Text template "" _ = Right "" template st ctx = case parseOnly templateP st of Right items -> expandItems ctx items Left e -> Left e expandItems :: Context -> [TemplateItem] -> Either String Text expandItems context items = let (lefts, rights) = partitionEithers $ expandItem context <$> items in if null lefts then Right $ T.concat rights else Left $ intercalate ", " lefts expandItem :: Context -> TemplateItem -> Either String Text expandItem ctx (Literal v) = Right v expandItem ctx (Variable name) = case ctx name of Nothing -> Left $ "key '" <> T.unpack name <> "' not found in context" Just v -> Right v templateP :: Parser [TemplateItem] templateP = many1 templateItemP <* endOfInput templateItemP :: Parser TemplateItem templateItemP = choice [escapedOrVariableP, literalItemP] literalItemP :: Parser TemplateItem literalItemP = Literal <$> takeWhile1 (/= '%') escapedOrVariableP :: Parser TemplateItem escapedOrVariableP = char '%' *> choice [variableItemP, escapedPercentP] where variableItemP = Variable <$> ("{" *> takeWhile (/= '}') <* "}") escapedPercentP = Literal <$> "%"