doctemplates 0.8.3 → 0.11.0.1
raw patch · 7 files changed
Files
- README.md +13/−6
- changelog.md +46/−0
- doctemplates.cabal +5/−7
- src/Text/DocTemplates.hs +11/−5
- src/Text/DocTemplates/Internal.hs +42/−64
- test/boolean.test +13/−0
- test/keyval.txt +0/−1
README.md view
@@ -107,6 +107,8 @@ rendered verbatim. (Note that no escaping is done; the assumption is that the calling program will escape the strings appropriately for the output format.)+- If the value of the variable is a boolean value, it+ will be rendered as `true` if true, or as empty if false. - If the value is a list, the values will be concatenated. - If the value is a map, the string `true` will be rendered. - Every other value will be rendered as the empty string.@@ -116,8 +118,6 @@ - If the value is a number, it will be rendered as an integer if possible, otherwise as a floating-point number.-- If the value is a JSON boolean, it will be rendered as `true`- if true, and as the empty string if false. ## Conditionals@@ -126,10 +126,17 @@ matched delimiters) and ends with `endif` (enclosed in matched delimiters). It may optionally contain an `else` (enclosed in matched delimiters). The `if` section is used if-`variable` has a non-empty value, otherwise the `else`-section is used (if present). (Note that even the-string `false` counts as a true value.) Examples:+`variable` has a true value, otherwise the `else`+section is used (if present). The following values+count as true: +- any map+- any array containing at least one true value+- any nonempty string (even `false`)+- boolean True++Examples:+ ``` $if(foo)$bar$endif$ @@ -187,7 +194,7 @@ A for loop begins with `for(variable)` (enclosed in matched delimiters) and ends with `endfor` (enclosed in matched-delimiters.+delimiters). - If `variable` is an array, the material inside the loop will be evaluated repeatedly, with `variable` being set to each
changelog.md view
@@ -1,5 +1,51 @@ # doctemplates +# 0.11.0.1++ * Bump version bounds for doclayout.++ * Fix typos.++# 0.11++ * Remove HsYAML depenedency.++ * Remove upper bound for criterion.++## 0.10.0.2++ * Use doclayout 0.4.++## 0.10.0.1++ * Don't rely on aeson Object being implemented as a HashMap.+ This change is needed for doctemplates to compile against aeson 2.0.0.0.++## 0.10++ * Change rendering and conditional behavior with booleans.+ Previously, `$if(foo)$` evaluated to false iff `foo`+ would render as the empty string. This forced us to render+ a boolean False value as an empty string, rather than `false`.+ And this has caused various problems with templates+ (#16, jgm/pandoc#7402). Now, boolean False values render as+ `false` -- just as True values render as `true`. And conditionals+ are now sensitive to booleans, so `$if(foo)$` evaluates to false+ when `foo` is a boolean False value, even though it would render+ as the nonempty string `false`.++## 0.9++ * Add BoolVal constructor to Val. This gives a smoother+ interface with JSON and YAML. [API change]++ * Remove overlapping instances by generalizing+ `ToContext String String` and `FromContext String String`+ to `TemplateTarget [a] => ToContext [a] [a]` and+ `TemplateTarget [a] => FromContext [a] [a]`.+ Remove the instance `ToContext String (Doc String)`.+ Remove redundant constraints. (#9, favonia) [API change]+ ## 0.8.3 * Properly handle nested loops (#15). Previously "it" was
doctemplates.cabal view
@@ -1,5 +1,5 @@ name: doctemplates-version: 0.8.3+version: 0.11.0.1 synopsis: Pandoc-style document templates description: This is the text templating system used by pandoc. It supports variable interpolation, iteration,@@ -32,15 +32,13 @@ safe, text-conversions, aeson,- HsYAML >= 0.2 && < 0.3, text,- doclayout >= 0.3 && < 0.4,+ doclayout >= 0.4 && < 0.6, containers, vector, filepath, parsec, mtl,- unordered-containers, scientific if !impl(ghc >= 8.0) build-depends: semigroups == 0.18.*@@ -53,7 +51,7 @@ main-is: test.hs build-depends: base, doctemplates,- doclayout >= 0.3 && < 0.4,+ doclayout >= 0.4 && < 0.6, containers, aeson, Glob,@@ -72,9 +70,9 @@ Main-Is: bench.hs Hs-Source-Dirs: bench Build-Depends: doctemplates,- doclayout >= 0.3 && < 0.4,+ doclayout >= 0.4 && < 0.6, base >= 4.8 && < 5,- criterion >= 1.0 && < 1.6,+ criterion >= 1.0, filepath, aeson, text,
src/Text/DocTemplates.hs view
@@ -104,6 +104,8 @@ verbatim. (Note that no escaping is done; the assumption is that the calling program will escape the strings appropriately for the output format.)+- If the value of the variable is a boolean value, it will be rendered+ as @true@ if true, or as empty if false. - If the value is a list, the values will be concatenated. - If the value is a map, the string @true@ will be rendered. - Every other value will be rendered as the empty string.@@ -113,17 +115,21 @@ - If the value is a number, it will be rendered as an integer if possible, otherwise as a floating-point number.-- If the value is a JSON boolean, it will be rendered as @true@ if- true, and as the empty string if false. == Conditionals A conditional begins with @if(variable)@ (enclosed in matched delimiters) and ends with @endif@ (enclosed in matched delimiters). It may optionally contain an @else@ (enclosed in matched delimiters). The-@if@ section is used if @variable@ has a non-empty value, otherwise the-@else@ section is used (if present). (Note that even the string @false@-counts as a true value.) Examples:+@if@ section is used if @variable@ has a true value, otherwise the+@else@ section is used (if present). The following values count as true:++- any map+- any array containing at least one true value+- any nonempty string (even @false@)+- boolean True++Examples: > $if(foo)$bar$endif$ >
src/Text/DocTemplates/Internal.hs view
@@ -37,7 +37,6 @@ import Data.Text.Conversions (FromText(..), ToText(..)) import Data.Aeson (Value(..), ToJSON(..), FromJSON(..), Result(..), fromJSON)-import Data.YAML (ToYAML(..), FromYAML(..), Node(..), Scalar(..)) import Control.Monad.Identity import qualified Control.Monad.State.Strict as S import Data.Char (chr, ord)@@ -53,7 +52,6 @@ import GHC.Generics (Generic) import Data.Text (Text) import qualified Data.Map as M-import qualified Data.HashMap.Strict as H import qualified Data.Vector as V import Data.Scientific (floatingOrInteger) import Data.List (intersperse)@@ -129,7 +127,7 @@ mappend = (<>) type TemplateTarget a =- (Monoid a, IsString a, HasChars a, ToText a, FromText a)+ (HasChars a, ToText a, FromText a) -- | A 'Context' defines values for template's variables. newtype Context a = Context { unContext :: M.Map Text (Val a) }@@ -141,6 +139,7 @@ SimpleVal (Doc a) | ListVal [Val a] | MapVal (Context a)+ | BoolVal Bool | NullVal deriving (Show, Traversable, Foldable, Functor, Data, Typeable) @@ -166,25 +165,19 @@ instance ToContext a a => ToContext a (Doc a) where 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 {-# OVERLAPPING #-} ToContext String String where- toVal = SimpleVal . DL.literal--instance {-# OVERLAPPING #-} ToContext String (Doc String) where- toVal = SimpleVal- instance ToContext a b => ToContext a [b] where toVal = ListVal . map toVal +instance {-# OVERLAPPING #-} TemplateTarget [a] => ToContext [a] [a] where+ toVal = SimpleVal . DL.literal+ instance ToContext a b => ToContext a (M.Map Text b) where toVal = MapVal . toContext toContext = Context . M.map toVal instance TemplateTarget a => ToContext a Bool where- toVal True = SimpleVal "true"- toVal False = NullVal+ toVal True = BoolVal True+ toVal False = BoolVal False instance TemplateTarget a => ToContext a Value where toContext x = case fromJSON x of@@ -212,10 +205,7 @@ fromVal (SimpleVal x) = Just (DL.render Nothing x) fromVal _ = Nothing --- 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 {-# OVERLAPPING #-} FromContext String String where+instance {-# OVERLAPPING #-} TemplateTarget [a] => FromContext [a] [a] where fromVal (SimpleVal x) = Just (DL.render Nothing x) fromVal _ = Nothing @@ -232,9 +222,8 @@ case floatingOrInteger n of Left (r :: Double) -> show r Right (i :: Integer) -> show i- Bool True -> return $ SimpleVal "true"- Object o -> MapVal . Context . M.fromList . H.toList <$>- mapM parseJSON o+ Bool b -> return $ BoolVal b+ Object _ -> MapVal . Context <$> parseJSON v _ -> return NullVal instance TemplateTarget a => FromJSON (Context a) where@@ -244,28 +233,6 @@ MapVal o -> return o _ -> fail "Expecting MapVal" -instance TemplateTarget a => FromYAML (Val a) where- parseYAML v =- case v of- Mapping _ _ m -> MapVal . Context . M.fromList <$>- mapM (\(key, val) -> do- val' <- parseYAML val- key' <- parseYAML key- return (key', val')) (M.toList m)- Sequence _ _ xs -> ListVal <$> mapM parseYAML xs- Scalar _ (SStr t) -> return $ SimpleVal $ fromString . fromText $ t- Scalar _ (SFloat n) -> return $ SimpleVal $ fromString . show $ n- Scalar _ (SInt n) -> return $ SimpleVal $ fromString . show $ n- Scalar _ (SBool True) -> return $ SimpleVal "true"- _ -> return NullVal--instance TemplateTarget a => FromYAML (Context a) where- parseYAML v = do- val <- parseYAML v- case val of- MapVal o -> return o- _ -> fail "Expecting MapVal"- instance TemplateTarget a => ToJSON (Context a) where toJSON (Context m) = toJSON m @@ -274,15 +241,7 @@ toJSON (MapVal m) = toJSON m toJSON (ListVal xs) = toJSON xs toJSON (SimpleVal d) = toJSON $ toText $ DL.render Nothing d--instance TemplateTarget a => ToYAML (Context a) where- toYAML (Context m) = toYAML m--instance TemplateTarget a => ToYAML (Val a) where- toYAML NullVal = toYAML (Nothing :: Maybe Text)- toYAML (MapVal m) = toYAML m- toYAML (ListVal xs) = toYAML xs- toYAML (SimpleVal d) = toYAML $ toText $ DL.render Nothing d+ toJSON (BoolVal b) = toJSON b mapDoc :: TemplateTarget a => (Doc a -> Doc a) -> Val a -> Val a mapDoc f val =@@ -290,6 +249,7 @@ SimpleVal d -> SimpleVal (f d) MapVal (Context m) -> MapVal (Context $ M.map (mapDoc f) m) ListVal xs -> ListVal $ map (mapDoc f) xs+ BoolVal b -> BoolVal b NullVal -> NullVal mapText :: TemplateTarget a => (Text -> Text) -> Val a -> Val a@@ -303,6 +263,7 @@ SimpleVal d -> T.length . toText $ DL.render Nothing d MapVal (Context m) -> M.size m ListVal xs -> length xs+ BoolVal _ -> 0 NullVal -> 0 applyPipe ToUppercase val = mapText T.toUpper val applyPipe ToLowercase val = mapText T.toLower val@@ -400,18 +361,34 @@ Just v' -> multiLookup vs v' multiLookup _ _ = NullVal -resolveVariable :: TemplateTarget a => Variable -> Context a -> [Doc a]+-- The Bool indicates whether it's a true or false value.+data Resolved a = Resolved Bool [Doc a]+ deriving (Show, Read, Data, Typeable, Generic, Eq, Ord,+ Foldable, Traversable, Functor)++instance Semigroup (Resolved a) where+ Resolved b1 x1 <> Resolved b2 x2 = Resolved (b1 || b2) (x1 <> x2)++instance Monoid (Resolved a) where+ mappend = (<>)+ mempty = Resolved False []++resolveVariable :: TemplateTarget a+ => Variable -> Context a -> Resolved a resolveVariable v ctx = resolveVariable' v (MapVal ctx) -resolveVariable' :: TemplateTarget a => Variable -> Val a -> [Doc a]+resolveVariable' :: TemplateTarget a+ => Variable -> Val a -> Resolved a resolveVariable' v val = case applyPipes (varPipes v) $ multiLookup (varParts v) val of- ListVal xs -> concatMap (resolveVariable' mempty) xs+ ListVal xs -> mconcat $ map (resolveVariable' mempty) xs SimpleVal d- | DL.isEmpty d -> []- | otherwise -> [removeFinalNl d]- MapVal _ -> ["true"]- NullVal -> []+ | DL.isEmpty d -> Resolved False []+ | otherwise -> Resolved True [removeFinalNl d]+ MapVal _ -> Resolved True ["true"]+ BoolVal True -> Resolved True ["true"]+ BoolVal False -> Resolved False ["false"]+ NullVal -> Resolved False [] removeFinalNl :: Doc a -> Doc a removeFinalNl DL.NewLine = mempty@@ -456,12 +433,13 @@ renderTemp :: forall a . TemplateTarget a => Template a -> Context a -> RenderState (Doc a) renderTemp (Literal t) _ = updateColumn t-renderTemp (Interpolate v) ctx = updateColumn $ mconcat $ resolveVariable v ctx+renderTemp (Interpolate v) ctx =+ case resolveVariable v ctx of+ Resolved _ xs -> updateColumn (mconcat xs) renderTemp (Conditional v ift elset) ctx =- let res = resolveVariable v ctx- in case res of- [] -> renderTemp elset ctx- _ -> renderTemp ift ctx+ case resolveVariable v ctx of+ Resolved False _ -> renderTemp elset ctx+ Resolved True _ -> renderTemp ift ctx renderTemp (Iterate v t sep) ctx = do xs <- withVariable v ctx (renderTemp t) sep' <- renderTemp sep ctx
+ test/boolean.test view
@@ -0,0 +1,13 @@+{ "foo": true,+ "bar": false+}+.+$foo$+$bar$+$if(foo)$XXX$else$YYY$endif$+$if(bar)$XXX$else$YYY$endif$+.+true+false+XXX+YYY
− test/keyval.txt
@@ -1,1 +0,0 @@-