Docstring Processing

How Julia docstrings are converted to Quarto documentation

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 julia blocks; jldoctest and unlabelled blocks are treated as julia. 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

  1. First line should be a brief summary
  2. Signature should be included
  3. Arguments section for parameters
  4. 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)

““” ```