-
Notifications
You must be signed in to change notification settings - Fork 23
feat(quarto): add quarto-lua skill for Lua shortcodes and filters #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mcanouil
wants to merge
18
commits into
posit-dev:main
Choose a base branch
from
mcanouil:feat/quarto-lua
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+329
−12
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
e8ead93
feat(quarto): add quarto-lua skill for Lua shortcodes and filters
mcanouil 564f18c
fix(quarto-lua): remove broken links note to external .llms.md pages
mcanouil d28f2b1
chore: update Quarto CLI version in SKILL.md
mcanouil 0d275b8
chore: add date of last check
mcanouil ce6bdca
feat(quarto-lua): add custom AST nodes section and references
mcanouil 5fcf9fe
chore: update Quarto CLI version in SKILL.md
mcanouil 8025269
fix: update with clearer YAML registration details
mcanouil 2044e4e
fix: relax luadocs header requirement
mcanouil 8c40b52
fix: typo
mcanouil fc59a3f
feat: make extension an alternative to standalone with starting question
mcanouil 8be24d3
fix: don't mention "WebFetch" to not enforce a retrieval method
mcanouil e76d631
docs: rename section from 'lua' to 'quarto-lua'
mcanouil df3b61a
fix: update task descriptions to use 'Read' instead of 'Fetch' and to…
mcanouil 91b1ada
fix: strenghen logic to ensure resources and files are read before us…
mcanouil cb32531
fix: rewrite description to improve trigger of the skill
mcanouil 18d33b7
fix: improve access to crustom AST node reference using plain english
mcanouil 7199586
fix: use single quote
mcanouil 4dc3cd1
Merge branch 'main' into feat/quarto-lua
mcanouil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } | ||
| ``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.