mirror of
https://github.com/jgm/pandoc.git
synced 2026-09-28 18:35:49 +00:00
233 lines
7.1 KiB
Markdown
233 lines
7.1 KiB
Markdown
---
|
|
author:
|
|
- John MacFarlane
|
|
date: 'November 21, 2021'
|
|
title: Creating Custom Pandoc Writers in Lua
|
|
---
|
|
|
|
# Introduction
|
|
|
|
If you need to render a format not already handled by pandoc,
|
|
or you want to change how pandoc renders a format,
|
|
you can create a custom writer using the [Lua] language.
|
|
Pandoc has a built-in Lua interpreter, so you needn't
|
|
install any additional software to do this.
|
|
|
|
[Lua]: https://www.lua.org
|
|
|
|
A custom writer is a Lua file that defines how to render the
|
|
document. Writers must define just a single function, named either
|
|
`Writer` or `ByteStringWriter`, which gets passed the document and
|
|
writer options, and then handles the conversion of the document,
|
|
rendering it into a string. This interface was introduced in
|
|
pandoc 2.17.2, with ByteString writers becoming available in
|
|
pandoc 3.0.
|
|
|
|
Pandoc also supports "classic" custom writers, where a Lua
|
|
function must be defined for each AST element type. Classic style
|
|
writers are *deprecated* and should be replaced with new-style
|
|
writers if possible.
|
|
|
|
# Writers
|
|
|
|
Custom writers using the new style must contain a global function
|
|
named `Writer` or `ByteStringWriter`. Pandoc calls this function
|
|
with the document and writer options as arguments, and expects the
|
|
function to return a UTF-8 encoded string.
|
|
|
|
``` lua
|
|
function Writer (doc, opts)
|
|
-- ...
|
|
end
|
|
```
|
|
|
|
Writers that do not return text but binary data should define a
|
|
function with name `ByteStringWriter` instead. The function must
|
|
still return a string, but it does not have to be UTF-8 encoded
|
|
and can contain arbitrary binary data.
|
|
|
|
If both `Writer` and `ByteStringWriter` functions are defined,
|
|
then only the `Writer` function will be used.
|
|
|
|
## Format extensions
|
|
|
|
Writers can be customized through format extensions, such as
|
|
`smart`, `citations`, or `hard_line_breaks`. The global
|
|
`Extensions` table indicates supported extensions with a
|
|
key. Extensions enabled by default are assigned a true value,
|
|
while those that are supported but disabled are assigned a false
|
|
value.
|
|
|
|
Example: A writer with the following global table supports the
|
|
extensions `smart`, `citations`, and `foobar`, with `smart` enabled and
|
|
the others disabled by default:
|
|
|
|
``` lua
|
|
Extensions = {
|
|
smart = true,
|
|
citations = false,
|
|
foobar = false
|
|
}
|
|
```
|
|
|
|
The users control extensions as usual, e.g., `pandoc -t
|
|
my-writer.lua+citations`. The extensions are accessible through
|
|
the writer options' `extensions` field, e.g.:
|
|
|
|
``` lua
|
|
function Writer (doc, opts)
|
|
print(
|
|
'The citations extension is',
|
|
opts.extensions:includes 'citations' and 'enabled' or 'disabled'
|
|
)
|
|
-- ...
|
|
end
|
|
```
|
|
|
|
## Default template
|
|
|
|
The default template of a custom writer is defined by the return
|
|
value of the global function `Template`. Pandoc uses the default
|
|
template for rendering when the user has not specified a template,
|
|
but invoked with the `-s`/`--standalone` flag.
|
|
|
|
The `Template` global can be left undefined, in which case pandoc
|
|
will throw an error when it would otherwise use the default
|
|
template.
|
|
|
|
## Example: modified Markdown writer
|
|
|
|
Writers have access to all modules described in the [Lua filters
|
|
documentation][]. This includes `pandoc.write`, which can be used
|
|
to render a document in a format already supported by pandoc. The
|
|
document can be modified before this conversion, as demonstrated
|
|
in the following short example. It renders a document as GitHub
|
|
Flavored Markdown, but always uses fenced code blocks, never
|
|
indented code.
|
|
|
|
``` lua
|
|
function Writer (doc, opts)
|
|
local filter = {
|
|
CodeBlock = function (cb)
|
|
-- only modify if code block has no attributes
|
|
if cb.attr == pandoc.Attr() then
|
|
local delimited = '```\n' .. cb.text .. '\n```'
|
|
return pandoc.RawBlock('markdown', delimited)
|
|
end
|
|
end
|
|
}
|
|
return pandoc.write(doc:walk(filter), 'gfm', opts)
|
|
end
|
|
|
|
Template = pandoc.template.default 'gfm'
|
|
```
|
|
|
|
[Lua filters documentation]: https://pandoc.org/lua-filters.html
|
|
|
|
## Reducing boilerplate with `pandoc.scaffolding.Writer`
|
|
|
|
The `pandoc.scaffolding.Writer` structure is a custom writer scaffold
|
|
that serves to avoid common boilerplate code when defining a custom
|
|
writer. The object can be used as a function and allows to skip details
|
|
like metadata and template handling, requiring only the render functions
|
|
for each AST element type.
|
|
|
|
The value of `pandoc.scaffolding.Writer` is a function that should
|
|
usually be assigned to the global `Writer`:
|
|
|
|
``` lua
|
|
Writer = pandoc.scaffolding.Writer
|
|
```
|
|
|
|
The render functions for Block and Inline values can then be added
|
|
to `Writer.Block` and `Writer.Inline`, respectively. The functions
|
|
are passed the element and the WriterOptions.
|
|
|
|
``` lua
|
|
Writer.Inline.Str = function (str)
|
|
return str.text
|
|
end
|
|
Writer.Inline.SoftBreak = function (_, opts)
|
|
return opts.wrap_text == "wrap-preserve"
|
|
and cr
|
|
or space
|
|
end
|
|
Writer.Inline.LineBreak = cr
|
|
|
|
Writer.Block.Para = function (para)
|
|
return {Writer.Inlines(para.content), pandoc.layout.blankline}
|
|
end
|
|
```
|
|
|
|
The render functions must return a string, a pandoc.layout *Doc*
|
|
element, or a list of such elements. In the latter case, the
|
|
values are concatenated as if they were passed to
|
|
`pandoc.layout.concat`. If the value does not depend on the input,
|
|
a constant can be used as well.
|
|
|
|
The tables `Writer.Block` and `Writer.Inline` can be used as
|
|
functions; they apply the right render function for an element of
|
|
the respective type. E.g., `Writer.Block(pandoc.Para 'x')` will
|
|
delegate to the `Writer.Para` render function and will return the
|
|
result of that call.
|
|
|
|
Similarly, the functions `Writer.Blocks` and `Writer.Inlines` can
|
|
be used to render lists of elements, and `Writer.Pandoc` renders
|
|
the document's blocks. The function `Writer.Blocks` can take a
|
|
separator as an optional second argument, e.g.,
|
|
`Writer.Blocks(blks, pandoc.layout.cr)`; the default block
|
|
separator is `pandoc.layout.blankline`.
|
|
|
|
All predefined functions can be overwritten when needed.
|
|
|
|
The resulting Writer uses the render functions to handle metadata
|
|
values and converts them to template variables. The template is
|
|
applied automatically if one is given.
|
|
|
|
# Classic style
|
|
|
|
A writer using the classic style defines rendering functions for
|
|
each element of the pandoc AST. Note that this style is
|
|
*deprecated* and may be removed in later versions.
|
|
|
|
For example,
|
|
|
|
``` lua
|
|
function Para(s)
|
|
return "<paragraph>" .. s .. "</paragraph>"
|
|
end
|
|
```
|
|
|
|
## Template variables
|
|
|
|
New template variables can be added, or existing ones
|
|
modified, by returning a second value from function `Doc`.
|
|
|
|
For example, the following will add the current date in
|
|
variable `date`, unless `date` is already defined as either a
|
|
metadata value or a variable:
|
|
|
|
``` lua
|
|
function Doc (body, meta, vars)
|
|
vars.date = vars.date or meta.date or os.date '%B %e, %Y'
|
|
return body, vars
|
|
end
|
|
```
|
|
|
|
## Changes in pandoc 3.0
|
|
|
|
Custom writers were reworked in pandoc 3.0. For technical reasons,
|
|
the global variables `PANDOC_DOCUMENT` and `PANDOC_WRITER_OPTIONS`
|
|
are set to the empty document and default values, respectively.
|
|
The old behavior can be restored by adding the following snippet,
|
|
which turns a classic into a new style writer.
|
|
|
|
``` lua
|
|
function Writer (doc, opts)
|
|
PANDOC_DOCUMENT = doc
|
|
PANDOC_WRITER_OPTIONS = opts
|
|
loadfile(PANDOC_SCRIPT_FILE)()
|
|
return pandoc.write_classic(doc, opts)
|
|
end
|
|
```
|