Skip to content

Optional-variable syntax for doctemplate: $variable?$ #158

@cscheid

Description

@cscheid

Context

While working on bd-xdnk (plumbing doctemplate diagnostics through quarto render so undefined-variable warnings reach the user), it became clear that making the warnings visible is only half the story. Once they show up, users will reasonably ask: "what's the cleanest way to silence one I actually meant to be optional?"

Today the answer is to wrap each reference in a guard:

$if(author-greeting)$$author-greeting$$endif$

This is verbose, repeats the variable name, and obscures the intent ("this variable is optional") behind boilerplate. With the warning surfaced, every optional reference becomes a place users will reach for the suppression — so the cost compounds.

Proposal

A short suffix-? form that is exactly equivalent to the $if$/$endif$ wrapper:

$variable?$

would render the variable's value if the variable is defined and truthy, and produce nothing (no diagnostic) otherwise.

Feasibility (already studied)

I sketched the implementation while doing bd-xdnk so we'd know whether this is a small-ish lift or a re-design. Notes from the plan (see claude-notes/plans/2026-05-05-doctemplate-diagnostics-quarto-render.md § Follow-up):

  • ? is unused as a sigil in the current grammar. Pipes use / as their separator (tree-sitter-doctemplate/grammar/grammar.js:84-86).
  • No collision with any existing pipe name: pairs, first, last, rest, allbutlast, uppercase, lowercase, length, reverse, chomp, nowrap, alpha, roman, left, center, right.
  • Implementation cost is small: one new grammar token, one bool optional field on VariableRef, one branch in render_variable that returns Doc::Empty silently when optional is set and the lookup fails (skipping the warn_or_error_with_code(\"Q-10-2\", ...) call).
  • Behavioral parity with $if(var)$$var$$endif$ is exact for scalar values. For lists/maps, the $if$ truthy semantics already match is_truthy(), so no surprises there.

Open design questions for discussion

  1. Spelling. Suffix $variable?$ reads as "optional variable" and keeps pipes pure value-to-value. The alternative $variable/?$ would treat ? as a degenerate pipe in the existing repeat(seq(\"/\", $.pipe)) chain — more uniform syntactically, but ? doesn't transform a value, it changes resolution semantics, so the suffix form feels more honest.

  2. Applied partials. Should ? propagate to applied partials ($var?:partial()$ — render the partial only if var is defined)? Probably yes, same intent. Not applicable to $if$ itself.

  3. Should existing built-in templates adopt it? Phase 0 of bd-xdnk found the built-in MINIMAL_HTML_TEMPLATE and FULL_HTML_TEMPLATE are clean (every $var$ is already wrapped in $if(...)$). If $variable?$ lands, those wrappers could collapse for readability — but that's a stylistic choice, not load-bearing.

  4. Pandoc compatibility. Pandoc's template syntax (which we are mostly compatible with) does not have this. Adopting ? is a Q2 extension. We should weigh "qmd is a dialect, this is a useful extension" against "users may copy templates between Pandoc and qmd and now hit a parse error in one direction." (The Pandoc parser would treat $variable?$ as just a literal $variable?$ — variable name variable?, which is also invalid syntax in their grammar — so we need to be deliberate about the divergence.)

Why now, why this issue

Not asking for an implementation decision — just asking for a place to discuss before deciding whether to build it.

The bd-xdnk work uncovered the use case in a concrete way (warning noise on optional metadata variables in custom templates), but the syntax decision is the kind of thing that benefits from more eyes. If you're a Quarto contributor and you have an opinion on grammar extensions, optional-variable ergonomics, or Pandoc-compat tradeoffs, please weigh in here.

Internal tracking: bd-x5r4 (linked discovered-from to bd-xdnk).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttemplatesissues with our doctemplate port

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions