-
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.
Open
Changes from 6 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,181 @@ | ||
| --- | ||
| name: quarto-lua | ||
| description: > | ||
| Write Lua shortcodes and filters for Quarto. | ||
| Use when creating, debugging, or modifying Lua code that runs inside | ||
| Quarto, including shortcode handlers, Quarto Lua filters, and | ||
| Quarto-specific Lua APIs. | ||
|
mcanouil marked this conversation as resolved.
Outdated
|
||
| metadata: | ||
| author: Mickaël Canouil (@mcanouil) | ||
| version: "1.0" | ||
| license: MIT | ||
| --- | ||
|
|
||
| # Quarto Lua | ||
|
|
||
| Write Lua shortcodes and filters for Quarto. | ||
|
|
||
| > This skill is based on Quarto CLI v1.9.36 (2026-03-24). | ||
|
|
||
| ## When to Use What | ||
|
|
||
| Task: Write a shortcode -> "Writing a Shortcode" below | ||
| Task: Write a filter -> "Writing a Filter" below | ||
| Task: Pandoc Lua API (constructors, types, methods) -> WebFetch `https://quarto.org/docs/extensions/lua-api.llms.md` | ||
| Task: Debug Lua / tooling -> WebFetch `https://quarto.org/docs/extensions/lua.llms.md` | ||
| Task: Shortcode details (args, raw output) -> WebFetch `https://quarto.org/docs/extensions/shortcodes.llms.md` | ||
| Task: Filter details (AST traversal, multi-pass) -> WebFetch `https://quarto.org/docs/extensions/filters.llms.md` | ||
| Task: Metadata / project filters -> WebFetch `https://quarto.org/docs/extensions/metadata.llms.md` | ||
|
mcanouil marked this conversation as resolved.
Outdated
|
||
| Task: Custom AST nodes / filter timing -> Read `references/custom-ast-nodes.md` in this skill directory | ||
|
|
||
| Fetch only pages relevant to the current task. | ||
|
|
||
| ## Writing a Shortcode | ||
|
|
||
| A shortcode exports a function called whenever `{{< name ... >}}` appears in `.qmd`. | ||
| Register under `shortcodes:` in `_extension.yml` or document YAML. | ||
|
mcanouil marked this conversation as resolved.
Outdated
|
||
| 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 `_extension.yml` or document YAML. | ||
| 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 | ||
| --- @license MIT | ||
| --- @copyright 2026 Author Name | ||
| --- @author Author Name | ||
| --- @version 0.1.0 | ||
| --- @brief One-line summary. | ||
| --- @description Longer explanation of purpose and behaviour. | ||
| --- Wrap at ~72 chars, indent continuation with two spaces. | ||
| ``` | ||
|
|
||
| Fields: `@module` (filename), `@license`, `@copyright`, `@author`, `@version` (semver), `@brief` (one-liner), `@description` (multi-line). | ||
| Always generate for new files. Update `@version`/`@description` when modifying. | ||
|
mcanouil marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## 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.html) | ||
| - [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) | ||
|
mcanouil marked this conversation as resolved.
|
||
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.