Building a Quarto Extension
Building my First Quarto Shortcode Extension
Quarto is a scientific publishing system used to build documents, presentations, books and websites. Quarto extensions are a powerful way to extend the behavior of Quarto. These extensions are built using Lua, a language that I do not have much familiarity with but, building a simple extension is actually quite straighforward.
In this example I'll create a shortcode extension that allows you to add comments to a Quarto document. The comments can be turned on or off by using an environemnt variable defined in
Initialization
To initialize a Quarto shortcode extension, use the following command and enter a name for the extension (in this case comment)
1quarto create extension shortcode
The following folder structure will be created:
1comment
2├── _extensions
3│ └── comment
4│ ├── _extension.yml
5│ └── comment.lua
6├── .gitignore
7├── .luarc.json
8├── example.qmd
9└── README.md
I use VS Code for coding. When the quarto create
command is executed, VS Code opens a new window at the comment
folder, ready for development.
Building the Shortcode Extension
This Quarto extension allows an author to add comments to a Quarto document. It also adds a parameter to control if comments should included on not when a file is rendered. I've found this extension useful when adding instructional comments to a template.
The extension contains three shortcodes. One to start a comment block, one to end a comment block and one to add an inline comment.
The code concept is pretty simple. For comment blocks, surround the contents in a div with either the cmt
class (which can be used to control styling) when the comment variable is set to true, or display: none
when the comment variable is set to false. For inline comments surround the contents in a span with the cmt
class when the comment variable is set to true.
The comment.lua
file is shown below.
1-- start a comment block
2function comment_start(args, kwargs, meta)
3 cmt = meta['show_comments']
4 if cmt == nil or cmt == false then
5 return pandoc.RawBlock('html', "<div style = 'display: none;'>")
6 else
7 return pandoc.RawBlock('html', "<div class = 'cmt'>")
8 end
9end
10
11-- end a comment block
12function comment_stop()
13 return pandoc.RawBlock('html', "</div>")
14end
15
16-- inline comment
17function comment(args, kwargs, meta)
18 txt = pandoc.utils.stringify(args[1])
19 cmt = meta['show_comments']
20 if cmt == true then
21 return pandoc.RawInline('html', "<span class = 'cmt'>" .. txt .. "</span>")
22 end
23end
In addition to the comment.lua
file, an example.qmd
should be written (plus, in this case, a style.css
css file). If the example.qmd
file is previewed then it will automatically update as you edit comment.lua - a great way to ensure that your extension is working as expected. The example.qmd
and style.css
files for the comment extension are shown below:
1---
2title: "Comment Example"
3format:
4 html:
5 css: style.css
6show_comments: true
7---
8
9## Comments Test - Commented text within a paragraph
10
11This is some uncommented text.
12{{< comment_start >}}
13Here is a <b>comment</b> containing some instructional information.
14{{< comment_stop >}}
15Finally, some additional uncommented text.
16
17## Comments Test - Inline commenting
18
19This comment is an inline comment {{< comment "It can include instruction within the text" >}} followed by addititional text.
1.cmt {
2 color: #AAAA00
3}
To run in interactive / preview mode simple execute the command:
1quarto preview example.qmd
Exploring the Files
The example.qmd
file contains some example Quarto to test the extension along with a header. The header applies styles in the style.css
file (the .cmt class) and also defines a boolean parameter called show_comments
. The comment.lua
file contains three functions, each evaluating to a shortcode in Quarto. They are each explored below.
comment_start
This shortcode starts a comment block. It takes three arguments, args, kwargs and meta (arguments, named arguments and document/project-level metadata). In this function we only use the document/project-level metadata and run the command cmt = meta['show_comments']
to create a variable, cmt
holding the metadata show_comments
, a boolean. Generally, arguments passed to lua will be a list of pandoc inlines and require pandoc.utils.stringify()
to convert to strings. In our case, we are only passing a single value in show_comments
and it will be a boolean.
If the cmt
variable is false or missing then a div block is started with display: none
to hide it. If the cmt
variable is true then a div block is started with the .cmt style.
comment_stop
This simply closes the div initiated by comment_start()
.
comment_inline
This shortcode adds a comment inline. It works slightly differently to comment_start()
by reading the first argument from the shortcode using txt = pandoc.utils.stringify(args[1])
. It then defines the cmt
variable as in comment_start()
and, if cmt
is true, outputs the commented text, returning nothing if cmt
is not true.
Extending
In this example, I've included show_comments
as a document-level parameter. It could be substituted at the project-level in _quarto.yml
to manage a set of documents / website. It can also be included in _variables.yml
as a project-level variable in which case the line cmt = meta['show_comments']
would be replaced by cmt = meta['_quarto-vars']['show_comments']
in order to access variables defined in the _variables.yml
file.
Final Steps
Finally, a licence file was added (MIT licence), README was edited and the extension was pushed to GitHub at https://github.com/harveyl888/comment. To install to a quarto project simply run:
1quarto add harveyl888/comment
Conclusion
Quarto includes methods to build all manner of extensions for documents and projects. It is pretty simple to pick up lua, the scripting language for Quarto extensions, which means writing shortcodes should be fairly straightforward. The use of an example file along with Quarto's hot reload provides a powerful way to build up shortcodes in an interactive manner1.
-
Note: the lua
print
command can be used when developing in an interactive manner - it will print to the terminal whenever the example file is re-rendered. ↩︎