Parsing¶
The [parse] section extends ubCode’s built-in reStructuredText parser
with custom directives and roles from your Sphinx extensions.
Use this when ubCode warns about unknown directives or roles
that are provided by third-party packages.
It also configures which parser handles which file —
for example adding Markdown (MyST) alongside reStructuredText
within a single project — via the [parse.parsers.*] tables
(see Parsers and file routing).
Minimal example¶
[parse]
ignore_directives = ["my-custom-directive"]
[parse.extend_directives.my-admonition]
argument = true
content = true
parse_content = true
description = "A custom admonition"
extension = "my-extension"
[parse.extend_roles.my-role]
description = "A custom inline role"
extension = "my-extension"
Core options¶
- ignore_directives
Type:
array(default:[])List of directive names that won’t trigger warnings when encountered but not recognised by the parser. Useful for custom directives from external extensions.
ignore_directives = [ "my-custom-directive", "experimental-feature", "legacy-directive" ]
- default_role
Type:
string(default: unset)The role applied to bare interpreted text (`` text
) in every document, i.e. the seed for the ``.. default-role::state. When unset (or set to an empty string, which is equivalent), bare interpreted text uses the docutils default,title-reference.[parse] default_role = "emphasis"
An in-document
.. default-role::still overrides this from its point, and a bare.. default-role::reset returns totitle-reference(not to this configured value). If the value names a role ubCode does not recognise, a configuration diagnostic is reported and the seed is ignored.This corresponds to Sphinx’s
default_roleconfval; aconf.pyprojection loader that maps ubCode’s native keys onto Sphinx’s confval names is planned.- highlight_language
Type:
string(default: unset)The highlight language applied to language-less literal/code blocks in every document, i.e. the seed for the
.. highlight::state. When unset (or set to an empty string, which is equivalent), language-less blocks stay language-less (no language class), preserving ubCode’s default behaviour.[parse] highlight_language = "python"
An in-document
.. highlight:: <lang>still overrides this from its point.This corresponds to Sphinx’s
highlight_languageconfval (whose Sphinx default isdefault; ubCode leaves it unset instead, so existing projects gain no language-class change). Aconf.pyprojection loader that maps ubCode’s native keys onto Sphinx’s confval names is planned.- rst_prolog
Type:
string(default: unset)reStructuredText source prepended to every RST document in the rendered preview. The content is parsed at the very start of each document, so a
.. default-role::or.. role::defined here applies to the whole document — its state flows out of the prologue into the body. When unset (or set to an empty string, which is equivalent), nothing is prepended.[parse] rst_prolog = """ .. |sub| replace:: substitution text """
This corresponds to Sphinx’s
rst_prologconfval; aconf.pyprojection loader that maps ubCode’s native keys onto Sphinx’s confval names is planned.- rst_epilog
Type:
string(default: unset)reStructuredText source appended to every RST document in the rendered preview, parsed at the very end of each document. When unset (or set to an empty string, which is equivalent), nothing is appended.
[parse] rst_epilog = """ .. |trademark| replace:: ™ """
This corresponds to Sphinx’s
rst_epilogconfval; aconf.pyprojection loader that maps ubCode’s native keys onto Sphinx’s confval names is planned.
Extending directives¶
The extend_directives section allows you to define custom directives
or override built-in directive behaviour.
Each directive is configured as a subsection:
[parse.extend_directives.my-directive]
argument = true
options = true
content = true
content_required = false
parse_content = true
description = "My custom directive"
extension = "my-package"
# Define named options
[parse.extend_directives.my-directive.named_options]
title = { description = "The title of the element" }
class = { description = "CSS classes to apply" }
Directive configuration options:
- argument
Type:
boolean(default:false)Whether the directive accepts an argument (text on the same line as the directive name).
.. my-directive:: This is the argument Content here.
- options
Type:
boolean(default:false)Whether the directive accepts options (field list immediately after the directive line).
.. my-directive:: :option1: value1 :option2: value2 Content here.
- content
Type:
boolean(default:false)Whether the directive can have content (indented text block after options).
- content_required
Type:
boolean(default:false)When
true, emit a warning if the directive has no content when content is expected.- parse_content
Type:
boolean(default:false)When
true, parse the directive content as reStructuredText instead of treating it as literal text.- description
Type:
string(default:"")Human-readable description of what the directive does.
- extension
Type:
string(default:"")Name of the extension or package that provides this directive.
- named_options
Type:
object(default:{})Map defining the specific options this directive accepts. Each option supports the following properties:
- description
Type:
string(default:"")A short, human-readable description of the option.
- choices
Type:
array | null(default:null)A list of valid string values for the option. When set, ubCode can validate option values and offer autocompletion.
- flag
Type:
boolean(default:false)When
true, the option is a flag that takes no value. It is either present or absent.
[parse.extend_directives.figure.named_options] width = { description = "Width of the figure" } height = { description = "Height of the figure" } alt = { description = "Alternative text for accessibility" } align = { description = "Alignment", choices = ["left", "center", "right"] } figclass = { description = "CSS class for the figure" } nofooter = { description = "Hide footer", flag = true }
Extending roles¶
The extend_roles section allows you to define custom inline roles:
[parse.extend_roles.api]
description = "Reference to an API endpoint"
extension = "my-api-docs"
[parse.extend_roles.issue]
description = "Reference to a GitHub issue"
extension = "github-integration"
Role configuration options:
- description
Type:
string(default:"")Human-readable description of what the role does.
- extension
Type:
string | null(default:null)Name of the extension or package that provides this role.
Example usage¶
Once defined in configuration, you can use custom directives and roles in your RST files:
This is a paragraph with a :api:`/users/create` endpoint reference
and an :issue:`123` issue reference.
.. my-admonition:: Important Notice
:class: warning highlight
:name: security-note
This is custom admonition content that will be parsed as RST.
Parsers and file routing¶
Added in version 0.30.0.
By default ubCode parses every discovered file as reStructuredText.
The [parse.parsers.<name>] tables let you instead route different files
to different parsers — for example reStructuredText and Markdown (MyST)
within a single project.
The shared ignore_directives, extend_directives, and extend_roles
options documented above apply to all parsers
(RST and MyST share the same directive and role definitions);
each [parse.parsers.<name>] table then configures one parser
and selects the files it owns.
Classic mode vs. parser mode¶
The behaviour is modal,
derived solely from whether any [parse.parsers.*] table is declared:
- Classic mode (no parsers declared)
The default, and unchanged from earlier versions. File discovery is governed by [source].include (default
["*.rst"]) and every discovered file is parsed as reStructuredText. Existing projects keep behaving exactly as before.- Parser mode (one or more parsers declared)
The declared parsers are the complete set. File discovery is derived from the parsers’
includeglobs (the[source].includedefault of["*.rst"]no longer applies), and each discovered file is routed to the parser that owns it. A lone[parse.parsers.md]therefore parses Markdown only.
Note
Because the declared parsers are the complete set,
a mixed reStructuredText + Markdown project must list both parsers
explicitly — adding [parse.parsers.md] alone would drop *.rst discovery:
[parse.parsers.rst]
[parse.parsers.md]
Parser options¶
# A mixed RST + Markdown project
[parse.parsers.rst]
[parse.parsers.md]
flavour = "myst"
include = ["*.md", "docs/**/*.md"]
[parse.parsers.md.extensions]
dollarmath = { allow_space = true }
deflist = true
Each [parse.parsers.<name>] table accepts the following keys:
- type
Type:
string("rst"or"md", optional)The parser type. The canonical keys
rstandmdinfer their type automatically, sotypeonly needs to be set for a table with a different name (e.g.[parse.parsers.notes]withtype = "rst"). A non-canonical key without an explicittypefalls back to Markdown and emits a configuration diagnostic, so a custom-named parser never silently becomes Markdown.- include
Type:
array(default:["*.rst"]for RST,["*.md"]for Markdown)Glob patterns selecting which discovered files this parser handles. In parser mode the union of all parsers’
includeglobs also drives file discovery itself (replacing the[source].includedefault). See File routing for how patterns are matched.- priority
Type:
integer(default:0)Tie-breaker used when a file matches several parsers equally specifically (see File routing). Higher wins. This is an escape hatch; routing normally resolves on glob specificity alone.
- flavour
Type:
string("commonmark","myst", or"gfm", default:"commonmark")(Markdown parsers only.) The Markdown flavour, which selects a base set of enabled extensions:
commonmark— plain CommonMark, no extensions enabled by default.myst— MyST Markdown, enabling directive/role syntax, front matter, and tables (the always-on MyST core). This is the flavour to use for Sphinx-Needs-style directives in Markdown. Matching MyST-Parser, no optional extensions (such asdeflist,dollarmath,colon_fence, orfieldlist) are enabled by default — enable the ones you need explicitly viaextensions(see Markdown extensions below).gfm— GitHub Flavored Markdown, enabling thestrikethrough,tasklist, andgfm_autolinkextensions.
- extensions
Type:
arrayorobject(default:{})(Markdown parsers only.) Markdown extensions to enable on top of the flavour defaults. See Markdown extensions below.
Markdown extensions¶
The effective extension set for a Markdown parser is the flavour’s defaults unioned with any extensions you enable explicitly.
Available extensions¶
ubCode recognises the following extension names
(matching the MyST-Parser /
markdown-it-py extension keys).
Those marked (GFM default) are enabled automatically by the gfm flavour;
every other extension is off by default and must be enabled explicitly,
whatever the flavour.
deflistDefinition lists — a term line followed by one or more
: definitionblocks.dollarmathDollar math:
$...$inline and$$...$$block (block math may carry a label). Tunable viadollarmath.allow_spaceanddollarmath.allow_digits(see Per-extension options).fieldlistreStructuredText-style field lists (
:name: value).colon_fenceColon-delimited directives and admonitions (
:::{note} ... :::), an alternative fence to backticks and tildes. Tunable viacolon_fence.exact_match(see Per-extension options).strikethroughStrikethrough with
~~text~~(and, optionally, single-tilde~text~). (GFM default.) Tunable viastrikethrough.single_tilde(see Per-extension options).tasklistTask-list checkboxes in list items (
- [x] done,- [ ] todo). (GFM default.)gfm_autolinkAutolinking of bare URLs and email addresses. (GFM default.)
alertGitHub-style alerts in blockquotes — a
[!TYPE]marker on the first line, whereTYPEis one ofNOTE,TIP,IMPORTANT,WARNING, orCAUTION(matched case-insensitively):> [!NOTE] > Highlights information that users should take into account.
This is the set ubCode understands and acts on today. It is not a closed whitelist, though — unrecognised names are passed through rather than rejected (see Specifying extensions below), so a name not listed here is simply enabled verbatim.
Specifying extensions¶
Extensions can be given as a simple list of names…
[parse.parsers.md]
extensions = ["deflist", "dollarmath"]
…or as a table,
which additionally lets you disable a flavour default (name = false)
or pass per-extension options (name = { option = value }):
[parse.parsers.md]
flavour = "gfm"
[parse.parsers.md.extensions]
deflist = true # enable on top of the flavour
tasklist = false # disable a GFM default
dollarmath = { allow_space = true, allow_digits = false }
strikethrough = { single_tilde = true }
colon_fence = { exact_match = true }
Unknown extension names are passed through rather than rejected, so newer extensions work without a ubCode update.
Per-extension options¶
The following per-extension options are recognised:
dollarmath.allow_spaceType:
boolean(default:true)Allow spaces adjacent to
$in inline dollar-math.dollarmath.allow_digitsType:
boolean(default:true)Allow digits adjacent to
$in inline dollar-math.strikethrough.single_tildeType:
boolean(default:false)Treat single-tilde
~text~as strikethrough (in addition to the standard~~text~~).colon_fence.exact_matchType:
boolean(default:false)Require a closing colon fence to match the opening fence length exactly.
File routing¶
When a file matches the include globs of more than one parser,
the most specific glob wins — not the order of the tables in the file.
Specificity is measured by path-prefix depth (number of / separators),
then by the count of literal (non-wildcard) characters.
Remaining ties are broken by priority (higher wins),
then deterministically by parser name.
Glob patterns follow the same two forms as [source]:
Separator-less globs (e.g.
*.md) match against a file’s base name, so they apply at any directory depth.Path-prefix globs (e.g.
docs/*.mdordocs/**/*.md) are matched relative to the directory containingubproject.toml.
For example, given both a broad and a narrow Markdown parser:
[parse.parsers.rst]
[parse.parsers.md]
include = ["*.md"]
[parse.parsers.design]
type = "md"
flavour = "myst"
include = ["docs/design/**/*.md"]
a file at docs/design/spec.md routes to the design parser
(the deeper, more specific glob),
while readme.md routes to the broad md parser.
In parser mode, a discovered file that matches no parser is reported with a diagnostic rather than parsed — an unmatched file is almost always a misconfiguration.
Discovery and the [source] section¶
In parser mode the parsers own inclusion, so the [source] section keeps only its discovery / traversal responsibilities:
exclude/extend_exclude,respect_gitignore, andfollow_linksstill apply — they configure the filesystem walk itself, before any routing happens.include/extend_includeare ignored (with a diagnostic). Move those globs onto the relevant parser’sincludeinstead.
In classic mode (no parsers declared) [source] behaves exactly as before.
Tip
To see which parser each discovered file routes to, run:
$ ubc build list-documents --parser
index.rst (rst)
guide.md (md)
This is the quickest way to debug a routing or discovery problem —
if a file is missing, or owned by the wrong parser,
check its parser’s include globs and their relative specificity.
Common patterns¶
Sphinx extension integration: Define directives from Sphinx extensions you use:
[parse.extend_directives.automodule]
argument = true
options = true
description = "Automatically document a Python module"
extension = "sphinx.ext.autodoc"
[parse.extend_directives.automodule.named_options]
members = { description = "Include module members" }
undoc-members = { description = "Include undocumented members" }
Custom documentation patterns: Define project-specific directives:
[parse.extend_directives.api-endpoint]
argument = true
options = true
content = true
description = "Document an API endpoint"
extension = "project-docs"
[parse.extend_directives.api-endpoint.named_options]
method = { description = "HTTP method (GET, POST, etc.)" }
path = { description = "URL path pattern" }
deprecated = { description = "Mark as deprecated" }