mole-0.0.5: src/Data/Mole/Builder/Internal/Template.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.Mole.Builder.Internal.Template where
import Control.Applicative
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Attoparsec.Text as AP
import Data.Maybe
import Data.Monoid
import Data.Mole.Types
type Bracket = (Text,Text)
type Brackets = [Bracket]
type Context = Bracket -> Text -> Either Error Text
data Fragment = Lit !Text | Var !Bracket !Text deriving (Show, Eq)
newtype Template = Template [Fragment] deriving (Show, Eq)
template :: Brackets -> Text -> Template
template brackets input = case AP.parseOnly (fragmentParser brackets) input of
Left x -> error $ x ++ " on input '" ++ T.unpack input ++ "'"
Right x -> Template $ mergeLiterals x
mergeLiterals :: [Fragment] -> [Fragment]
mergeLiterals = reverse . foldl f []
where
f acc (Lit "") = acc
f ((Lit a):xs) (Lit b) = (Lit $ a <> b) : xs
f acc frag = frag:acc
fragmentParser :: Brackets -> AP.Parser [Fragment]
fragmentParser brackets = go
where
go = lit <|> rest
-- The rest of the input as a single 'Lit'. The contents of the text may be
-- empty, but we drop empty literals during postprocessing.
rest = pure . Lit <$> AP.takeText
-- Matches 'Text' which marks the beginning of a 'Var'.
varMarkers = foldl1 (<|>) $ map (AP.string . fst) brackets
-- The contents of a variable and its end marker. The start marker must be
-- consumed before. This function recurses into 'go' to continue parsing
-- the rest of the input.
var (a,b) = do
text <- T.strip . T.pack <$> AP.manyTill AP.anyChar (AP.string b)
(Var (a, b) text :) <$> go
-- A literal (may be empty), up until the start marker of a var, followed
-- by a var.
lit = do
(text, a) <- stringTill varMarkers
let b = fromJust $ lookup a brackets
(Lit (T.pack text) :) <$> var (a, b)
-- The string until 'end' matches. What matched at the end is returned, to
-- give the caller a chance to go different paths depending on how the
-- string ended.
stringTill end = scan
where scan = ((\x -> ([],x)) <$> end) <|> do
x <- AP.anyChar
(xs, e) <- scan
return $ (x:xs, e)
render :: Template -> Context -> Either Error Text
render (Template frags) ctxFunc =
mconcat <$> traverse renderFrag frags
where
renderFrag (Lit s) = pure s
renderFrag (Var bracket x) = ctxFunc bracket x