BSD-3-Clause licensed by John MacFarlane
Maintained by [email protected]
This version can be pinned in stack with:doctemplates-0.9@sha256:eb0bf957ba8571746f36f07ceca43d311a0e390a9fdcfde6909f9f9be85b3b28,3125

Module documentation for 0.9

doctemplates

CI tests

This is the text templating system used by pandoc. Its basic function is to fill variables in a template. Variables are provided by a “context.” Any instance of the ToContext typeclass (such as an aeson Value) can serve as the context, or a Context value can be constructed manually.

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. 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 underlying string type). If Doc values are used in the context, rendered documents will be able to wrap flexibly on breaking spaces. This feature makes doctemplates more suitable than other template engines for plain-text formats (like Markdown).

Unlike the various HTML-centered template engines, doctemplates is output-format agnostic, so no automatic escaping is done on interpolated values. Values are assumed to be escaped properly in the Context.

Example of use

{-# LANGUAGE OverloadedStrings #-}
import Data.Text (Text)
import qualified Data.Text.IO as T
import Data.Aeson
import Text.DocTemplates
import Text.DocLayout (render)

data Employee = Employee { firstName :: String
                         , lastName  :: String
                         , salary    :: Maybe Int }
instance ToJSON Employee where
  toJSON e = object [ "name" .= object [ "first" .= firstName e
                                       , "last"  .= lastName e ]
                    , "salary" .= salary e ]

template :: Text
template = "$for(employee)$Hi, $employee.name.first$. $if(employee.salary)$You make $employee.salary$.$else$No salary data.$endif$$sep$\n$endfor$"

main :: IO ()
main = do
  res <- compileTemplate "mytemplate.txt" template
  case res of
         Left e    -> error e
         Right t   -> T.putStrLn $ render Nothing $ renderTemplate t $ object
                        ["employee" .=
                          [ Employee "John" "Doe" Nothing
                          , Employee "Omar" "Smith" (Just 30000)
                          , Employee "Sara" "Chen" (Just 60000) ]
                        ]

Delimiters

To mark variables and control structures in the template, either $$ or ${} may be used as delimiters. The styles may also be mixed in the same template, but the opening and closing delimiter must match in each case. The opening delimiter may be followed by one or more spaces or tabs, which will be ignored. The closing delimiter may be followed by one or more spaces or tabs, which will be ignored.

To include a literal $ in the document, use $$.

Comments

Anything between the sequence $-- and the end of the line will be treated as a comment and omitted from the output.

Interpolated variables

A slot for an interpolated variable is a variable name surrounded by matched delimiters. Variable names must begin with a letter and can contain letters, numbers, _, -, and .. The keywords it, if, else, endif, for, sep, and endfor may not be used as variable names. Examples:

$foo$
$foo.bar.baz$
$foo_bar.baz-bim$
$ foo $
${foo}
${foo.bar.baz}
${foo_bar.baz-bim}
${ foo }

The values of variables are determined by the Context that is passed as a parameter to renderTemplate. So, for example, title will return the value of the title field, and employee.salary will return the value of the salary field of the object that is the value of the employee field.

  • If the value of the variable is simple value, it will be 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.

When a Context is derived from an aeson (JSON) Value, the following conversions are done:

  • If the value is a number, it will be rendered as an integer if possible, otherwise as a floating-point number.

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(foo)$bar$endif$

$if(foo)$
  $foo$
$endif$

$if(foo)$
part one
$else$
part two
$endif$

${if(foo)}bar${endif}

${if(foo)}
  ${foo}
${endif}

${if(foo)}
${ foo.bar }
${else}
no foo!
${endif}

The keyword elseif may be used to simplify complex nested conditionals. Thus

$if(foo)$
XXX
$elseif(bar)$
YYY
$else$
ZZZ
$endif$

is equivalent to

$if(foo)$
XXX
$else$
$if(bar)$
YYY
$else$
ZZZ
$endif$
$endif$

For loops

A for loop begins with for(variable) (enclosed in matched delimiters) and ends with endfor (enclosed in matched delimiters.

  • If variable is an array, the material inside the loop will be evaluated repeatedly, with variable being set to each value of the array in turn, and concatenated.
  • If variable is a map, the material inside will be set to the map.
  • If the value of the associated variable is not an array or a map, a single iteration will be performed on its value.

Examples:

$for(foo)$$foo$$sep$, $endfor$

$for(foo)$
  - $foo.last$, $foo.first$
$endfor$

${ for(foo.bar) }
  - ${ foo.bar.last }, ${ foo.bar.first }
${ endfor }

$for(mymap)$
$it.name$: $it.office$
$endfor$

You may optionally specify a separator between consecutive values using sep (enclosed in matched delimiters). The material between sep and the endfor is the separator.

${ for(foo) }${ foo }${ sep }, ${ endfor }

Instead of using variable inside the loop, the special anaphoric keyword it may be used.

${ for(foo.bar) }
  - ${ it.last }, ${ it.first }
${ endfor }

Partials

Partials (subtemplates stored in different files) may be included using the syntax

${ boilerplate() }

The partials are obtained using getPartial from the TemplateMonad class. This may be implemented differently in different monads. The path passed to getPartial is computed on the basis of the original template path (a parameter to compileTemplate) and the partial’s name. The partial’s name is substituted for the base name of the original template path (leaving the original template’s extension), unless the partial has an explicit extension, in which case this is kept. So, with the TemplateMonad instance for IO, partials will be sought in the directory containing the main template, and will be assumed to have the extension of the main template.

Partials may optionally be applied to variables using a colon:

${ date:fancy() }

${ articles:bibentry() }

If articles is an array, this will iterate over its values, applying the partial bibentry() to each one. So the second example above is equivalent to

${ for(articles) }
${ it:bibentry() }
${ endfor }

Note that the anaphoric keyword it must be used when iterating over partials. In the above examples, the bibentry partial should contain it.title (and so on) instead of articles.title.

Final newlines are omitted from included partials.

Partials may include other partials. If you exceed a nesting level of 50, though, in resolving partials, the literal (loop) will be returned, to avoid infinite loops.

A separator between values of an array may be specified in square brackets, immediately after the variable name or partial:

${months[, ]}$

${articles:bibentry()[; ]$

The separator in this case is literal and (unlike with sep in an explicit for loop) cannot contain interpolated variables or other template directives.

Nesting

To ensure that content is “nested,” that is, subsequent lines indented, use the ^ directive:

$item.number$  $^$$item.description$ ($item.price$)

In this example, if item.description has multiple lines, they will all be indented to line up with the first line:

00123  A fine bottle of 18-year old
       Oban whiskey. ($148)

To nest multiple lines to the same level, align them with the ^ directive in the template. For example:

$item.number$  $^$$item.description$ ($item.price$)
               (Available til $item.sellby$.)

will produce

00123  A fine bottle of 18-year old
       Oban whiskey. ($148)
       (Available til March 30, 2020.)

If a variable occurs by itself on a line, preceded by whitespace and not followed by further text or directives on the same line, and the variable’s value contains multiple lines, it will be nested automatically.

Breakable spaces

When rendering to a Doc, a distinction can be made between breakable and unbreakable spaces. Normally, spaces in the template itself (as opposed to values of the interpolated variables) are not breakable, but they can be made breakable in part of the template by using the ~ keyword (ended with another ~).

$~$This long line may break if the document is rendered
with a short line length.$~$

The ~ keyword has no effect when rendering to Text or String.

Pipes

A pipe transforms the value of a variable or partial. Pipes are specified using a slash (/) between the variable name (or partial) and the pipe name. Example:

$for(name)$
$name/uppercase$
$endfor$

$for(metadata/pairs)$
- $it.key$: $it.value$
$endfor$

$employee:name()/uppercase$

Pipes may be chained:

$for(employees/pairs)$
$it.key/alpha/uppercase$. $it.name$
$endfor$

Some pipes take parameters:

|----------------------|------------|
$for(employee)$
$it.name.first/uppercase/left 20 "| "$$it.name.salary/right 10 " | " " |"$
$endfor$
|----------------------|------------|

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.

  • lowercase: Converts text to lowercase.

  • length: Returns the length of the value: number of characters for a textual value, number of elements for a map or array.

  • reverse: Reverses a textual value or array, and has no effect on other values.

  • chomp: Removes trailing newlines (and breakable space).

  • nowrap: Disables line wrapping on breakable spaces.

  • alpha: Converts textual values that can be read as an integer into lowercase alphabetic characters a..z (mod 26). This can be used to get lettered enumeration from array indices. To get uppercase letters, chain with uppercase.

  • roman: Converts textual values that can be read as an integer into lowercase roman numerials. This can be used to get lettered enumeration from array indices. To get uppercase roman, chain with uppercase.

  • left n "leftborder" "rightborder": Renders a textual value in a block of width n, aligned to the left, with an optional left and right border. Has no effect on other values. This can be used to align material in tables. Widths are positive integers indicating the number of characters. Borders are strings inside double quotes; literal " and \ characters must be backslash-escaped.

  • right n "leftborder" "rightborder": Renders a textual value in a block of width n, aligned to the right, and has no effect on other values.

  • center n "leftborder" "rightborder": Renders a textual value in a block of width n, aligned to the center, and has no effect on other values.

Changes

doctemplates

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

  • New constructors for Filter: FirstItem, LastItem, Rest, AllButLast [API change].

0.8.1

  • Depend on doclayout 0.3, which adds an additional method on the HasChars class. This fixes some stack overflows in rendering very long lines.

0.8

  • Change Filter data type to Pipe. Use the nomenclature of “pipe” instead of “filter” to avoid confusion in pandoc between two notions of filter. Otherwise everything works the same.

0.7.2

  • Add nowrap filter.

  • Improve alpha, roman, uppercase, lowercase filters so they apply recursively within a list or map.

  • Allow for loops to bind map value. In this case there is no iteration, but the anophoric variable ‘it’ is assigned, which may help in using filters that destructure a string into a map (if we add any).

0.7.1

  • Add chomp filter.

  • Allow filters to be applied to output of partials.

0.7

  • Add haddock Makefile target, which regenerates haddocks from README and tests the code example.

  • Remove BreakingSpace constructor on Template. Now we use doclayout BreakingSpace inside a Literal.

  • Add instance for ToContext a (Doc a).

  • Get benchmarks compiling again.

  • Use (doclayout) Doc internally and for rendered output.

    • TemplateTarget is now a type constraint synonym, not a regular typeclass.
    • Constraint on compileTemplate and applyTemplate simplified using TemplateTarget.
    • DocTemplates reexports Text.DocLayout.Doc.
    • The Literal costructor of Template now takes a Doc a rather than an a.
    • The SimpleVal constructor of Val now takes a Doc a rather than an a.
    • renderTemplate now returns a Doc a rather than an a. (This value can be converted to an a using render Nothing.)
  • Remove fromText from TemplateTarget. Now we use fromString from Data.String.

  • Parameterize Template on underlying stringlike type.

  • Improved behavior of partials.

  • Improve indent functions: don’t drop final newline.

  • Allow blank lines in nested section.

  • Indent for Text/String: don’t indent empty lines.

  • Additional tests and documentation about nesting.

  • Render items in for loop before separator. Otherwise we throw off column calculation.

  • Remove +-reflow; replace with toggle $~$.

  • Remove pNewline parser; it isn’t needed now.

  • Remove +-nest.

  • Fix nest parsing bug.

  • Improve nesting.

    • Change Nested constructor for Template so it doesn’t take a parameter.
    • Nesting level is now determined dynamically at render time rather than at compile time. This gives much better results when nesting occurs after template directives. Benchmarks show a slight penalty in performance (from 3.5ms to 3.1ms in rendering), but it’s not too much.
  • Add filters. Filters transform the value of a variable, e.g. changing a map into an array of key/value pairs. Closes #5.

    • Internal: Add Filter type and [Filter] parameter on Variable.
    • Remove unVariable; now we have varParts and varFilters.
    • Document filters in README.md.
    • Implement filters.
    • Add tests.
  • Add ToYAML, FromYAML instances for Context, Val.

0.6.2

  • Remove unnecessary TemplateTarget constraints on ToContext instances.

  • Add ToContext instance for Map Text a.

  • Add Data, Typeable instances for Context and Val.

0.6.1

  • Indent bare partials.

0.6

  • Add +nest/-nest keywords.

  • Add +reflow/-reflow keywords.

  • Add Nested constructor to Template, remove Indented and Indented parameter for Interpolate.

  • More expansive description of library.

0.5.1

  • Add elseif keyword.

  • Improve compile error source locations with partials.

  • Handle templates that don’t end in newlines. Previously this caused problems in some cases.

0.5

  • Add toText method to TemplateTarget class.

  • Add String and Lazy Text instances for TemplateTarget.

  • Swap Parameters in ToContext (so that the first parameter for both ToContext and FromContext refers to the parameter of Context).

  • Add toVal method to ToContext.

  • Default instance definition for toContext in terms of toVal, so that defining toVal is sufficient.

  • Add instances for ToContext and FromContext.

  • Remove valueToContext. Add ToJSON, FromJSON instances for Context and Val instead.

  • isEmpty: For Doc, treat Text 0 _ as empty. Also Concat x y when x and y are empty. This differs from isEmpty in DocLayout itself, which only applies to Empty.

  • Code cleanup.

0.4

  • Split into three modules. Main module only exports an opaque version of the Template type. Import Internal if you need to manipulate a Template.

  • Add Context type, parameterized on the underlying content’s type.

  • Add Val type.

  • Add valueToContext for converting an Aeson Value to a Context.

  • Make renderTemplate and applyTemplate polymorphic in both context and target. Context parameter is now any instance of ToContext (instead of ToJSON). Result is now any instance of TemplateTarget.

  • Change type of getPartial in TemplateMonad so it runs in the TemplateMonad instance, not the Parser. Return a simple value rather than an Either; error handling can vary with the monad.

  • Remove TemplatePart. Template is now an algebraci data type, not a list of TemplateParts.

  • Add an Indented type to indicate indentation for interpolated variables.

  • Improve architecture, doing more at compile time.

  • Depend on doclayout. Context can be parameterized on a doclayout Doc type, allowing intelligent reflowing of content.

  • Remove single final newline in interpolated variable.

  • Remove final newline from partial.

  • Don’t iterate when the variable evaluates to NullVal.

  • Only indented interpolated variables if by themselves on line.

  • Add Indented parameter to Interpolate constructor.

  • Update documentation and haddocks.

  • Add benchmark.

0.3.0.1

  • Bump lower bound on base to 4.9, drop support for ghc 7.10.

  • Add needed import for older base versions.

  • Add test.hs to repository.

0.3

  • Note that all of the changes to template syntax described below are backwards compatible, and all old pandoc templates should continue to work as before.

  • Allow ${...} style delimiters around variables and directives, in addition to $...$. Allow space around the delimiters.

  • Support $it$ as a variable for the current value in an iteration. (The old method, where the containing variable name is used, still works.)

  • Support partials (subtemplates defined in different files).

  • Interpolated array variables now have all elements rendered, concatenated, with an optional separator that can be specified using a new bracketed syntax.

  • Remove TemplateTarget class. It was pointless; the calling program can just do these trivial transformations. Avoids dependencies on bytestring, blaze-html, blaze-markup.

  • Change type of renderTemplate and applyTemplate to produce a Text, instead of being polymorphic.

  • Changed type of compileTemplate: it now takes a template path and the template contents, and returns either a template or an error. It runs in an instance of TemplateMonad, which is an abstraction around different ways of getting partials. (For example, in IO we can get partials by reading them from a file system, but in a web application one might want to obtain them from the database or have a set of them baked in.)

  • Remove varListToJSON.

  • Changed the architecture: Template is no longer just a newtype around a function, but a list of TemplateParts.

  • Added a newtype for Variable.

  • Improved documentation in README.md.

  • Added a new test framework and much more extensive tests.