doctemplates 0.8.2 → 0.8.3
raw patch · 6 files changed
+118/−57 lines, 6 filesdep ~HsYAMLPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: HsYAML
API changes (from Hackage documentation)
- Text.DocTemplates.Internal: instance (Data.String.IsString a, Text.DocTemplates.Internal.TemplateTarget a) => Data.Aeson.Types.FromJSON.FromJSON (Text.DocTemplates.Internal.Context a)
- Text.DocTemplates.Internal: instance (Data.String.IsString a, Text.DocTemplates.Internal.TemplateTarget a) => Data.Aeson.Types.FromJSON.FromJSON (Text.DocTemplates.Internal.Val a)
- Text.DocTemplates.Internal: instance (Data.String.IsString a, Text.DocTemplates.Internal.TemplateTarget a) => Data.YAML.FromYAML (Text.DocTemplates.Internal.Context a)
- Text.DocTemplates.Internal: instance (Data.String.IsString a, Text.DocTemplates.Internal.TemplateTarget a) => Text.DocTemplates.Internal.ToContext a Data.Aeson.Types.Internal.Value
+ Text.DocTemplates.Internal: instance Text.DocTemplates.Internal.TemplateTarget a => Data.Aeson.Types.FromJSON.FromJSON (Text.DocTemplates.Internal.Context a)
+ Text.DocTemplates.Internal: instance Text.DocTemplates.Internal.TemplateTarget a => Data.Aeson.Types.FromJSON.FromJSON (Text.DocTemplates.Internal.Val a)
+ Text.DocTemplates.Internal: instance Text.DocTemplates.Internal.TemplateTarget a => Data.YAML.FromYAML (Text.DocTemplates.Internal.Context a)
+ Text.DocTemplates.Internal: instance Text.DocTemplates.Internal.TemplateTarget a => Text.DocTemplates.Internal.ToContext a Data.Aeson.Types.Internal.Value
Files
- changelog.md +21/−0
- doctemplates.cabal +2/−2
- src/Text/DocTemplates.hs +21/−9
- src/Text/DocTemplates/Internal.hs +27/−20
- src/Text/DocTemplates/Parser.hs +6/−26
- test/nested-loop.test +41/−0
changelog.md view
@@ -1,5 +1,26 @@ # doctemplates +## 0.8.3++ * Properly handle nested loops (#15). Previously "it" was+ always used for the variable in a loop, and in a nested loop+ there was no way to distinguish the value of the inner+ iteration from the value of the outer one. Now we assign+ the iterated value to both "it" and to the original variable+ name (e.g. "foo.bar"). This probably has a small negative+ performance impact. Note that this change also affects+ the output of the template parser: original variable+ names are now retained instead of being replaced by "it".++ * Remove duplicate IsString constraint (#14, Mario Lang).++ * Update haddocks from README (#10).++ * Minor code clean-ups (#7, favonia).++ * Add hsyaml >= 0.2 constraint (#6).++ ## 0.8.2 * Add filters: first, rest, last, allbutlast.
doctemplates.cabal view
@@ -1,5 +1,5 @@ name: doctemplates-version: 0.8.2+version: 0.8.3 synopsis: Pandoc-style document templates description: This is the text templating system used by pandoc. It supports variable interpolation, iteration,@@ -32,7 +32,7 @@ safe, text-conversions, aeson,- HsYAML,+ HsYAML >= 0.2 && < 0.3, text, doclayout >= 0.3 && < 0.4, containers,
src/Text/DocTemplates.hs view
@@ -14,9 +14,9 @@ Control structures are provided to test whether a variable has a non-blank value and to iterate over the items of a list. Partials—that-is, subtemplates defined in different files—are supported. Filters can-be used to transform the values of variables or partials. The provided-filters make it possible to do list enumeration and tabular layout in+is, subtemplates defined in different files—are supported. Pipes can be+used to transform the values of variables or partials. The provided+pipes make it possible to do list enumeration and tabular layout in templates. Templates are rendered to a doclayout @Doc@ (which is polymorphic in the@@ -309,11 +309,11 @@ The @~@ keyword has no effect when rendering to @Text@ or @String@. -== Filters+== Pipes -A filter transforms the value of a variable or partial. Filters are+A pipe transforms the value of a variable or partial. Pipes are specified using a slash (@\/@) between the variable name (or partial)-and the filter name. Example:+and the pipe name. Example: > $for(name)$ > $name/uppercase$@@ -325,13 +325,13 @@ > > $employee:name()/uppercase$ -Filters may be chained:+Pipes may be chained: > $for(employees/pairs)$ > $it.key/alpha/uppercase$. $it.name$ > $endfor$ -Some filters take parameters:+Some pipes take parameters: > |----------------------|------------| > $for(employee)$@@ -339,11 +339,23 @@ > $endfor$ > |----------------------|------------| -Currently the following filters are predefined:+Currently the following pipes are predefined: - @pairs@: Converts a map or array to an array of maps, each with @key@ and @value@ fields. If the original value was an array, the @key@ will be the array index, starting with 1.++- @first@: Returns the first value of an array, if applied to a+ non-empty array; otherwise returns the original value.++- @last@: Returns the last value of an array, if applied to a+ non-empty array; otherwise returns the original value.++- @rest@: Returns all but the first value of an array, if applied to a+ non-empty array; otherwise returns the original value.++- @allbutlast@: Returns all but the last value of an array, if applied+ to a non-empty array; otherwise returns the original value. - @uppercase@: Converts text to uppercase.
src/Text/DocTemplates/Internal.hs view
@@ -2,7 +2,6 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-}@@ -10,8 +9,6 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-} {- | Module : Text.DocTemplates.Internal@@ -44,6 +41,7 @@ import Control.Monad.Identity import qualified Control.Monad.State.Strict as S import Data.Char (chr, ord)+import Data.Maybe (fromMaybe) import qualified Data.Text.Read as T import qualified Data.Text as T import qualified Data.Text.IO as TIO@@ -166,15 +164,15 @@ toVal = SimpleVal . DL.literal instance ToContext a a => ToContext a (Doc a) where- toVal = SimpleVal+ toVal = SimpleVal -- This is needed because otherwise the compiler tries to -- match on ToContext a [b], with a = b = Char, even though -- we don't have ToContext Char Char. I don't understand why.-instance {-# OVERLAPS #-} ToContext String String where+instance {-# OVERLAPPING #-} ToContext String String where toVal = SimpleVal . DL.literal -instance {-# OVERLAPS #-} ToContext String (Doc String) where+instance {-# OVERLAPPING #-} ToContext String (Doc String) where toVal = SimpleVal instance ToContext a b => ToContext a [b] where@@ -188,7 +186,7 @@ toVal True = SimpleVal "true" toVal False = NullVal -instance (IsString a, TemplateTarget a) => ToContext a Value where+instance TemplateTarget a => ToContext a Value where toContext x = case fromJSON x of Success y -> y Error _ -> mempty@@ -217,7 +215,7 @@ -- This is needed because otherwise the compiler tries to -- match on FromContext a [b], with a = b = Char, even though -- we don't have FromContext Char Char. I don't understand why.-instance {-# OVERLAPS #-} FromContext String String where+instance {-# OVERLAPPING #-} FromContext String String where fromVal (SimpleVal x) = Just (DL.render Nothing x) fromVal _ = Nothing @@ -225,7 +223,7 @@ fromVal (ListVal xs) = mapM fromVal xs fromVal x = sequence [fromVal x] -instance (IsString a, TemplateTarget a) => FromJSON (Val a) where+instance TemplateTarget a => FromJSON (Val a) where parseJSON v = case v of Array vec -> ListVal <$> mapM parseJSON (V.toList vec)@@ -239,7 +237,7 @@ mapM parseJSON o _ -> return NullVal -instance (IsString a, TemplateTarget a) => FromJSON (Context a) where+instance TemplateTarget a => FromJSON (Context a) where parseJSON v = do val <- parseJSON v case val of@@ -261,7 +259,7 @@ Scalar _ (SBool True) -> return $ SimpleVal "true" _ -> return NullVal -instance (IsString a, TemplateTarget a) => FromYAML (Context a) where+instance TemplateTarget a => FromYAML (Context a) where parseYAML v = do val <- parseYAML v case val of@@ -349,7 +347,7 @@ applyPipe ToRoman val = mapText toRoman' val where toRoman' t = case T.decimal t of- Right (y,"") -> maybe t id (toRoman y)+ Right (y,"") -> fromMaybe t (toRoman y) _ -> t applyPipe NoWrap val = mapDoc DL.nowrap val applyPipe (Block align n border) val =@@ -424,14 +422,23 @@ withVariable :: (Monad m, TemplateTarget a) => Variable -> Context a -> (Context a -> m (Doc a)) -> m [Doc a]-withVariable v ctx f =- case applyPipes (varPipes v) $ multiLookup (varParts v) (MapVal ctx) of+withVariable var ctx f =+ case applyPipes (varPipes var) $ multiLookup (varParts var) (MapVal ctx) of NullVal -> return mempty- ListVal xs -> mapM (\iterval -> f $- Context $ M.insert "it" iterval $ unContext ctx) xs- MapVal ctx' -> (:[]) <$> f- (Context $ M.insert "it" (MapVal ctx') $ unContext ctx)- val' -> (:[]) <$> f (Context $ M.insert "it" val' $ unContext ctx)+ ListVal xs -> mapM (\iterval -> f $ setVarVal iterval) xs+ MapVal ctx' -> (:[]) <$> f (setVarVal (MapVal ctx'))+ val' -> (:[]) <$> f (setVarVal val')+ where+ setVarVal x =+ addToContext var x $ Context $ M.insert "it" x $ unContext ctx+ addToContext (Variable [] _) _ (Context ctx') = Context ctx'+ addToContext (Variable (v:vs) fs) x (Context ctx') =+ Context $ M.adjust+ (\z -> case z of+ _ | null vs -> x+ MapVal m ->+ MapVal $ addToContext (Variable vs fs) x m+ _ -> z) v ctx' type RenderState = S.State Int @@ -448,7 +455,7 @@ renderTemp :: forall a . TemplateTarget a => Template a -> Context a -> RenderState (Doc a)-renderTemp (Literal t) _ = updateColumn $ t+renderTemp (Literal t) _ = updateColumn t renderTemp (Interpolate v) ctx = updateColumn $ mconcat $ resolveVariable v ctx renderTemp (Conditional v ift elset) ctx = let res = resolveVariable v ctx
src/Text/DocTemplates/Parser.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE BangPatterns #-} {- | Module : Text.DocTemplates.Parser Copyright : Copyright (C) 2009-2019 John MacFarlane@@ -24,7 +23,6 @@ import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Read as T-import Data.List (isPrefixOf) import System.FilePath import Text.DocTemplates.Internal import qualified Text.DocLayout as DL@@ -140,10 +138,9 @@ pDirective :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)-pDirective = do- res <- pConditional <|> pForLoop <|> pReflowToggle <|> pNested <|>- pInterpolate <|> pBarePartial- return res+pDirective =+ pConditional <|> pForLoop <|> pReflowToggle <|> pNested <|>+ pInterpolate <|> pBarePartial pEnclosed :: Monad m => Parser m a -> Parser m a pEnclosed parser = P.try $ do@@ -227,7 +224,7 @@ return (y <> z) let contents = x <> mconcat xs P.updateState $ \st -> st{ nestedCol = oldNested }- return $ Nested $ contents+ return $ Nested contents pForLoop :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a) pForLoop = do@@ -235,31 +232,14 @@ -- if newline after the "for", then a newline after "endfor" will be swallowed pInside $ do multiline <- P.option False $ skipEndline >> return True- contents <- changeToIt v <$> pTemplate+ contents <- pTemplate sep <- P.option mempty $ do pEnclosed (P.string "sep") when multiline $ P.option () skipEndline- changeToIt v <$> pTemplate+ pTemplate pEnclosed (P.string "endfor") when multiline $ P.option () skipEndline return $ Iterate v contents sep--changeToIt :: (Semigroup a) => Variable -> Template a -> Template a-changeToIt v = go- where- go (Interpolate w) = Interpolate (reletter v w)- go (Conditional w t1 t2) = Conditional (reletter v w)- (changeToIt v t1) (changeToIt v t2)- go (Iterate w t1 t2) = Iterate (reletter v w)- (changeToIt v t1) (changeToIt v t2)- go (Concat t1 t2) = changeToIt v t1 <> changeToIt v t2- go (Partial fs t) = Partial fs t -- don't reletter inside partial- go (Nested t) = Nested (go t)- go x = x- reletter (Variable vs _fs) (Variable ws gs) =- if vs `isPrefixOf` ws- then Variable ("it" : drop (length vs) ws) gs- else Variable ws gs pInterpolate :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)
+ test/nested-loop.test view
@@ -0,0 +1,41 @@+{+ "pages": [+ {+ "subpages": [+ {+ "slug": "subpage-1"+ },+ {+ "slug": "subpage-2"+ }+ ],+ "slug": "page-1"+ },+ {+ "subpages": [+ {+ "slug": "subpage-1"+ },+ {+ "slug": "subpage-2"+ }+ ],+ "slug": "page-2"+ }+ ]+}+.+$-- see #15+$for(pages)$+/$pages.slug$+$for(pages.subpages)$+ /$pages.slug$/$pages.subpages.slug$+$endfor$+$endfor$+.+/page-1+ /page-1/subpage-1+ /page-1/subpage-2+/page-2+ /page-2/subpage-1+ /page-2/subpage-2