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 <$> "%"