Copyright | Copyright (C) 2009-2019 John MacFarlane |
---|---|
License | BSD3 |
Maintainer | John MacFarlane <jgm@berkeley.edu> |
Stability | alpha |
Portability | portable |
Safe Haskell | None |
Language | Haskell2010 |
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. 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 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
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 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.
- 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(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, withvariable
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
.
Filters
A filter transforms the value of a variable or partial. Filters are
specified using a slash (/
) between the variable name (or partial)
and the filter name. Example:
$for(name)$ $name/uppercase$ $endfor$ $for(metadata/pairs)$ - $it.key$: $it.value$ $endfor$ $employee:name()/uppercase$
Filters may be chained:
$for(employees/pairs)$ $it.key/alpha/uppercase$. $it.name$ $endfor$
Some filters take parameters:
|----------------------|------------| $for(employee)$ $it.name.first/uppercase/left 20 "| "$$it.name.salary/right 10 " | " " |"$ $endfor$ |----------------------|------------|
Currently the following filters are predefined:
pairs
: Converts a map or array to an array of maps, each withkey
andvalue
fields. If the original value was an array, thekey
will be the array index, starting with 1.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 charactersa..z
(mod 26). This can be used to get lettered enumeration from array indices. To get uppercase letters, chain withuppercase
.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 withuppercase
.left n "leftborder" "rightborder"
: Renders a textual value in a block of widthn
, 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 widthn
, aligned to the right, and has no effect on other values.center n "leftborder" "rightborder"
: Renders a textual value in a block of widthn
, aligned to the center, and has no effect on other values.
Synopsis
- renderTemplate :: (TemplateTarget a, ToContext a b) => Template a -> b -> Doc a
- compileTemplate :: (TemplateMonad m, TemplateTarget a) => FilePath -> Text -> m (Either String (Template a))
- compileTemplateFile :: TemplateTarget a => FilePath -> IO (Either String (Template a))
- applyTemplate :: (TemplateMonad m, TemplateTarget a, ToContext a b) => FilePath -> Text -> b -> m (Either String (Doc a))
- class Monad m => TemplateMonad m where
- getPartial :: FilePath -> m Text
- type TemplateTarget a = (Monoid a, IsString a, HasChars a, ToText a, FromText a)
- newtype Context a = Context {}
- data Val a
- class ToContext a b where
- class FromContext a b where
- data Template a
- data Doc a
- = Text Int a
- | Block Int [a]
- | VFill Int a
- | Prefixed Text (Doc a)
- | BeforeNonBlank (Doc a)
- | Flush (Doc a)
- | BreakingSpace
- | AfterBreak Text
- | CarriageReturn
- | NewLine
- | BlankLines Int
- | Concat (Doc a) (Doc a)
- | Empty
Documentation
renderTemplate :: (TemplateTarget a, ToContext a b) => Template a -> b -> Doc a Source #
Render a compiled template in a "context" which provides values for the template's variables.
compileTemplate :: (TemplateMonad m, TemplateTarget a) => FilePath -> Text -> m (Either String (Template a)) Source #
Compile a template. The FilePath parameter is used to determine a default path and extension for partials and may be left empty if partials are not used.
compileTemplateFile :: TemplateTarget a => FilePath -> IO (Either String (Template a)) Source #
Compile a template from a file. IO errors will be raised as exceptions; template parsing errors result in Left return values.
applyTemplate :: (TemplateMonad m, TemplateTarget a, ToContext a b) => FilePath -> Text -> b -> m (Either String (Doc a)) Source #
Compile a template and apply it to a context. This is
just a convenience function composing compileTemplate
and renderTemplate
. If a template will be rendered
more than once in the same process, compile it separately
for better performance.
class Monad m => TemplateMonad m where Source #
A TemplateMonad
defines a function to retrieve a partial
(from the file system, from a database, or using a default
value).
getPartial :: FilePath -> m Text Source #
Instances
TemplateMonad IO Source # | |
Defined in Text.DocTemplates.Internal | |
TemplateMonad Identity Source # | |
Defined in Text.DocTemplates.Internal |
A Context
defines values for template's variables.
Instances
Functor Context Source # | |
Foldable Context Source # | |
Defined in Text.DocTemplates.Internal fold :: Monoid m => Context m -> m # foldMap :: Monoid m => (a -> m) -> Context a -> m # foldr :: (a -> b -> b) -> b -> Context a -> b # foldr' :: (a -> b -> b) -> b -> Context a -> b # foldl :: (b -> a -> b) -> b -> Context a -> b # foldl' :: (b -> a -> b) -> b -> Context a -> b # foldr1 :: (a -> a -> a) -> Context a -> a # foldl1 :: (a -> a -> a) -> Context a -> a # elem :: Eq a => a -> Context a -> Bool # maximum :: Ord a => Context a -> a # minimum :: Ord a => Context a -> a # | |
Traversable Context Source # | |
ToContext a (Context a) Source # | |
Data a => Data (Context a) Source # | |
Defined in Text.DocTemplates.Internal gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Context a -> c (Context a) # gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Context a) # toConstr :: Context a -> Constr # dataTypeOf :: Context a -> DataType # dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Context a)) # dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Context a)) # gmapT :: (forall b. Data b => b -> b) -> Context a -> Context a # gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Context a -> r # gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Context a -> r # gmapQ :: (forall d. Data d => d -> u) -> Context a -> [u] # gmapQi :: Int -> (forall d. Data d => d -> u) -> Context a -> u # gmapM :: Monad m => (forall d. Data d => d -> m d) -> Context a -> m (Context a) # gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Context a -> m (Context a) # gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Context a -> m (Context a) # | |
Show a => Show (Context a) Source # | |
Semigroup (Context a) Source # | |
Monoid (Context a) Source # | |
(IsString a, TemplateTarget a) => FromYAML (Context a) Source # | |
TemplateTarget a => ToYAML (Context a) Source # | |
Defined in Text.DocTemplates.Internal | |
TemplateTarget a => ToJSON (Context a) Source # | |
Defined in Text.DocTemplates.Internal | |
(IsString a, TemplateTarget a) => FromJSON (Context a) Source # | |
A variable value.
Instances
Functor Val Source # | |
Foldable Val Source # | |
Defined in Text.DocTemplates.Internal fold :: Monoid m => Val m -> m # foldMap :: Monoid m => (a -> m) -> Val a -> m # foldr :: (a -> b -> b) -> b -> Val a -> b # foldr' :: (a -> b -> b) -> b -> Val a -> b # foldl :: (b -> a -> b) -> b -> Val a -> b # foldl' :: (b -> a -> b) -> b -> Val a -> b # foldr1 :: (a -> a -> a) -> Val a -> a # foldl1 :: (a -> a -> a) -> Val a -> a # elem :: Eq a => a -> Val a -> Bool # maximum :: Ord a => Val a -> a # | |
Traversable Val Source # | |
TemplateTarget a => FromContext a (Val a) Source # | |
ToContext a (Val a) Source # | |
Data a => Data (Val a) Source # | |
Defined in Text.DocTemplates.Internal gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> Val a -> c (Val a) # gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c (Val a) # dataTypeOf :: Val a -> DataType # dataCast1 :: Typeable t => (forall d. Data d => c (t d)) -> Maybe (c (Val a)) # dataCast2 :: Typeable t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c (Val a)) # gmapT :: (forall b. Data b => b -> b) -> Val a -> Val a # gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> Val a -> r # gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> Val a -> r # gmapQ :: (forall d. Data d => d -> u) -> Val a -> [u] # gmapQi :: Int -> (forall d. Data d => d -> u) -> Val a -> u # gmapM :: Monad m => (forall d. Data d => d -> m d) -> Val a -> m (Val a) # gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> Val a -> m (Val a) # gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> Val a -> m (Val a) # | |
Show a => Show (Val a) Source # | |
TemplateTarget a => FromYAML (Val a) Source # | |
TemplateTarget a => ToYAML (Val a) Source # | |
Defined in Text.DocTemplates.Internal | |
TemplateTarget a => ToJSON (Val a) Source # | |
Defined in Text.DocTemplates.Internal | |
(IsString a, TemplateTarget a) => FromJSON (Val a) Source # | |
class ToContext a b where Source #
Instances
(IsString a, TemplateTarget a) => ToContext a Value Source # | |
TemplateTarget a => ToContext a Bool Source # | |
TemplateTarget a => ToContext a a Source # | |
ToContext String String Source # | |
ToContext a b => ToContext a [b] Source # | |
ToContext a a => ToContext a (Doc a) Source # | |
ToContext a (Val a) Source # | |
ToContext a (Context a) Source # | |
ToContext String (Doc String) Source # | |
ToContext a b => ToContext a (Map Text b) Source # | |
class FromContext a b where Source #
The FromContext
class provides functions for extracting
values from Val
and Context
.
Instances
TemplateTarget a => FromContext a a Source # | |
FromContext String String Source # | |
FromContext a b => FromContext a [b] Source # | |
TemplateTarget a => FromContext a (Doc a) Source # | |
TemplateTarget a => FromContext a (Val a) Source # | |
A template.
Instances
Document, including structure relevant for layout.
Text Int a | Text with specified width. |
Block Int [a] | A block with a width and lines. |
VFill Int a | A vertically expandable block; when concatenated with a block, expands to height of block, with each line containing the specified text. |
Prefixed Text (Doc a) | Doc with each line prefixed with text. Note that trailing blanks are omitted from the prefix when the line after it is empty. |
BeforeNonBlank (Doc a) | Doc that renders only before nonblank. |
Flush (Doc a) | Doc laid out flush to left margin. |
BreakingSpace | A space or line break, in context. |
AfterBreak Text | Text printed only at start of line. |
CarriageReturn | Newline unless we're at start of line. |
NewLine | newline. |
BlankLines Int | Ensure a number of blank lines. |
Concat (Doc a) (Doc a) | Two documents concatenated. |
Empty |