module Text.Microstache.Parser
( parseMustache )
where
import Control.Applicative hiding (many)
import Control.Monad
import Data.Char (isSpace, isAlphaNum)
import Data.List (intercalate)
import Data.Functor.Identity
import Data.Maybe (catMaybes)
import Data.Text.Lazy (Text)
import Text.Parsec hiding ((<|>))
import Text.Parsec.Char ()
import Data.Word (Word)
import Text.Microstache.Type
import qualified Data.Text as T
parseMustache
:: FilePath
-> Text
-> Either ParseError [Node]
parseMustache = runParser (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 :: String -> (Key -> [Node] -> Node) -> Parser Node
pSection suffix f = do
key <- withStandalone (pTag suffix)
nodes <- (pMustache . withStandalone . pClosingTag) key
return (f key nodes)
pPartial :: (Word -> Maybe Word) -> Parser Node
pPartial f = do
pos <- f <$> 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)
putState (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 :: String -> 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 = keyToString key
void $ between (symbol $ start ++ "/") (string end) (symbol str)
pKey :: Parser Key
pKey = (fmap Key . lexeme . flip label "key") (implicit <|> other)
where
implicit = [] <$ char '.'
other = sepBy1 (T.pack <$> some ch) (char '.')
ch = alphaNumChar <|> oneOf "-_"
pDelimiter :: Parser String
pDelimiter = some (satisfy delChar) <?> "delimiter"
where delChar x = not (isSpace x) && x /= '='
indentLevel :: Parser Word
indentLevel = fmap (fromIntegral . sourceColumn) getPosition
pBol :: Parser ()
pBol = do
level <- indentLevel
unless (level == 1) empty
type Parser = ParsecT Text Delimiters Identity
data Delimiters = Delimiters
{ openingDel :: String
, closingDel :: String }
scn :: Parser ()
scn = spaces
sc :: Parser ()
sc = void (many (oneOf " \t"))
lexeme :: Parser a -> Parser a
lexeme p = p <* spaces
eol :: Parser ()
eol = void (char '\n') <|> void (char '\r' >> char '\n')
string' :: String -> Parser String
string' = try . string
symbol :: String -> Parser String
symbol = lexeme . string'
keyToString :: Key -> String
keyToString (Key []) = "."
keyToString (Key ks) = intercalate "." (T.unpack <$> ks)
someTill :: Stream s m t => ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a]
someTill p end = (:) <$> p <*> manyTill p end
gets :: Monad m => (u -> a) -> ParsecT s u m a
gets f = fmap f getState
alphaNumChar :: Parser Char
alphaNumChar = satisfy isAlphaNum