packages feed

ginger 0.3.9.1 → 0.5.0.0

raw patch · 8 files changed

+820/−107 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Text.Ginger.AST: DoE :: Statement -> Expression
+ Text.Ginger.AST: ExpressionS :: Expression -> Statement
+ Text.Ginger.AST: IndentS :: Expression -> Statement -> Statement
- Text.Ginger.Run: easyRenderM :: (Monad m, ContextEncodable h, Monoid h, ToGVal (Run m h) v, ToGVal (Run m h) h) => (h -> m ()) -> v -> Template -> m ()
+ Text.Ginger.Run: easyRenderM :: (Monad m, ContextEncodable h, Monoid h, ToGVal (Run m h) v, ToGVal (Run m h) h) => (h -> m ()) -> v -> Template -> m (GVal (Run m h))
- Text.Ginger.Run: makeContext' :: Monoid h => (VarName -> GVal (Run (Writer h) h)) -> (GVal (Run (Writer h) h) -> h) -> GingerContext (Writer h) h
+ Text.Ginger.Run: makeContext' :: Monoid h => (VarName -> GVal (Run (Writer h) h)) -> (GVal (Run (Writer h) h) -> h) -> Maybe (Newlines h) -> GingerContext (Writer h) h
- Text.Ginger.Run: makeContextM' :: (Monad m, Functor m) => (VarName -> Run m h (GVal (Run m h))) -> (h -> m ()) -> (GVal (Run m h) -> h) -> GingerContext m h
+ Text.Ginger.Run: makeContextM' :: (Monad m, Functor m) => (VarName -> Run m h (GVal (Run m h))) -> (h -> m ()) -> (GVal (Run m h) -> h) -> Maybe (Newlines h) -> GingerContext m h
- Text.Ginger.Run: runGingerT :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m) => GingerContext m h -> Template -> m ()
+ Text.Ginger.Run: runGingerT :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m) => GingerContext m h -> Template -> m (GVal (Run m h))

Files

cli/GingerCLI.hs view
@@ -85,7 +85,7 @@             displayParserError tplSource err         Right t -> do             let context = makeContextHtmlM contextLookup (putStr . Text.unpack . htmlSource)-            runGingerT context t+            runGingerT context t >>= hPutStrLn stderr . show  printParserError :: ParserError -> IO () printParserError = putStrLn . formatParserError
ginger.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                ginger-version:             0.3.9.1+version:             0.5.0.0 synopsis:            An implementation of the Jinja2 template language in Haskell description:         Ginger is Jinja, minus the most blatant pythonisms. Wants                      to be feature complete, but isn't quite there yet.
src/Text/Ginger/AST.hs view
@@ -36,8 +36,10 @@ data Statement     = MultiS [Statement] -- ^ A sequence of multiple statements     | ScopedS Statement -- ^ Run wrapped statement in a local scope+    | IndentS Expression Statement -- ^ Establish an indented context around the wrapped statement     | LiteralS Html -- ^ Literal output (anything outside of any tag)     | InterpolationS Expression -- ^ {{ expression }}+    | ExpressionS Expression -- ^ Evaluate expression     | IfS Expression Statement Statement -- ^ {% if expression %}statement{% else %}statement{% endif %}     | SwitchS Expression [(Expression, Statement)] Statement -- ^ {% switch expression %}{% case expression %}statement{% endcase %}...{% default %}statement{% enddefault %}{% endswitch %}     | ForS (Maybe VarName) VarName Expression Statement -- ^ {% for index, varname in expression %}statement{% endfor %}@@ -61,4 +63,5 @@     | CallE Expression [(Maybe Text, Expression)] -- ^ foo(bar=baz, quux)     | LambdaE [Text] Expression -- ^ (foo, bar) -> expr     | TernaryE Expression Expression Expression -- ^ expr ? expr : expr+    | DoE Statement -- ^ do { statement; }     deriving (Show)
src/Text/Ginger/Optimizer.hs view
@@ -268,7 +268,7 @@ runCT = runGinger ctContext  ctContext :: GingerContext (Writer Collected) Collected-ctContext = makeContext' ctLookup ctEncode+ctContext = makeContext' ctLookup ctEncode Nothing  ctLookup :: VarName -> GVal m ctLookup = const def
src/Text/Ginger/Parse.hs view
@@ -20,11 +20,11 @@                    , runParserT                    , try, lookAhead                    , manyTill, oneOf, string, notFollowedBy, between, sepBy-                   , eof, spaces, anyChar, char+                   , eof, spaces, anyChar, noneOf, char                    , option, optionMaybe                    , unexpected                    , digit-                   , getState, modifyState+                   , getState, modifyState, putState                    , (<?>)                    ) import Text.Parsec.Error ( errorMessages@@ -34,6 +34,7 @@ import Text.Ginger.AST import Text.Ginger.Html ( unsafeRawHtml ) +import Control.Monad (when) import Control.Monad.Reader ( ReaderT                             , runReaderT                             , ask, asks@@ -53,6 +54,7 @@ import qualified Data.HashMap.Strict as HashMap import Data.Default ( Default (..) ) import Data.Monoid ( (<>) )+import Data.Char (isSpace)  import System.FilePath ( takeDirectory, (</>) ) @@ -124,12 +126,14 @@ data ParseState     = ParseState         { psBlocks :: HashMap VarName Block+        , psStripIndent :: String         }  defParseState :: ParseState defParseState =     ParseState         { psBlocks = HashMap.empty+        , psStripIndent = ""         }  -- | Parse Ginger source from memory.@@ -186,13 +190,29 @@     blocks <- psBlocks <$> getState     return Template { templateBody = body, templateParent = Nothing, templateBlocks = blocks } +isNullS NullS = True+isNullS _ = False+ statementsP :: Monad m => Parser m Statement statementsP =     reduceStatements . filter (not . isNullS) <$> many (try statementP)-    where-        isNullS NullS = True-        isNullS _ = False +scriptStatementsP :: Monad m => Parser m Statement+scriptStatementsP = do+    spacesOrComment+    reduceStatements . filter (not . isNullS) <$>+        many (try scriptStatementP)+++scriptStatementBlockP :: Monad m => Parser m Statement+scriptStatementBlockP = do+    char '{'+    spacesOrComment+    inner <- scriptStatementsP+    char '}'+    spacesOrComment+    return inner+ statementP :: Monad m => Parser m Statement statementP = interpolationStmtP            <|> commentStmtP@@ -205,25 +225,63 @@            <|> blockStmtP            <|> callStmtP            <|> scopeStmtP+           <|> indentStmtP+           <|> scriptStmtP            <|> literalStmtP +scriptStatementP :: Monad m => Parser m Statement+scriptStatementP = scriptStatementBlockP+                 <|> scriptEchoStmtP+                 <|> scriptIfStmtP+                 <|> scriptSwitchStmtP+                 <|> scriptSetStmtP+                 <|> scriptForStmtP+                 <|> scriptIncludeP+                 <|> scriptMacroStmtP+                 <|> scriptScopeStmtP+                 <|> scriptExprStmtP+ interpolationStmtP :: Monad m => Parser m Statement interpolationStmtP = do     try openInterpolationP-    spaces+    spacesOrComment     expr <- expressionP-    spaces+    spacesOrComment     closeInterpolationP     return $ InterpolationS expr +scriptEchoStmtP :: Monad m => Parser m Statement+scriptEchoStmtP = do+    try $ keyword "echo"+    spacesOrComment+    char '('+    expr <- expressionP+    spacesOrComment+    char ')'+    spacesOrComment+    char ';'+    spacesOrComment+    return $ InterpolationS expr+ literalStmtP :: Monad m => Parser m Statement literalStmtP = do-    txt <- manyTill anyChar endOfLiteralP+    txt <- manyTill literalCharP endOfLiteralP      case txt of         [] -> unexpected "{{"         _ -> return . LiteralS . unsafeRawHtml . Text.pack $ txt +literalCharP :: Monad m => Parser m Char+literalCharP =+    literalNewlineP <|> anyChar++literalNewlineP :: Monad m => Parser m Char+literalNewlineP = do+    stripStr <- psStripIndent <$> getState+    char '\n'+    when (not $ null stripStr) (ignore . optional . try $ string stripStr)+    return '\n'+ endOfLiteralP :: Monad m => Parser m () endOfLiteralP =     (ignore . lookAhead . try $ openInterpolationP) <|>@@ -234,9 +292,41 @@ commentStmtP :: Monad m => Parser m Statement commentStmtP = do     try openCommentP-    manyTill anyChar (try closeCommentP)+    manyTill+        (   (noneOf "#" *> return ())+        <|> (try $ char '#' *> notFollowedBy (char '}'))+        )+        (try closeCommentP)     return NullS +scriptCommentP :: Monad m => Parser m ()+scriptCommentP = do+    try $ char '#' *> notFollowedBy (char '}')+    manyTill anyChar endl+    spacesOrComment++spacesOrComment :: Monad m => Parser m ()+spacesOrComment = do+    many $ scriptCommentP <|> (oneOf " \t\r\n" *> return ())+    return ()++scriptExprStmtP :: Monad m => Parser m Statement+scriptExprStmtP = do+    expr <- try $ expressionP+    char ';'+    spacesOrComment+    return $ ExpressionS expr++endl :: Monad m => Parser m Char+endl = char '\n' <|> (char '\r' >> char '\n')++scriptStmtP :: Monad m => Parser m Statement+scriptStmtP =+    between+        (try $ simpleTagP "script")+        (simpleTagP "endscript")+        scriptStatementsP+ ifStmtP :: Monad m => Parser m Statement ifStmtP = do     condExpr <- fancyTagP "if" expressionP@@ -258,6 +348,41 @@     -- No endif here: the parent {% if %} owns that one.     return $ IfS condExpr trueStmt falseStmt +scriptIfStmtP :: Monad m => Parser m Statement+scriptIfStmtP = do+    try $ keyword "if"+    spacesOrComment+    char '('+    condExpr <- expressionP+    spacesOrComment+    char ')'+    spacesOrComment+    trueStmt <- scriptStatementP+    spacesOrComment+    falseStmt <- scriptElifP <|> scriptElseP <|> return NullS+    return $ IfS condExpr trueStmt falseStmt++scriptElseP :: Monad m => Parser m Statement+scriptElseP = do+    try $ keyword "else"+    spacesOrComment+    scriptStatementP++scriptElifP :: Monad m => Parser m Statement+scriptElifP = do+    try $ keyword "elif"+    spacesOrComment+    char '('+    spacesOrComment+    condExpr <- expressionP+    spacesOrComment+    char ')'+    spacesOrComment+    trueStmt <- scriptStatementP+    spacesOrComment+    falseStmt <- scriptElifP <|> scriptElseP <|> return NullS+    return $ IfS condExpr trueStmt falseStmt+ switchStmtP :: Monad m => Parser m Statement switchStmtP = do     pivotExpr <- try $ fancyTagP "switch" expressionP@@ -277,19 +402,74 @@ switchDefaultP = do     try (simpleTagP "default") *> statementsP <* simpleTagP "enddefault" +scriptSwitchStmtP :: Monad m => Parser m Statement+scriptSwitchStmtP = do+    try $ keyword "switch"+    spacesOrComment+    char '('+    spacesOrComment+    pivotExpr <- expressionP+    spacesOrComment+    char ')'+    spacesOrComment+    char '{'+    spacesOrComment+    cases <- many scriptSwitchCaseP+    def <- option NullS $ scriptSwitchDefaultP+    spacesOrComment+    char '}'+    spacesOrComment+    return $ SwitchS pivotExpr cases def++scriptSwitchCaseP :: Monad m => Parser m (Expression, Statement)+scriptSwitchCaseP = do+    try $ keyword "case"+    spacesOrComment+    cmpExpr <- expressionP+    spacesOrComment+    char ':'+    spacesOrComment+    body <- scriptStatementP+    spacesOrComment+    return (cmpExpr, body)++scriptSwitchDefaultP :: Monad m => Parser m Statement+scriptSwitchDefaultP = do+    try $ keyword "default"+    spacesOrComment+    char ':'+    spacesOrComment+    body <- scriptStatementP+    spacesOrComment+    return body+ setStmtP :: Monad m => Parser m Statement setStmtP = fancyTagP "set" setStmtInnerP  setStmtInnerP :: Monad m => Parser m Statement setStmtInnerP = do     name <- identifierP-    spaces+    spacesOrComment     char '='-    spaces+    spacesOrComment     val <- expressionP-    spaces+    spacesOrComment     return $ SetVarS name val +scriptSetStmtP :: Monad m => Parser m Statement+scriptSetStmtP = do+    try $ keyword "set"+    spacesOrComment+    name <- identifierP+    spacesOrComment+    char '='+    spacesOrComment+    val <- expressionP+    spacesOrComment+    char ';'+    spacesOrComment+    return $ SetVarS name val+ defineBlock :: VarName -> Block -> ParseState -> ParseState defineBlock name block s =     s { psBlocks = HashMap.insert name block (psBlocks s) }@@ -304,22 +484,34 @@ blockP = do     name <- fancyTagP "block" identifierP     body <- statementsP-    fancyTagP "endblock" (optional $ string (Text.unpack name) >> spaces)+    fancyTagP "endblock" (optional $ string (Text.unpack name) >> spacesOrComment)     return (name, Block body)  macroStmtP :: Monad m => Parser m Statement macroStmtP = do     (name, args) <- try $ fancyTagP "macro" macroHeadP     body <- statementsP-    fancyTagP "endmacro" (optional $ string (Text.unpack name) >> spaces)+    fancyTagP "endmacro" (optional $ string (Text.unpack name) >> spacesOrComment)     return $ DefMacroS name (Macro args body) +scriptMacroStmtP :: Monad m => Parser m Statement+scriptMacroStmtP = do+    try $ keyword "macro"+    spacesOrComment+    name <- identifierP+    spacesOrComment+    args <- option [] $ groupP "(" ")" identifierP+    spacesOrComment+    body <- scriptStatementP+    spacesOrComment+    return $ DefMacroS name (Macro args body)+ macroHeadP :: Monad m => Parser m (VarName, [VarName]) macroHeadP = do     name <- identifierP-    spaces+    spacesOrComment     args <- option [] $ groupP "(" ")" identifierP-    spaces+    spacesOrComment     return (name, args)  -- {% call (foo) bar(baz) %}quux{% endcall %}@@ -345,19 +537,41 @@ callHeadP :: Monad m => Parser m ([Text], Expression) callHeadP = do     callerArgs <- option [] $ groupP "(" ")" identifierP-    spaces+    spacesOrComment     call <- expressionP-    spaces+    spacesOrComment     return (callerArgs, call)  scopeStmtP :: Monad m => Parser m Statement scopeStmtP =     ScopedS <$>         between-            (simpleTagP "scope")+            (try $ simpleTagP "scope")             (simpleTagP "endscope")             statementsP +indentStmtP :: Monad m => Parser m Statement+indentStmtP = do+    indentExpr <- try $ fancyTagP "indent" indentHeadP+    preIndent <- many (oneOf " \t")+    oldState <- getState+    modifyState $ \state ->+        state { psStripIndent = preIndent }+    body <- statementsP+    putState oldState+    simpleTagP "endindent"+    return $ IndentS indentExpr body++indentHeadP :: Monad m => Parser m Expression+indentHeadP =+    (expressionP <|> return (StringLiteralE "  ")) <* spacesOrComment++scriptScopeStmtP :: Monad m => Parser m Statement+scriptScopeStmtP = do+    try $ keyword "scope"+    spacesOrComment+    ScopedS <$> scriptStatementP+ forStmtP :: Monad m => Parser m Statement forStmtP = do     (iteree, varNameVal, varNameIndex) <- fancyTagP "for" forHeadP@@ -372,14 +586,47 @@         (IfS iteree forLoop)         elseBranchMay +scriptForStmtP :: Monad m => Parser m Statement+scriptForStmtP = do+    try $ keyword "for"+    spacesOrComment+    char '('+    (iteree, varNameVal, varNameIndex) <- forHeadP+    spacesOrComment+    char ')'+    spacesOrComment+    body <- scriptStatementP+    elseBranchMay <- optionMaybe $ do+        try $ keyword "else"+        spacesOrComment+        scriptStatementP+    let forLoop = ForS varNameIndex varNameVal iteree body+    return $ maybe+        forLoop+        (IfS iteree forLoop)+        elseBranchMay+ includeP :: Monad m => Parser m Statement includeP = do     sourceName <- fancyTagP "include" stringLiteralP     include sourceName +scriptIncludeP :: Monad m => Parser m Statement+scriptIncludeP = do+    try $ keyword "include"+    spacesOrComment+    char '('+    sourceName <- stringLiteralP+    spacesOrComment+    char ')'+    spacesOrComment+    char ';'+    spacesOrComment+    include sourceName+ forHeadP :: Monad m => Parser m (Expression, VarName, Maybe VarName) forHeadP =-    (try forHeadInP <|> forHeadAsP) <* optional (string "recursive" >> spaces)+    (try forHeadInP <|> forHeadAsP) <* optional (keyword "recursive" >>spacesOrComment)  forIteratorP :: Monad m => Parser m (VarName, Maybe VarName) forIteratorP = try forIndexedIteratorP <|> try forSimpleIteratorP <?> "iteration variables"@@ -387,36 +634,34 @@ forIndexedIteratorP :: Monad m => Parser m (VarName, Maybe VarName) forIndexedIteratorP = do     indexIdent <- identifierP-    spaces+    spacesOrComment     char ','-    spaces+    spacesOrComment     varIdent <- identifierP-    spaces+    spacesOrComment     return (varIdent, Just indexIdent)  forSimpleIteratorP :: Monad m => Parser m (VarName, Maybe VarName) forSimpleIteratorP = do     varIdent <- identifierP-    spaces+    spacesOrComment     return (varIdent, Nothing)  forHeadInP :: Monad m => Parser m (Expression, VarName, Maybe VarName) forHeadInP = do     (varIdent, indexIdent) <- forIteratorP-    spaces-    string "in"-    notFollowedBy identCharP-    spaces+    spacesOrComment+    keyword "in"+    spacesOrComment     iteree <- expressionP     return (iteree, varIdent, indexIdent)  forHeadAsP :: Monad m => Parser m (Expression, VarName, Maybe VarName) forHeadAsP = do     iteree <- expressionP-    spaces-    string "as"-    notFollowedBy identCharP-    spaces+    spacesOrComment+    keyword "as"+    spacesOrComment     (varIdent, indexIdent) <- forIteratorP     return (iteree, varIdent, indexIdent) @@ -425,8 +670,8 @@     between         (try $ do             openTagP-            string tagName-            spaces)+            keyword tagName+            spacesOrComment)         closeTagP  simpleTagP :: Monad m => String -> Parser m ()@@ -457,27 +702,27 @@ openWP c = ignore $ do     spaces     string [ '{', c, '-' ]-    spaces+    spacesOrComment  openNWP :: Monad m => Char -> Parser m () openNWP c = ignore $ do     string [ '{', c ]-    spaces+    spacesOrComment  closeP :: Monad m => Char -> Parser m () closeP c = try (closeWP c) <|> try (closeNWP c)  closeWP :: Monad m => Char -> Parser m () closeWP c = ignore $ do-    spaces+    spacesOrComment     string [ '-', c, '}' ]     spaces  closeNWP :: Monad m => Char -> Parser m () closeNWP c = ignore $ do-    spaces+    spacesOrComment     string [ c, '}' ]-    optional . ignore . char $ '\n'+    optional . ignore $ literalNewlineP  expressionP :: Monad m => Parser m Expression expressionP = lambdaExprP <|> ternaryExprP@@ -486,12 +731,12 @@ lambdaExprP = do     argNames <- try $ do         char '('-        spaces-        argNames <- sepBy (spaces >> identifierP) (try $ spaces >> char ',')+        spacesOrComment+        argNames <- sepBy (spacesOrComment>> identifierP) (try $ spacesOrComment>> char ',')         char ')'-        spaces+        spacesOrComment         string "->"-        spaces+        spacesOrComment         return argNames     body <- expressionP     return $ LambdaE argNames body@@ -499,7 +744,7 @@ operativeExprP :: forall m. Monad m => Parser m Expression -> [ (String, Text) ] -> Parser m Expression operativeExprP operandP operators = do     lhs <- operandP-    spaces+    spacesOrComment     tails <- many . try $ operativeTail     return $ foldl (flip ($)) lhs tails     where@@ -510,36 +755,34 @@             funcName <-                 foldl (<|>) (fail "operator")                     [ try (string op >> notFollowedBy (oneOf opChars)) >> return fn | (op, fn) <- operators ]-            spaces+            spacesOrComment             rhs <- operandP-            spaces+            spacesOrComment             return (\lhs -> CallE (VarE funcName) [(Nothing, lhs), (Nothing, rhs)])  ternaryExprP :: Monad m => Parser m Expression ternaryExprP = do     expr1 <- booleanExprP-    spaces+    spacesOrComment     cTernaryTailP expr1 <|> pyTernaryTailP expr1 <|> return expr1  cTernaryTailP :: Monad m => Expression -> Parser m Expression cTernaryTailP condition = do     char '?'-    spaces+    spacesOrComment     yesBranch <- expressionP     char ':'-    spaces+    spacesOrComment     noBranch <- expressionP     return $ TernaryE condition yesBranch noBranch  pyTernaryTailP :: Monad m => Expression -> Parser m Expression pyTernaryTailP yesBranch = do-    string "if"-    notFollowedBy identCharP-    spaces+    keyword "if"+    spacesOrComment     condition <- booleanExprP-    string "else"-    notFollowedBy identCharP-    spaces+    keyword "else"+    spacesOrComment     noBranch <- expressionP     return $ TernaryE condition yesBranch noBranch @@ -585,8 +828,8 @@ postfixExprP :: Monad m => Parser m Expression postfixExprP = do     base <- atomicExprP-    spaces-    postfixes <- many . try $ postfixP `before` spaces+    spacesOrComment+    postfixes <- many . try $ postfixP `before`spacesOrComment     return $ foldl (flip ($)) base postfixes  postfixP :: Monad m => Parser m (Expression -> Expression)@@ -598,7 +841,7 @@ dotPostfixP :: Monad m => Parser m (Expression -> Expression) dotPostfixP = do     char '.'-    spaces+    spacesOrComment     i <- StringLiteralE <$> identifierP     return $ \e -> MemberLookupE e i @@ -632,7 +875,7 @@  namedFuncArgP :: Monad m => Parser m (Maybe Text, Expression) namedFuncArgP = do-    name <- try $ identifierP `before` between spaces spaces (string "=")+    name <- try $ identifierP `before` between spacesOrComment spacesOrComment (string "=")     expr <- expressionP     return (Just name, expr) @@ -642,13 +885,14 @@ filterP :: Monad m => Parser m (Expression -> Expression) filterP = do     char '|'-    spaces+    spacesOrComment     func <- atomicExprP     args <- option [] $ groupP "(" ")" funcArgP     return $ \e -> CallE func ((Nothing, e):args)  atomicExprP :: Monad m => Parser m Expression-atomicExprP = parenthesizedExprP+atomicExprP = doExprP+            <|> parenthesizedExprP             <|> objectExprP             <|> listExprP             <|> stringLiteralExprP@@ -658,10 +902,18 @@ parenthesizedExprP :: Monad m => Parser m Expression parenthesizedExprP =     between-        (try . ignore $ char '(' >> spaces)-        (ignore $ char ')' >> spaces)+        (try . ignore $ char '(' >> spacesOrComment)+        (ignore $ char ')' >> spacesOrComment)         expressionP +doExprP :: Monad m => Parser m Expression+doExprP = do+    try $ keyword "do"+    spacesOrComment+    stmt <- scriptStatementP+    spacesOrComment+    return $ DoE stmt+ listExprP :: Monad m => Parser m Expression listExprP = ListE <$> groupP "[" "]" expressionP @@ -671,28 +923,28 @@ expressionPairP :: Monad m => Parser m (Expression, Expression) expressionPairP = do     a <- expressionP-    spaces+    spacesOrComment     char ':'-    spaces+    spacesOrComment     b <- expressionP-    spaces+    spacesOrComment     return (a, b)  groupP :: Monad m => String -> String -> Parser m a -> Parser m [a] groupP obr cbr inner =     bracedP obr cbr-        (sepBy (inner `before` spaces) (try $ string "," `before` spaces))+        (sepBy (inner `before` spacesOrComment) (try $ string "," `before` spacesOrComment))  bracedP :: Monad m => String -> String -> Parser m a -> Parser m a bracedP obr cbr =     between-        (try . ignore $ string obr >> spaces)-        (ignore $ string cbr >> spaces)+        (try . ignore $ string obr >> spacesOrComment)+        (ignore $ string cbr >> spacesOrComment)  varExprP :: Monad m => Parser m Expression varExprP = do     litName <- identifierP-    spaces+    spacesOrComment     return $ case litName of         "true" -> BoolLiteralE True         "false" -> BoolLiteralE False@@ -755,3 +1007,9 @@  before :: Monad m => m a -> m b -> m a before = flip followedBy++keyword :: Monad m => String -> Parser m String+keyword kw = do+    string kw+    notFollowedBy identCharP+    return kw
src/Text/Ginger/Run.hs view
@@ -5,6 +5,7 @@ {-#LANGUAGE TypeSynonymInstances #-} {-#LANGUAGE MultiParamTypeClasses #-} {-#LANGUAGE ScopedTypeVariables #-}+{-#LANGUAGE LambdaCase #-} -- | Execute Ginger templates in an arbitrary monad. -- -- Usage example:@@ -101,6 +102,7 @@ import Network.HTTP.Types (urlEncode) import Debug.Trace (trace) import Data.List (lookup, zipWith, unzip)+import Data.Aeson as JSON  defaultScope :: forall m h. (Monoid h, Monad m, ToGVal (Run m h) h) => [(Text, GVal (Run m h))] defaultScope =@@ -164,7 +166,7 @@                , ToGVal (Run m h) v                , ToGVal (Run m h) h                )-            => (h -> m ()) -> v -> Template -> m ()+            => (h -> m ()) -> v -> Template -> m (GVal (Run m h)) easyRenderM emit context template =     runGingerT (easyContext emit context) template @@ -191,7 +193,10 @@ runGinger context template = execWriter $ runGingerT context template  -- | Monadically run a Ginger template. The @m@ parameter is the carrier monad.-runGingerT :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m) => GingerContext m h -> Template -> m ()+runGingerT :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m)+           => GingerContext m h+           -> Template+           -> m (GVal (Run m h)) runGingerT context tpl = runReaderT (evalStateT (runTemplate tpl) (defRunState tpl)) context  -- | Find the effective base template of an inheritance chain@@ -202,11 +207,16 @@         Just p -> baseTemplate p  -- | Run a template.-runTemplate :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m) => Template -> Run m h ()+runTemplate :: (ToGVal (Run m h) h, Monoid h, Monad m, Functor m)+            => Template+            -> Run m h (GVal (Run m h)) runTemplate = runStatement . templateBody . baseTemplate  -- | Run an action within a different template context.-withTemplate :: (Monad m, Functor m) => Template -> Run m h a -> Run m h a+withTemplate :: (Monad m, Functor m)+             => Template+             -> Run m h a+             -> Run m h a withTemplate tpl a = do     oldTpl <- gets rsCurrentTemplate     oldBlockName <- gets rsCurrentBlockName@@ -216,7 +226,10 @@     return result  -- | Run an action within a block context-withBlockName :: (Monad m, Functor m) => VarName -> Run m h a -> Run m h a+withBlockName :: (Monad m, Functor m)+              => VarName+              -> Run m h a+              -> Run m h a withBlockName blockName a = do     oldBlockName <- gets rsCurrentBlockName     modify (\s -> s { rsCurrentBlockName = Just blockName })@@ -241,16 +254,37 @@                     templateParent tpl >>= resolveBlock name  -- | Run one statement.-runStatement :: forall m h. (ToGVal (Run m h) h, Monoid h, Monad m, Functor m) => Statement -> Run m h ()-runStatement NullS = return ()-runStatement (MultiS xs) = forM_ xs runStatement-runStatement (LiteralS html) = echo (toGVal html)-runStatement (InterpolationS expr) = runExpression expr >>= echo+runStatement :: forall m h+              . ( ToGVal (Run m h) h+                , Monoid h+                , Monad m+                , Functor m+                )+             => Statement+             -> Run m h (GVal (Run m h))+runStatement NullS =+    return def+runStatement (MultiS xs) =+    forM xs runStatement >>= \case+        [] -> return def+        rvals -> return $ List.last rvals+runStatement (LiteralS html) =+    echo (toGVal html) >> return def+runStatement (InterpolationS expr) =+    runExpression expr >>= echo >> return def+runStatement (ExpressionS expr) =+    runExpression expr runStatement (IfS condExpr true false) = do     cond <- runExpression condExpr     runStatement $ if toBoolean cond then true else false -runStatement (SwitchS pivotExpr cases def) = do+runStatement (IndentS expr body) = do+    i <- runExpression expr+    encode <- asks contextEncode+    let istr = encode i+    indented istr $ runStatement body++runStatement (SwitchS pivotExpr cases defBranch) = do     pivot <- runExpression pivotExpr     let branches =             [ \cont -> do@@ -261,20 +295,23 @@             | (condExpr, body)             <- cases             ] ++-            [ Prelude.const (runStatement def)-            ]+            [ Prelude.const $ runStatement defBranch ]     go branches     where-        go [] = return ()+        go :: [ Run m h (GVal (Run m h)) -> Run m h (GVal (Run m h)) ]+           -> Run m h (GVal (Run m h))+        go [] = return def         go (x:xs) = x (go xs)  runStatement (SetVarS name valExpr) = do     val <- runExpression valExpr     setVar name val+    return def  runStatement (DefMacroS name macro) = do     let val = macroToGVal macro     setVar name val+    return def  runStatement (BlockRefS blockName) = do     block <- lookupBlock blockName@@ -283,7 +320,7 @@  runStatement (ScopedS body) = withLocalScope runInner     where-        runInner :: (Functor m, Monad m) => Run m h ()+        runInner :: (Functor m, Monad m) => Run m h (GVal (Run m h))         runInner = runStatement body  runStatement (ForS varNameIndex varNameValue itereeExpr body) = do@@ -305,7 +342,8 @@                 loop :: [(Maybe Text, GVal (Run m h))] -> Run m h (GVal (Run m h))                 loop [] = fail "Invalid call to `loop`; at least one argument is required"                 loop ((_, loopee):_) = go (Prelude.succ recursionDepth) loopee-                iteration :: (Int, (GVal (Run m h), GVal (Run m h))) -> Run m h ()+                iteration :: (Int, (GVal (Run m h), GVal (Run m h)))+                          -> Run m h (GVal (Run m h))                 iteration (index, (key, value)) = do                     setVar varNameValue value                     setVar "loop" $@@ -322,18 +360,24 @@                              ])                              { asFunction = Just loop }                     case varNameIndex of-                        Nothing -> return ()+                        Nothing -> return def                         Just n -> setVar n key                     runStatement body-            withLocalScope $ forM_ (Prelude.zip [0..] iterPairs) iteration-            return def-    runExpression itereeExpr >>= go 0 >> return ()+            (withLocalScope $ forM (Prelude.zip [0..] iterPairs) iteration) >>= \case+                [] -> return def+                rvals -> return $ List.last rvals+    runExpression itereeExpr >>= go 0  runStatement (PreprocessedIncludeS tpl) =     withTemplate tpl $ runTemplate tpl  -- | Deeply magical function that converts a 'Macro' into a Function.-macroToGVal :: forall m h. (ToGVal (Run m h) h, Monoid h, Functor m, Monad m) => Macro -> GVal (Run m h)+macroToGVal :: forall m h+             . ( ToGVal (Run m h) h+               , Monoid h+               , Functor m+               , Monad m+               ) => Macro -> GVal (Run m h) macroToGVal (Macro argNames body) =     fromFunction f     where@@ -392,15 +436,64 @@     condVal <- runExpression condition     let expr = if asBoolean condVal then yes else no     runExpression expr+runExpression (DoE stmt) =+    runStatement stmt  -- | Helper function to output a HTML value using whatever print function the -- context provides.-echo :: (Monad m, Functor m) => GVal (Run m h) -> Run m h ()+echo :: (Monad m, Functor m, Monoid h)+     => GVal (Run m h) -> Run m h () echo src = do     e <- asks contextEncode+    newlinesMay <- asks contextNewlines+    indentationMay <- gets rsIndentation     p <- asks contextWrite-    p . e $ src+    let indent = fromMaybe id $ do+            newlines <- newlinesMay+            indentation <- indentationMay+            return $ applyIndentation newlines indentation +    p . indent . e $ src++applyIndentation :: (Monoid h)+                 => Newlines h+                 -> [h]+                 -> h+                 -> h+applyIndentation n levels =+    let indent = mconcat . List.reverse $ levels+    in joinLines n .+       fmap (indent <>) .+       splitLines n++indented :: (Monad m, Functor m, Monoid h)+         => h+         -> Run m h a+         -> Run m h a+indented i action = do+    pushIndent i *> action <* popIndent++pushIndent :: (Monad m, Functor m, Monoid h)+           => h+           -> Run m h ()+pushIndent i =+    modify $ \state ->+        state { rsIndentation = increaseIndent i (rsIndentation state) }+popIndent :: (Monad m, Functor m, Monoid h)+           => Run m h ()+popIndent =+    modify $ \state ->+        state { rsIndentation = decreaseIndent (rsIndentation state) }++increaseIndent :: a -> Maybe [a] -> Maybe [a]+increaseIndent _ Nothing = Just []+increaseIndent x (Just xs) = Just (x:xs)++decreaseIndent :: Maybe [a] -> Maybe [a]+decreaseIndent Nothing = Nothing+decreaseIndent (Just []) = Nothing+decreaseIndent (Just (x:xs)) = Just xs+ defRunState :: forall m h. (ToGVal (Run m h) h, Monoid h, Monad m)             => Template             -> RunState m h@@ -410,9 +503,11 @@         , rsCapture = mempty         , rsCurrentTemplate = tpl         , rsCurrentBlockName = Nothing+        , rsIndentation = Nothing         } -gfnEval :: (Monad m, Monoid h, ToGVal (Run m h) h) => Function (Run m h)+gfnEval :: (Monad m, Monoid h, ToGVal (Run m h) h)+        => Function (Run m h) gfnEval args =     let extracted =             extractArgsDefL
src/Text/Ginger/Run/Type.hs view
@@ -21,6 +21,9 @@ , liftRun2 , Run (..) , RunState (..)+-- * The Newlines type+-- | Required for handling indentation+, Newlines (..) ) where @@ -54,6 +57,7 @@ import Text.PrintfA import Data.Scientific (formatScientific) +import Data.Char (isSpace) import Data.Text (Text) import Data.String (fromString) import qualified Data.Text as Text@@ -82,6 +86,7 @@         { contextLookup :: VarName -> Run m h (GVal (Run m h))         , contextWrite :: h -> Run m h ()         , contextEncode :: GVal (Run m h) -> h+        , contextNewlines :: Maybe (Newlines h)         }  contextWriteEncoded :: GingerContext m h -> GVal (Run m h) -> Run m h ()@@ -101,20 +106,25 @@                     (toGVal context)))         emit         encode+        newlines   -- | Typeclass that defines how to encode 'GVal's into a given type. class ContextEncodable h where     encode :: forall m. GVal m -> h+    newlines :: Maybe (Newlines h)+    newlines = Nothing  -- | Encoding to text just takes the text representation without further -- processing. instance ContextEncodable Text where     encode = asText+    newlines = Just textNewlines  -- | Encoding to Html is implemented as returning the 'asHtml' representation. instance ContextEncodable Html where     encode = toHtml+    newlines = Just htmlNewlines  -- | Create an execution context for runGingerT. -- Takes a lookup function, which returns ginger values into the carrier monad@@ -125,12 +135,14 @@              => (VarName -> Run m h (GVal (Run m h)))              -> (h -> m ())              -> (GVal (Run m h) -> h)+             -> Maybe (Newlines h)              -> GingerContext m h-makeContextM' lookupFn writeFn encodeFn =+makeContextM' lookupFn writeFn encodeFn newlines =     GingerContext         { contextLookup = lookupFn         , contextWrite = liftRun2 writeFn         , contextEncode = encodeFn+        , contextNewlines = newlines         }  liftLookup :: (Monad m, ToGVal (Run m h) v) => (VarName -> m v) -> VarName -> Run m h (GVal (Run m h))@@ -151,12 +163,12 @@ makeContext' :: Monoid h             => (VarName -> GVal (Run (Writer h) h))             -> (GVal (Run (Writer h) h) -> h)+            -> Maybe (Newlines h)             -> GingerContext (Writer h) h-makeContext' lookupFn encodeFn =+makeContext' lookupFn =     makeContextM'         (return . lookupFn)         tell-        encodeFn  {-#DEPRECATED makeContext "Compatibility alias for makeContextHtml" #-} makeContext :: (VarName -> GVal (Run (Writer Html) Html))@@ -172,31 +184,66 @@  makeContextHtml :: (VarName -> GVal (Run (Writer Html) Html))                 -> GingerContext (Writer Html) Html-makeContextHtml l = makeContext' l toHtml+makeContextHtml l = makeContext' l toHtml (Just htmlNewlines)  makeContextHtmlM :: (Monad m, Functor m)                  => (VarName -> Run m Html (GVal (Run m Html)))                  -> (Html -> m ())                  -> GingerContext m Html-makeContextHtmlM l w = makeContextM' l w toHtml+makeContextHtmlM l w = makeContextM' l w toHtml (Just htmlNewlines)  makeContextText :: (VarName -> GVal (Run (Writer Text) Text))                 -> GingerContext (Writer Text) Text-makeContextText l = makeContext' l asText+makeContextText l = makeContext' l asText (Just textNewlines)  makeContextTextM :: (Monad m, Functor m)                  => (VarName -> Run m Text (GVal (Run m Text)))                  -> (Text -> m ())                  -> GingerContext m Text-makeContextTextM l w = makeContextM' l w asText+makeContextTextM l w = makeContextM' l w asText (Just textNewlines) +-- | A 'Newlines' determines the rules by which a 'h' value can be+-- split into lines, how a list of lines can be joined into a single+-- value, and how to remove leading whitespace.+data Newlines h =+    Newlines+        { splitLines :: h -> [h]+        , joinLines :: [h] -> h+        , stripIndent :: h -> h+        } +stringNewlines :: Newlines String+stringNewlines =+    Newlines+        { splitLines = List.lines+        , joinLines = List.unlines+        , stripIndent = List.dropWhile isSpace+        }++textNewlines :: Newlines Text+textNewlines =+    Newlines+        { splitLines = Text.lines+        , joinLines = Text.unlines+        , stripIndent = Text.stripStart+        }++htmlNewlines :: Newlines Html+htmlNewlines =+    Newlines+        { splitLines = fmap unsafeRawHtml . splitLines textNewlines . htmlSource+        , joinLines = unsafeRawHtml . joinLines textNewlines . fmap htmlSource+        , stripIndent = unsafeRawHtml . stripIndent textNewlines . htmlSource+        }++ data RunState m h     = RunState         { rsScope :: HashMap VarName (GVal (Run m h))         , rsCapture :: h         , rsCurrentTemplate :: Template -- the template we are currently running         , rsCurrentBlockName :: Maybe Text -- the name of the innermost block we're currently in+        , rsIndentation :: Maybe [h] -- current indentation level, if any         }  -- | Internal type alias for our template-runner monad stack.@@ -209,5 +256,3 @@ -- | Lift a function from the host monad @m@ into the 'Run' monad. liftRun2 :: Monad m => (a -> m b) -> a -> Run m h b liftRun2 f x = liftRun $ f x--
test/Text/Ginger/SimulationTests.hs view
@@ -727,6 +727,126 @@         , testCase "after exiting local scope" $ do             mkTestHtml [] [] "{% set bedazzle = \"no\" %}{% scope %}{% set bedazzle = \"ya\" %}{% endscope %}{{ bedazzle }}" "no"         ]+    , testGroup "Indentation"+        [ testCase "stripping leading spaces" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent %}"+                    , "  aaaaa"+                    , "  aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "aaaaa"+                    ])+        , testCase "explicit indent string" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent %}"+                    , "    aaaaa"+                    , "{% indent '    ' %}"+                    , "    aaaaa"+                    , "{% endindent %}"+                    , "    aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "    aaaaa"+                    , "aaaaa"+                    ])+        , testCase "implicit indent string" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent %}"+                    , "    aaaaa"+                    , "{% indent %}"+                    , "  aaaaa"+                    , "{% endindent %}"+                    , "    aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "  aaaaa"+                    , "aaaaa"+                    ])+        , testCase "explicit non-whitespace indent string" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent %}"+                    , "    aaaaa"+                    , "{% indent '--- ' %}"+                    , "    aaaaa"+                    , "{% endindent %}"+                    , "    aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "--- aaaaa"+                    , "aaaaa"+                    ])+        , testCase "explicit indent string from more complex expression" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent %}"+                    , "  aaaaa"+                    , "{% indent (17 + 4) ~ ' '%}"+                    , "  aaaaa"+                    , "{% endindent %}"+                    , "  aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "21 aaaaa"+                    , "aaaaa"+                    ])+        , testCase "discarding level-0 indents" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% indent 'nope' %}"+                    , "  aaaaa"+                    , "{% indent %}"+                    , "  aaaaa"+                    , "{% endindent %}"+                    , "  aaaaa"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "aaaaa"+                    , "  aaaaa"+                    , "aaaaa"+                    ])+        , testCase "indentation levels inherited at runtime (dynamic)" $ do+            mkTestHtml [] []+                (unlines+                    [ "{%- macro foobar() %}"+                    , "{% indent '  ' %}"+                    , "<div>"+                    , "{% indent '  ' %}"+                    , "<h1>Hello!</h1>"+                    , "{% endindent %}"+                    , "</div>"+                    , "{% endindent %}"+                    , "{% endmacro -%}"+                    , ""+                    , "{% indent '' %}"+                    , "<body>"+                    , "{{ foobar() }}"+                    , "</body>"+                    , "{% endindent %}"+                    ])+                (Text.unlines+                    [ "<body>"+                    , "  <div>"+                    , "    <h1>Hello!</h1>"+                    , "  </div>"+                    , "</body>"+                    ])+        ]     , testGroup "Macros"         [ testCase "simple" $ do             mkTestHtml [] []@@ -800,6 +920,198 @@                 "{{ '<>' }}{{ 1 }}{{ {'foo': true} }}"                 "[\"<>\",1,{\"foo\":true}]"         ]+    , testGroup "Script mode"+        [ testCase "empty script block" $+            mkTestHtml+                []+                []+                "{% script %}{% endscript %}"+                ""+        , testGroup "comments"+            [ testCase "simple" $+                mkTestHtml+                    []+                    []+                    "{% script %}  # this is a comment\n{% endscript %}"+                    ""+            , testCase "multiple" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "23; # this is a comment"+                        , " ## this is a comment, too"+                        , "{% endscript %}"+                        ])+                    ""+            , testCase "inside expressions" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "23 # this is a comment"+                        , " ## this is a comment, too"+                        , " ;"+                        , "{% endscript %}"+                        ])+                    ""+            ]+        , testCase "echo" $+            mkTestHtml+                []+                []+                "{% script %}echo('Hi!');{% endscript %}"+                "Hi!"+        , testCase "expression statement" $+            mkTestHtml+                []+                []+                "{% script %}12 + 15;{% endscript %}"+                ""+        , testCase "grouped statements" $+            mkTestHtml+                []+                []+                "{% script %}{ \n 1; 2; 3; 4; }{% endscript %}"+                ""+        , testCase "if" $+            mkTestHtml+                []+                []+                "{% script %}if (true) { echo('Hi!'); }{% endscript %}"+                "Hi!"+        , testCase "if/else" $+            mkTestHtml+                []+                []+                "{% script %}if (false) echo ('nope'); else { echo('Hi!'); }{% endscript %}"+                "Hi!"+        , testCase "switch" $+            mkTestHtml+                []+                []+                "{% script %}switch ('hi') { case 'hi': echo('Hello'); case 'no': echo('Nope'); default: echo('Default'); }{% endscript %}"+                "Hello"+        , testCase "for" $+            mkTestHtml+                []+                []+                "{% script %}for (i in [1,2,3]) echo(i);{% endscript %}"+                "123"+        , testCase "for/else (loop)" $+            mkTestHtml+                []+                []+                "{% script %}for (i in [1,2,3]) echo(i); else echo('none');{% endscript %}"+                "123"+        , testCase "for/else (recover)" $+            mkTestHtml+                []+                []+                "{% script %}for (i in null) echo(i); else echo('none');{% endscript %}"+                "none"+        , testCase "set" $+            mkTestHtml+                []+                []+                "{% script %}set a = 'Hi' ~ '!'; echo(a);{% endscript %}"+                "Hi!"+        , testCase "include" $ do+            mkTestHtml [] [("./features-included.html", "This is an included template")]+                "{% script %}include('features-included.html');{% endscript %}"+                "This is an included template"+        , testCase "macro" $ do+            mkTestHtml [] []+                (unlines+                    [ "{% script %}"+                    , "macro greet(name) {"+                    , "echo('Hello, ');"+                    , "echo(name);"+                    , "}"+                    , ""+                    , "echo(greet('tobias'));"+                    , "{% endscript %}"+                    ])+                "Hello, tobias"+        , testGroup "Script statment blocks"+            [ testCase "baseline" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "echo(bedazzle);"+                        , "{% endscript %}"+                        ])+                    "no"+            , testCase "inside local scope" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "{"+                        , "    set bedazzle = 'ya';"+                        , "    echo(bedazzle);"+                        , "}"+                        , "{% endscript %}"+                        ])+                    "ya"+            , testCase "after exiting block" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "{"+                        , "    set bedazzle = 'ya';"+                        , "}"+                        , "echo(bedazzle);"+                        , "{% endscript %}"+                        ])+                    "ya"+            ]+        , testGroup "Explicit Local Scopes"+            [ testCase "baseline" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "echo(bedazzle);"+                        , "{% endscript %}"+                        ])+                    "no"+            , testCase "inside local scope" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "scope {"+                        , "    set bedazzle = 'ya';"+                        , "    echo(bedazzle);"+                        , "}"+                        , "{% endscript %}"+                        ])+                    "ya"+            , testCase "after exiting local scope" $ do+                mkTestHtml [] []+                    (unlines+                        [ "{% script %}"+                        , "set bedazzle = 'no';"+                        , "scope {"+                        , "    set bedazzle = 'ya';"+                        , "}"+                        , "echo(bedazzle);"+                        , "{% endscript %}"+                        ])+                    "no"+            ]+        ]+    , testGroup "do expressions"+        [ testCase "single statement" $ do+            mkTestHtml [] []+                "{{ do 'hello'; }}"+                "hello"+        , testCase "statement block" $ do+            mkTestHtml [] []+                "{{ do { 'hello'; 'world'; } }}"+                "world"+        ]     ]  mkTestHtml :: [(VarName, GVal (Run IO Html))]@@ -822,7 +1134,7 @@            -> Text            -> Assertion mkTestJSON = mkTest-    (\l w -> makeContextM' l w toJSONSingleton) encodeText+    (\l w -> makeContextM' l w toJSONSingleton Nothing) encodeText     where         toJSONSingleton = (:[]) . JSON.toJSON