{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Text.Mustache.Parser
(
parse
, parseWithConf
, MustacheConf (..)
, defaultConf
, Parser
, MustacheState
, sectionBegin
, sectionEnd
, invertedSectionBegin
, unescape2
, unescape1
, delimiterChange
, nestingSeparator
) where
import Control.Monad ( void, when )
import Data.Char ( isAlphaNum, isSpace )
import Data.List ( nub )
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ( (<>) )
#endif
import Data.Text ( Text )
import qualified Data.Text as T
import Text.Mustache.Types ( DataIdentifier (..), Node (..), STree )
import Text.Parsec
( Parsec, ParseError, (<|>), anyChar, char, choice, eof
, getState, lookAhead, many, manyTill, modifyState, noneOf
, optionMaybe, oneOf, parserFail, putState, runParser
, satisfy, skipMany, space, spaces, string, try
)
newtype MustacheConf = MustacheConf
{ MustacheConf -> (String, String)
delimiters :: (String, String)
}
data MustacheState = MustacheState
{ MustacheState -> (String, String)
sDelimiters :: (String, String)
, MustacheState -> Text
textStack :: Text
, MustacheState -> Bool
isBeginngingOfLine :: Bool
, MustacheState -> Maybe DataIdentifier
currentSectionName :: Maybe DataIdentifier
}
data ParseTagRes
= SectionBegin Bool DataIdentifier
| SectionEnd DataIdentifier
| Tag (Node Text)
| HandledTag
sectionBegin :: Char
sectionBegin :: Char
sectionBegin = Char
'#'
sectionEnd :: Char
sectionEnd :: Char
sectionEnd = Char
'/'
partialBegin :: Char
partialBegin :: Char
partialBegin = Char
'>'
invertedSectionBegin :: Char
invertedSectionBegin :: Char
invertedSectionBegin = Char
'^'
unescape2 :: (Char, Char)
unescape2 :: (Char, Char)
unescape2 = (Char
'{', Char
'}')
unescape1 :: Char
unescape1 :: Char
unescape1 = Char
'&'
delimiterChange :: Char
delimiterChange :: Char
delimiterChange = Char
'='
nestingSeparator :: Char
nestingSeparator :: Char
nestingSeparator = Char
'.'
comment :: Char
= Char
'!'
implicitIterator :: Char
implicitIterator :: Char
implicitIterator = Char
'.'
isAllowedDelimiterCharacter :: Char -> Bool
isAllowedDelimiterCharacter :: Char -> Bool
isAllowedDelimiterCharacter =
Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
or ([Bool] -> Bool) -> (Char -> [Bool]) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char -> Bool] -> Char -> [Bool]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
forall (m :: * -> *) a. Monad m => [m a] -> m [a]
sequence
[ Char -> Bool
isSpace, Char -> Bool
isAlphaNum, (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
nestingSeparator) ]
allowedDelimiterCharacter :: Parser Char
allowedDelimiterCharacter :: Parser Char
allowedDelimiterCharacter =
(Char -> Bool) -> Parser Char
forall s (m :: * -> *) u.
Stream s m Char =>
(Char -> Bool) -> ParsecT s u m Char
satisfy Char -> Bool
isAllowedDelimiterCharacter
emptyState :: MustacheState
emptyState :: MustacheState
emptyState = (String, String)
-> Text -> Bool -> Maybe DataIdentifier -> MustacheState
MustacheState (String
"", String
"") Text
forall a. Monoid a => a
mempty Bool
True Maybe DataIdentifier
forall a. Maybe a
Nothing
defaultConf :: MustacheConf
defaultConf :: MustacheConf
defaultConf = (String, String) -> MustacheConf
MustacheConf (String
"{{", String
"}}")
initState :: MustacheConf -> MustacheState
initState :: MustacheConf -> MustacheState
initState (MustacheConf { (String, String)
delimiters :: MustacheConf -> (String, String)
delimiters :: (String, String)
delimiters }) = MustacheState
emptyState { sDelimiters = delimiters }
setIsBeginning :: Bool -> Parser ()
setIsBeginning :: Bool -> Parser ()
setIsBeginning Bool
b = (MustacheState -> MustacheState) -> Parser ()
forall (m :: * -> *) u s. Monad m => (u -> u) -> ParsecT s u m ()
modifyState (\MustacheState
s -> MustacheState
s { isBeginngingOfLine = b })
type Parser = Parsec Text MustacheState
(<<) :: Monad m => m b -> m a -> m b
<< :: forall (m :: * -> *) b a. Monad m => m b -> m a -> m b
(<<) = (m a -> m b -> m b) -> m b -> m a -> m b
forall a b c. (a -> b -> c) -> b -> a -> c
flip m a -> m b -> m b
forall a b. m a -> m b -> m b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
(>>)
endOfLine :: Parser String
endOfLine :: Parser String
endOfLine = do
r <- Parser Char -> ParsecT Text MustacheState Identity (Maybe Char)
forall s (m :: * -> *) t u a.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m (Maybe a)
optionMaybe (Parser Char -> ParsecT Text MustacheState Identity (Maybe Char))
-> Parser Char -> ParsecT Text MustacheState Identity (Maybe Char)
forall a b. (a -> b) -> a -> b
$ Char -> Parser Char
forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
'\r'
n <- char '\n'
pure $ maybe id (:) r [n]
parse :: FilePath -> Text -> Either ParseError STree
parse :: String -> Text -> Either ParseError STree
parse = MustacheConf -> String -> Text -> Either ParseError STree
parseWithConf MustacheConf
defaultConf
parseWithConf :: MustacheConf -> FilePath -> Text -> Either ParseError STree
parseWithConf :: MustacheConf -> String -> Text -> Either ParseError STree
parseWithConf = Parsec Text MustacheState STree
-> MustacheState -> String -> Text -> Either ParseError STree
forall s t u a.
Stream s Identity t =>
Parsec s u a -> u -> String -> s -> Either ParseError a
runParser Parsec Text MustacheState STree
parseText (MustacheState -> String -> Text -> Either ParseError STree)
-> (MustacheConf -> MustacheState)
-> MustacheConf
-> String
-> Text
-> Either ParseError STree
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MustacheConf -> MustacheState
initState
parseText :: Parser STree
parseText :: Parsec Text MustacheState STree
parseText = do
(MustacheState { isBeginngingOfLine }) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
if isBeginngingOfLine
then parseLine
else continueLine
appendStringStack :: String -> Parser ()
appendStringStack :: String -> Parser ()
appendStringStack String
t = (MustacheState -> MustacheState) -> Parser ()
forall (m :: * -> *) u s. Monad m => (u -> u) -> ParsecT s u m ()
modifyState (\MustacheState
s -> MustacheState
s { textStack = textStack s <> T.pack t})
continueLine :: Parser STree
continueLine :: Parsec Text MustacheState STree
continueLine = do
(MustacheState { sDelimiters = ( start@(x:_), _ )}) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
let forbidden = Char
x Char -> String -> String
forall a. a -> [a] -> [a]
: String
"\n\r"
many (noneOf forbidden) >>= appendStringStack
(try endOfLine >>= appendStringStack >> setIsBeginning True >> parseLine)
<|> (try (string start) >> switchOnTag >>= continueFromTag)
<|> (try eof >> finishFile)
<|> (anyChar >>= appendStringStack . (:[]) >> continueLine)
flushText :: Parser STree
flushText :: Parsec Text MustacheState STree
flushText = do
s@(MustacheState { textStack = text }) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
putState $ s { textStack = mempty }
pure [TextBlock text | not (T.null text)]
finishFile :: Parser STree
finishFile :: Parsec Text MustacheState STree
finishFile =
ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState ParsecT Text MustacheState Identity MustacheState
-> (MustacheState -> Parsec Text MustacheState STree)
-> Parsec Text MustacheState STree
forall a b.
ParsecT Text MustacheState Identity a
-> (a -> ParsecT Text MustacheState Identity b)
-> ParsecT Text MustacheState Identity b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
(MustacheState {currentSectionName :: MustacheState -> Maybe DataIdentifier
currentSectionName = Maybe DataIdentifier
Nothing}) -> Parsec Text MustacheState STree
flushText
(MustacheState {currentSectionName :: MustacheState -> Maybe DataIdentifier
currentSectionName = Just DataIdentifier
name}) ->
String -> Parsec Text MustacheState STree
forall s u (m :: * -> *) a. String -> ParsecT s u m a
parserFail (String -> Parsec Text MustacheState STree)
-> String -> Parsec Text MustacheState STree
forall a b. (a -> b) -> a -> b
$ String
"Unclosed section " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> DataIdentifier -> String
forall a. Show a => a -> String
show DataIdentifier
name
parseLine :: Parser STree
parseLine :: Parsec Text MustacheState STree
parseLine = do
(MustacheState { sDelimiters = ( start, _ ) }) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
initialWhitespace <- many (oneOf " \t")
let handleStandalone = do
tag <- ParsecT Text MustacheState Identity ParseTagRes
switchOnTag
let continueNoStandalone = do
String -> Parser ()
appendStringStack String
initialWhitespace
Bool -> Parser ()
setIsBeginning Bool
False
ParseTagRes -> Parsec Text MustacheState STree
continueFromTag ParseTagRes
tag
standaloneEnding = do
Parser () -> Parser ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m a
try (Parser Char -> Parser ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m ()
skipMany (String -> Parser Char
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
oneOf String
" \t") Parser () -> Parser () -> Parser ()
forall a b.
ParsecT Text MustacheState Identity a
-> ParsecT Text MustacheState Identity b
-> ParsecT Text MustacheState Identity b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> (Parser ()
forall s (m :: * -> *) t u.
(Stream s m t, Show t) =>
ParsecT s u m ()
eof Parser () -> Parser () -> Parser ()
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> Parser String -> Parser ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void Parser String
endOfLine))
Bool -> Parser ()
setIsBeginning Bool
True
case tag of
Tag (Partial Maybe Text
_ String
name) ->
( Parser ()
standaloneEnding Parser ()
-> Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
forall a b.
ParsecT Text MustacheState Identity a
-> ParsecT Text MustacheState Identity b
-> ParsecT Text MustacheState Identity b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
ParseTagRes -> Parsec Text MustacheState STree
continueFromTag (Node Text -> ParseTagRes
Tag (Maybe Text -> String -> Node Text
forall α. Maybe α -> String -> Node α
Partial (Text -> Maybe Text
forall a. a -> Maybe a
Just (String -> Text
T.pack String
initialWhitespace)) String
name))
) Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> Parsec Text MustacheState STree
continueNoStandalone
Tag Node Text
_ -> Parsec Text MustacheState STree
continueNoStandalone
ParseTagRes
_ ->
( Parser ()
standaloneEnding Parser ()
-> Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
forall a b.
ParsecT Text MustacheState Identity a
-> ParsecT Text MustacheState Identity b
-> ParsecT Text MustacheState Identity b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
ParseTagRes -> Parsec Text MustacheState STree
continueFromTag ParseTagRes
tag
) Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
-> Parsec Text MustacheState STree
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> Parsec Text MustacheState STree
continueNoStandalone
(try (string start) >> handleStandalone)
<|> (try eof >> appendStringStack initialWhitespace >> finishFile)
<|> (appendStringStack initialWhitespace >> setIsBeginning False >> continueLine)
continueFromTag :: ParseTagRes -> Parser STree
continueFromTag :: ParseTagRes -> Parsec Text MustacheState STree
continueFromTag (SectionBegin Bool
inverted DataIdentifier
name) = do
textNodes <- Parsec Text MustacheState STree
flushText
state@(MustacheState
{ currentSectionName = previousSection }) <- getState
putState $ state { currentSectionName = pure name }
innerSectionContent <- parseText
let sectionTag =
if Bool
inverted
then DataIdentifier -> ASTree α -> Node α
forall α. DataIdentifier -> ASTree α -> Node α
InvertedSection
else DataIdentifier -> ASTree α -> Node α
forall α. DataIdentifier -> ASTree α -> Node α
Section
modifyState $ \MustacheState
s -> MustacheState
s { currentSectionName = previousSection }
outerSectionContent <- parseText
pure (textNodes <> [sectionTag name innerSectionContent] <> outerSectionContent)
continueFromTag (SectionEnd DataIdentifier
name) = do
(MustacheState
{ currentSectionName }) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
case currentSectionName of
Just DataIdentifier
name' | DataIdentifier
name' DataIdentifier -> DataIdentifier -> Bool
forall a. Eq a => a -> a -> Bool
== DataIdentifier
name -> Parsec Text MustacheState STree
flushText
Just DataIdentifier
name' -> String -> Parsec Text MustacheState STree
forall s u (m :: * -> *) a. String -> ParsecT s u m a
parserFail (String -> Parsec Text MustacheState STree)
-> String -> Parsec Text MustacheState STree
forall a b. (a -> b) -> a -> b
$
String
"Expected closing sequence for \""
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> DataIdentifier -> String
forall a. Show a => a -> String
show DataIdentifier
name
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\" got \""
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> DataIdentifier -> String
forall a. Show a => a -> String
show DataIdentifier
name'
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\"."
Maybe DataIdentifier
Nothing -> String -> Parsec Text MustacheState STree
forall s u (m :: * -> *) a. String -> ParsecT s u m a
parserFail (String -> Parsec Text MustacheState STree)
-> String -> Parsec Text MustacheState STree
forall a b. (a -> b) -> a -> b
$
String
"Encountered closing sequence for \""
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> DataIdentifier -> String
forall a. Show a => a -> String
show DataIdentifier
name
String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\" which has never been opened."
continueFromTag (Tag Node Text
tag) = do
textNodes <- Parsec Text MustacheState STree
flushText
furtherNodes <- parseText
pure $ textNodes <> pure tag <> furtherNodes
continueFromTag ParseTagRes
HandledTag = Parsec Text MustacheState STree
parseText
switchOnTag :: Parser ParseTagRes
switchOnTag :: ParsecT Text MustacheState Identity ParseTagRes
switchOnTag = do
(MustacheState { sDelimiters = ( _, end )}) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
choice
[ SectionBegin False <$> (try (char sectionBegin) >> genParseTagEnd mempty)
, SectionEnd
<$> (try (char sectionEnd) >> genParseTagEnd mempty)
, Tag . Variable False
<$> (try (char unescape1) >> genParseTagEnd mempty)
, Tag . Variable False
<$> (try (char (fst unescape2)) >> genParseTagEnd (pure $ snd unescape2))
, Tag . Partial Nothing
<$> ( try (char partialBegin)
>> spaces
>> (noneOf (nub end) `manyTill` try (spaces >> string end))
)
, pure HandledTag
<< (try (char delimiterChange) >> parseDelimChange)
, SectionBegin True
<$> (try (char invertedSectionBegin) >> genParseTagEnd mempty >>= \case
n :: DataIdentifier
n@(NamedData [Text]
_) -> DataIdentifier
-> ParsecT Text MustacheState Identity DataIdentifier
forall a. a -> ParsecT Text MustacheState Identity a
forall (f :: * -> *) a. Applicative f => a -> f a
pure DataIdentifier
n
DataIdentifier
_ -> String -> ParsecT Text MustacheState Identity DataIdentifier
forall s u (m :: * -> *) a. String -> ParsecT s u m a
parserFail String
"Inverted Sections can not be implicit."
)
, pure HandledTag << (try (char comment) >> manyTill anyChar (try $ string end))
, Tag . Variable True
<$> genParseTagEnd mempty
]
where
parseDelimChange :: Parser ()
parseDelimChange = do
(MustacheState { sDelimiters = ( _, end )}) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
spaces
delim1 <- allowedDelimiterCharacter `manyTill` space
spaces
delim2 <- allowedDelimiterCharacter `manyTill` try (spaces >> string (delimiterChange : end))
when (delim1 == mempty || delim2 == mempty)
$ parserFail "Tags must contain more than 0 characters"
oldState <- getState
putState $ oldState { sDelimiters = (delim1, delim2) }
genParseTagEnd :: String -> Parser DataIdentifier
genParseTagEnd :: String -> ParsecT Text MustacheState Identity DataIdentifier
genParseTagEnd String
emod = do
(MustacheState { sDelimiters = ( start, end ) }) <- ParsecT Text MustacheState Identity MustacheState
forall (m :: * -> *) s u. Monad m => ParsecT s u m u
getState
let nEnd = String
emod String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
end
disallowed = String -> String
forall a. Eq a => [a] -> [a]
nub (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Char
nestingSeparator Char -> String -> String
forall a. a -> [a] -> [a]
: String
start String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
end
parseOne :: Parser [Text]
parseOne = do
one <- String -> Parser Char
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m Char
noneOf String
disallowed
Parser Char -> Parser () -> Parser String
forall s (m :: * -> *) t u a end.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a]
`manyTill` Parser () -> Parser ()
forall s (m :: * -> *) t u a.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m a
lookAhead
(Parser () -> Parser ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m a
try (Parser ()
forall s (m :: * -> *) u. Stream s m Char => ParsecT s u m ()
spaces Parser () -> Parser () -> Parser ()
forall a b.
ParsecT Text MustacheState Identity a
-> ParsecT Text MustacheState Identity b
-> ParsecT Text MustacheState Identity b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Parser String -> Parser ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (String -> Parser String
forall s (m :: * -> *) u.
Stream s m Char =>
String -> ParsecT s u m String
string String
nEnd))
Parser () -> Parser () -> Parser ()
forall s u (m :: * -> *) a.
ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
<|> Parser () -> Parser ()
forall s u (m :: * -> *) a. ParsecT s u m a -> ParsecT s u m a
try (Parser Char -> Parser ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (Parser Char -> Parser ()) -> Parser Char -> Parser ()
forall a b. (a -> b) -> a -> b
$ Char -> Parser Char
forall s (m :: * -> *) u.
Stream s m Char =>
Char -> ParsecT s u m Char
char Char
nestingSeparator))
others <- (char nestingSeparator >> parseOne)
<|> (mempty <$ (spaces >> string nEnd))
pure $ T.pack one : others
spaces
(try (char implicitIterator) >> spaces >> string nEnd >> pure Implicit)
<|> (NamedData <$> parseOne)