Docstring Processing
Docstring Processing
QuartoDocBuilder automatically extracts and formats Julia docstrings for Quarto.
How It Works
1. Extracting Documentation
Julia stores documentation in Base.Docs.meta(). QuartoDocBuilder iterates through all documented symbols:
# Get all documented symbols from a module
symbols = get_objects_from_module(MyModule)2. Converting to Markdown
Each docstring comes back from Base.Docs.doc as a Markdown.MD tree. The renderer in quarto_format.jl is deliberately structure-agnostic: it dispatches quarto_format on the type of each node and never reaches into a field (.code, .content[1], …) without first checking the node type. This is why a docstring that starts with a paragraph instead of a signature renders just as cleanly as one that starts with a code block — there is no assumed shape. Unknown node types fall back to Markdown.plain, so any well-formed docstring renders without error.
A Markdown.MD can be nested (one entry per method docstring), so the tree is flattened into a flat list of leaf nodes before each is formatted:
- Code blocks → re-fenced as
juliablocks;jldoctestand unlabelled blocks are treated asjulia. They are plain (non-executable) fences by default, so examples are not run by Quarto. - Headers → demoted by two levels (see below).
- Admonitions → mapped to the matching Quarto callout (see below).
- Everything else (lists, tables, block quotes, inline math) →
Markdown.plain, which preserves`x`as$x$.
3. Generating Pages
Each documented function gets its own page in docs/reference/:
docs/reference/
├── my_function.qmd
├── another_function.qmd
└── MyType.qmd
Supported Docstring Elements
Code Blocks
A fenced code block inside a docstring is re-emitted as a plain julia fence — it is shown as a static example and not executed by Quarto. Blocks tagged jldoctest, and blocks with no language tag, are also rendered as julia.
Admonitions
Julia admonitions are mapped to the corresponding Quarto callout type rather than always rendering as a warning:
| Julia admonition | Quarto callout |
|---|---|
!!! note, !!! info, !!! compat |
callout-note |
!!! tip |
callout-tip |
!!! warning |
callout-warning |
!!! danger |
callout-important |
| anything else | callout-note |
So a docstring admonition like:
"""
!!! warning "Performance Note"
This function is O(n²) for large inputs.
"""becomes a Quarto callout:
::: {.callout-warning title="Performance Note"}
This function is O(n²) for large inputs.
:::The callout takes its title from the admonition. When the title is empty or is just the capitalized category default (e.g. a note titled "Note"), the title attribute is omitted.
Headers
Headers inside a docstring are demoted by two levels (H1 → H3, H2 → H4, and so on, capped at H6). The reference page already uses an H1 for the object name, so demotion keeps a docstring’s own # Arguments / # Examples headings below that title and out of the page-level table of contents, which would otherwise be polluted by every section of every docstring.
Multiple Signatures and Methods
Base.Docs.doc returns one block per method docstring. The renderer produces one rendered block per entry, separated by a horizontal rule on the page, so a function with several documented methods has all of them shown:
"""
process(x::Int)
process(x::String)
Process different types of input.
"""Customizing Output
Reference Page Grouping
Use ReferenceGroup to organize the reference page:
reference = [
ReferenceGroup(
title = "Core Functions",
desc = "Main package functionality",
contents = [:main_func, :helper_func]
),
ReferenceGroup(
title = "Utilities",
contents = [starts_with("util_")]
)
]Short Descriptions
The reference index links each object and follows it with a short description. For a docstring that starts with a signature code block, the rendered signature is the link text; otherwise the object name is used. The description is a blockquote of the docstring’s first paragraph, truncated to roughly 200 characters. Because the index is also passed through the structure-agnostic renderer, a paragraph-first docstring produces a valid entry rather than crashing the build.
Best Practices
Write Good Docstrings
- First line should be a brief summary
- Signature should be included
- Arguments section for parameters
- Examples for usage
"""
process_data(input; normalize=true)
Process input data with optional normalization.
# Arguments
- `input`: The data to process
- `normalize`: Whether to normalize output (default: true)
# Returns
A processed DataFrame.
# Examples
```julia
result = process_data(my_data)
result = process_data(my_data, normalize=false)““” ```