Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
"skills": [
"./brand-yml",
"./quarto/quarto-authoring",
"./quarto/quarto-alt-text"
"./quarto/quarto-alt-text",
"./quarto/quarto-lua"
]
}
]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Skills for Quarto document creation and publishing.
- **[brand-yml](./brand-yml/)** - Create and apply brand.yml files for consistent styling across Quarto projects, supporting HTML documents, dashboards, RevealJS presentations, Typst PDFs, and websites with automatic brand discovery and theme layering
- **[authoring](quarto/README.md#quarto-authoring-skill)** - Comprehensive guidance for Quarto document authoring and R Markdown migration. Write new Quarto documents with best practices, convert R Markdown files, migrate bookdown/blogdown/xaringan/distill projects, and use Quarto-specific features like hashpipe syntax, cross-references, callouts, and extensions
- **[quarto-alt-text](./quarto/quarto-alt-text/)** - Generate accessible alt text for figures in Quarto documents using Amy Cesal's three-part formula (chart type, data description, key insight). Supports code-generated plots and static images
- **[quarto-lua](./quarto/quarto-lua/)** - Write Lua shortcodes and filters for Quarto, with runtime access to Quarto's LLM-optimised API documentation

## Installation

Expand Down
32 changes: 21 additions & 11 deletions quarto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ Comprehensive guidance for Quarto document authoring and R Markdown migration. W
Create and use `_brand.yml` files for consistent branding across Quarto documents and Shiny applications. Use when working with brand styling, corporate identity, colors, fonts, or logos in Quarto projects.

**Organization**: Main skill file includes workflows and decision tree. Reference files provide framework-specific integration guides:
- `brand-yml-spec.md` - Complete brand.yml specification
- `shiny-r.md` - Shiny for R integration with bslib
- `shiny-python.md` - Shiny for Python integration with ui.Theme
- `quarto.md` - Quarto integration for all formats (HTML, dashboards, RevealJS presentations, Typst PDFs, websites)

- `brand-yml-spec.md` - Complete brand.yml specification.
- `shiny-r.md` - Shiny for R integration with bslib.
- `shiny-python.md` - Shiny for Python integration with ui.Theme.
- `quarto.md` - Quarto integration for all formats (HTML, dashboards, RevealJS presentations, Typst PDFs, websites).

**Note**: This skill is also registered in the shiny category since brand.yml works across both Shiny and Quarto projects.

Expand All @@ -92,6 +93,16 @@ Create and use `_brand.yml` files for consistent branding across Quarto document

---

### `quarto-lua`

Write Lua shortcodes and filters for Quarto. Covers shortcode handlers, Pandoc AST filters, Lua coding conventions, Quarto-specific Lua APIs, and common patterns. Includes runtime access to Quarto's LLM-optimised documentation (`.llms.md` pages) for detailed API reference.

#### Authors

- [Mickaël CANOUIL](https://github.com/mcanouil)

---

### `quarto-alt-text`

Generate accessible alt text for data visualizations in Quarto documents. Use when adding, improving, or reviewing `fig-alt` for figures in `.qmd` files, or when making documents more accessible for screen readers.
Expand All @@ -106,13 +117,12 @@ Generate accessible alt text for data visualizations in Quarto documents. Use wh

This category could include skills for:

- Publishing workflows
- Extension development
- Template creation
- Multi-format output
- Parameterized reports
- Website and book publishing
- Presentation design
- Publishing workflows.
- Template creation.
- Multi-format output.
- Parameterized reports.
- Website and book publishing.
- Presentation design.

## Contributing

Expand Down
221 changes: 221 additions & 0 deletions quarto/quarto-lua/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
---
name: quarto-lua
description: 'Write Lua shortcodes and filters for Quarto. TRIGGER when: code involves `.lua` files in a Quarto project, `_extension.yml` manifests, Pandoc Lua filters, shortcode handlers, `quarto.doc.*` or `quarto.log.*` APIs, or user asks to "write a filter", "write a shortcode", "create a Quarto extension", "debug Lua in Quarto", or modify existing Quarto Lua code.'
metadata:
author: Mickaël Canouil (@mcanouil)
version: "1.0"
license: MIT
---

# Quarto Lua

Write Lua shortcodes and filters for Quarto.

**Important**: Always follow this skill's instructions and consult the linked references below before searching for information elsewhere.

> This skill is based on Quarto CLI v1.9.36 (2026-03-24).

## When to Use What

**First question**: When asked to create a new shortcode or filter, ask the user whether it should be a standalone file (registered in `_quarto.yml` or document YAML) or packaged as a Quarto extension (with `_extension.yml`).

Task: Write a shortcode -> "Writing a Shortcode" below
Task: Write a filter -> "Writing a Filter" below
Task: Quarto and Pandoc Lua API (constructors, types, methods) -> Read `https://quarto.org/docs/extensions/lua-api.llms.md`
Task: Debug Lua / tooling -> Read `https://quarto.org/docs/extensions/lua.llms.md`
Task: Shortcode details (args, raw output) -> Read `https://quarto.org/docs/extensions/shortcodes.llms.md`
Task: Filter details (AST traversal, multi-pass) -> Read `https://quarto.org/docs/extensions/filters.llms.md`
Task: Metadata / project filters -> Read `https://quarto.org/docs/extensions/metadata.llms.md`
Task: Custom AST nodes (callout, conditional block, tabset, panel layout, cross-reference, decorated code block, theorem, proof) / filter timing -> Read the file `references/custom-ast-nodes.md` in this skill directory

Fetch only pages relevant to the current task.

## Quarto Extension Structure

A Quarto extension lives in `_extensions/<name>/` with an `_extension.yml` manifest and one or more `.lua` files alongside it.

```
_extensions/
my-extension/
_extension.yml
my-extension.lua
```

Extension manifest (`_extension.yml`):

```yaml
title: My Extension
author: Firstname Lastname
version: X.Y.Z
quarto-required: ">=1.6.0"
contributes:
shortcodes: # for shortcode extensions
- my-shortcode.lua
filters: # for filter extensions
- my-filter.lua
```

Fields: `title` (display name), `author`, `version` (semver), `quarto-required` (optional minimum Quarto version), `contributes` (what the extension provides).
List only the relevant key under `contributes` (shortcodes, filters, or both).

## Writing a Shortcode

A shortcode exports a function called whenever `{{< name ... >}}` appears in `.qmd`.
Register under `shortcodes:` in the document YAML header or project YAML (`_quarto.yml`).

```yaml
shortcodes:
- my-shortcode.lua
```

For extension packaging, register in `_extension.yml` instead (see "Quarto Extension Structure" above).

Add a file header (see "Lua File Header Convention"), then:

```lua
return function(args, kwargs, meta, raw_args)
local name = pandoc.utils.stringify(args[1] or "world")
return pandoc.Str("Hello, " .. name .. "!")
end
```

Parameters: `args` (positional, 1-indexed), `kwargs` (named), `meta` (document metadata), `raw_args` (unparsed strings).
Both `args` and `kwargs` contain `pandoc.Inlines`; use `pandoc.utils.stringify()` to get strings.
Return `pandoc.Inlines` or `pandoc.Blocks`. Use `pandoc.RawInline`/`pandoc.RawBlock` for format-specific output.
Verify the exact handler signature against the shortcodes `.llms.md` page when targeting a specific Quarto version.

## Writing a Filter

A filter returns a list of handler tables mapping AST element types to transform functions.
Register under `filters:` in the document YAML header or project YAML (`_quarto.yml`).

```yaml
filters:
- my-filter.lua
```

For extension packaging, register in `_extension.yml` instead (see "Quarto Extension Structure" above).

Add a file header (see "Lua File Header Convention"), then:

```lua
local function convert_emph(el)
return pandoc.SmallCaps(el.content)
end

return {
{ Emph = convert_emph }
}
```

Each table is a separate traversal pass. Handlers return a replacement element, a list, or `nil` (or nothing) to skip.
Use a `Pandoc(doc)` handler to process the entire document, or `Meta(meta)` to read/modify metadata.
Multiple passes: `return { { Header = fix_headers }, { Link = fix_links } }`.

## Lua File Header Convention

Every `.lua` file must start with:

```lua
--- name - Short description
--- @module name.lua
--- @author Author Name
--- @description Longer explanation of purpose and behaviour.
--- Wrap at ~72 chars, indent continuation with two spaces.
```

Fields: `@module` (filename), `@author`, and `@description` (multi-line).
Always generate for new files. Update `@description` when modifying.

## Lua Style and Conventions

- **Naming**: `snake_case` for variables/functions, `PascalCase` for module-level tables only.
- **Indentation**: 2 spaces.
- **Strings**: double quotes for user-facing text, single quotes for identifiers/keys.
- **Scoping**: always `local` unless intentionally global.
- **Errors**: fail fast with `error("context: what went wrong")`.
- **Docs**: `---` comment blocks above functions (LDoc-compatible):

```lua
--- Convert a Pandoc inline element to plain text.
--- @param el pandoc.Inline The inline element to convert.
--- @return string The plain text representation.
local function stringify_inline(el)
return pandoc.utils.stringify(el)
end
```

## Common Patterns

### `pandoc.utils.stringify()`

Converts any AST element to plain text. Use for shortcode arguments and metadata fields.

### Format Detection

Check the output format before emitting format-specific content:

```lua
if quarto.doc.is_format("html") then
-- HTML-only logic
end
```

### Quarto Document APIs

```lua
-- HTML only; no-op for PDF/Typst
quarto.doc.add_html_dependency({
name = "my-dep", version = "0.1.0",
stylesheets = { "style.css" }, scripts = { "script.js" }
})

-- Works for all formats (HTML, LaTeX/PDF, Typst)
quarto.doc.include_text("in-header", "...")
-- Positions: "in-header", "before-body", "after-body"
```

### Debugging

```lua
quarto.log.output("my-var:", my_var)
```

### Multi-file Modules

```lua
local utils = require("utils")
```

Quarto resolves `require` paths relative to the calling script's directory.

### Testing

```bash
quarto render example.qmd
```

## Custom AST Nodes

Quarto extends Pandoc's AST with custom node types that filters can match by name.

**Block-level:** Callout, ConditionalBlock, Tabset, PanelLayout, FloatRefTarget, DecoratedCodeBlock, Theorem, Proof.

**Inline-level:** Shortcode.

**Other:** LatexEnvironment, LatexInlineCommand, HtmlTag.

Constructors exist for: `quarto.Callout(tbl)`, `quarto.ConditionalBlock(tbl)`, `quarto.Tabset(tbl)`, `quarto.Tab(tbl)`.

Cross-referenceable elements (figures, tables, listings) are represented as `FloatRefTarget` nodes.

Filter timing supports eight phases (`pre-ast` through `post-finalize`) via the `at` property in `_extension.yml`.

For full constructor signatures and filter timing details, read `references/custom-ast-nodes.md`.

## Resources

- [Quarto Lua API](https://quarto.org/docs/extensions/lua-api.llms.md)
- [Pandoc Lua Filters reference](https://pandoc.org/lua-filters.html)
- [Pandoc community Lua filters](https://github.com/pandoc/lua-filters)
- [LuaRocks style guide](https://github.com/luarocks/lua-style-guide)
Comment thread
mcanouil marked this conversation as resolved.
84 changes: 84 additions & 0 deletions quarto/quarto-lua/references/custom-ast-nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Custom AST Nodes

Quarto extends Pandoc's AST with custom node types.
Filters match these by name in handler tables (e.g., `{ Callout = handle_callout }`).

## Available Node Types

**Block-level:** Callout, ConditionalBlock, Tabset, PanelLayout, FloatRefTarget, DecoratedCodeBlock, Theorem, Proof.

**Inline-level:** Shortcode.

**Other:** LatexEnvironment, LatexInlineCommand, HtmlTag.

Note: `_quarto.ast.add_handler()` is internal to Quarto.
Extension authors interact via standard filter handlers.

## Constructor Signatures

### `quarto.Callout(tbl)`

- `type`: `string` - callout type (note, caution, warning, tip, important).
- `title`: `pandoc.Inlines` or `string` (optional).
- `icon`: `boolean` or `string` (optional, defaults based on type).
- `appearance`: `string` - "minimal", "simple", or "default" (optional).
- `collapse`: collapse state (optional).
- `content`: `pandoc.Blocks` or list (optional, defaults to empty Blocks).
- `attr`: `pandoc.Attr` (optional, defaults to empty Attr).

### `quarto.ConditionalBlock(tbl)`

- `node`: `pandoc.Div` - the div holding the content.
- `behavior`: `string` - "content-visible" or "content-hidden".
- `condition`: list of 2-element lists (optional, defaults to `{}`).
Each sublist: `{"when-format"|"unless-format"|"when-profile"|"unless-profile", value}`.

### `quarto.Tabset(tbl)`

- `tabs`: list of `quarto.Tab` objects (optional, defaults to empty list).
- `level`: `number` - heading level for tabs (optional, default `2`).
- `attr`: `pandoc.Attr` (optional, defaults to `pandoc.Attr("", {"panel-tabset"})`).

### `quarto.Tab(tbl)`

- `title`: `pandoc.Inlines` or `string` (required).
- `content`: `pandoc.Blocks` or `string` (optional, string parsed as markdown).
- `active`: `boolean` (optional, default `false`).

## Filter Timing

Eight phases available via the `at` property in `_extension.yml` or document YAML:

1. `pre-ast`
2. `post-ast`
3. `pre-quarto`
4. `post-quarto`
5. `pre-render`
6. `post-render`
7. `pre-finalize`
8. `post-finalize`

Syntax in `_extension.yml` or document YAML:

```yaml
filters:
- path: my-filter.lua
at: pre-quarto
```

Default (no `at`): filters listed before a "quarto" marker get `pre-quarto`, filters listed after get `post-render`.

## FloatRefTarget

Cross-referenceable elements (figures, tables, listings) are represented as `FloatRefTarget` custom AST nodes.
Filters operating on these should use the `FloatRefTarget` handler:

```lua
return {
{ FloatRefTarget = function(el)
-- el.caption, el.content, el.identifier, etc.
return el
end
}
}
```