module Text.Mustache.Parser
( parseMustache )
where
import Control.Applicative
import Control.Monad
import Control.Monad.State.Strict
import Data.Char (isSpace, isAlphaNum)
import Data.Maybe (catMaybes)
import Data.Semigroup ((<>))
import Data.Text (Text)
import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
import Text.Mustache.Type
import qualified Data.Text as T
import qualified Text.Megaparsec.Char.Lexer as L
parseMustache
:: FilePath
-> Text
-> Either (ParseError Char Void) [Node]
parseMustache = parse $
evalStateT (pMustache eof) (Delimiters "{{" "}}")
pMustache :: Parser () -> Parser [Node]
pMustache = fmap catMaybes . manyTill (choice alts)
where
alts =
[ Nothing <$ withStandalone pComment
, Just <$> pSection "#" Section
, Just <$> pSection "^" InvertedSection
, Just <$> pStandalone (pPartial Just)
, Just <$> pPartial (const Nothing)
, Nothing <$ withStandalone pSetDelimiters
, Just <$> pUnescapedVariable
, Just <$> pUnescapedSpecial
, Just <$> pEscapedVariable
, Just <$> pTextBlock ]
pTextBlock :: Parser Node
pTextBlock = do
start <- gets openingDel
(void . notFollowedBy . string) start
let terminator = choice
[ (void . lookAhead . string) start
, pBol
, eof ]
TextBlock . T.pack <$> someTill anyChar terminator
pUnescapedVariable :: Parser Node
pUnescapedVariable = UnescapedVar <$> pTag "&"
pUnescapedSpecial :: Parser Node
pUnescapedSpecial = do
start <- gets openingDel
end <- gets closingDel
between (symbol $ start <> "{") (string $ "}" <> end) $
UnescapedVar <$> pKey
pSection :: Text -> (Key -> [Node] -> Node) -> Parser Node
pSection suffix f = do
key <- withStandalone (pTag suffix)
nodes <- (pMustache . withStandalone . pClosingTag) key
return (f key nodes)
pPartial :: (Pos -> Maybe Pos) -> Parser Node
pPartial f = do
pos <- f <$> L.indentLevel
key <- pTag ">"
let pname = PName $ T.intercalate (T.pack ".") (unKey key)
return (Partial pname pos)
pComment :: Parser ()
pComment = void $ do
start <- gets openingDel
end <- gets closingDel
(void . symbol) (start <> "!")
manyTill anyChar (string end)
pSetDelimiters :: Parser ()
pSetDelimiters = void $ do
start <- gets openingDel
end <- gets closingDel
(void . symbol) (start <> "=")
start' <- pDelimiter <* scn
end' <- pDelimiter <* scn
(void . string) ("=" <> end)
put (Delimiters start' end')
pEscapedVariable :: Parser Node
pEscapedVariable = EscapedVar <$> pTag ""
withStandalone :: Parser a -> Parser a
withStandalone p = pStandalone p <|> p
pStandalone :: Parser a -> Parser a
pStandalone p = pBol *> try (between sc (sc <* (void eol <|> eof)) p)
pTag :: Text -> Parser Key
pTag suffix = do
start <- gets openingDel
end <- gets closingDel
between (symbol $ start <> suffix) (string end) pKey
pClosingTag :: Key -> Parser ()
pClosingTag key = do
start <- gets openingDel
end <- gets closingDel
let str = keyToText key
void $ between (symbol $ start <> "/") (string end) (symbol str)
pKey :: Parser Key
pKey = (fmap Key . lexeme . label "key") (implicit <|> other)
where
implicit = [] <$ char '.'
other = sepBy1 (takeWhile1P (Just lbl) f) (char '.')
lbl = "alphanumeric char or '-' or '_'"
f x = isAlphaNum x || x == '-' || x == '_'
pDelimiter :: Parser Text
pDelimiter = takeWhile1P (Just "delimiter char") delChar <?> "delimiter"
where delChar x = not (isSpace x) && x /= '='
pBol :: Parser ()
pBol = do
level <- L.indentLevel
unless (level == pos1) empty
type Parser = StateT Delimiters (Parsec Void Text)
data Delimiters = Delimiters
{ openingDel :: Text
, closingDel :: Text }
scn :: Parser ()
scn = L.space space1 empty empty
sc :: Parser ()
sc = L.space (void $ takeWhile1P Nothing f) empty empty
where
f x = x == ' ' || x == '\t'
lexeme :: Parser a -> Parser a
lexeme = L.lexeme scn
symbol :: Text -> Parser Text
symbol = L.symbol scn
keyToText :: Key -> Text
keyToText (Key []) = "."
keyToText (Key ks) = T.intercalate "." ks