packages feed

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 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