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" ]
- extensions
Type:
array(default:[])Built-in Sphinx-extension ports to enable (see Built-in extension ports below), named exactly as in a Sphinx
conf.pyextensionslist. A port is a Sphinx extension ubCode reimplements natively, so its directives render for real rather than being passed through as unknown markup. Until a port is listed here its directives stay unknown — the unknown-directive warning fires and names the port that would provide them.[parse] extensions = ["sphinxcontrib.video"]
Unknown names are ignored.
- 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.- syntax_example_numbering
Type:
boolean(default:false)Number the
syntax-exampleblocks of each document. When enabled, a block with no title argument is titledExample Nand a block with one is titledExample N: <title>, where N counts1, 2, …through the document and restarts at each document. When disabled (the default) every block keeps its plain title —Example, or the title argument on its own.[parse] extensions = ["sphinx_syntax_example"] syntax_example_numbering = true
The option only has an effect when
sphinx_syntax_exampleis listed inextensions(see Available ports below), since that is what makes the directive known. It corresponds to thesyntax_example_numberingconfiguration value of the sphinx-syntax-example Sphinx extension, so a project sharing one source tree betweensphinx-buildand ubCode gets the same numbering from both — requiressphinx-syntax-example0.2.0 or later, since earlier versions have no such confval and ignore it, which would leave the Sphinx build unnumbered while ubCode numbers.- raw_enabled
Type:
boolean(default:true)Whether the
rawdirective passes its content through to the output. On by default, matching docutils and Sphinx: arawblock’s content reaches the built HTML verbatim, unescaped.[parse] raw_enabled = false
With the switch off, every
.. raw::(and MyST{raw}) passes nothing through: it raises adirective.raw_disabledwarning — docutils’"raw" directive disabled.message — and renders a placeholder showing its own source, escaped, instead. The:file:form is not read at all, so a disabledrawrecords no dependency on the file it names. This applies wherever a document is processed:ubc build,ubc check, and the editor preview.Set it to
falsefor pipelines that build contributions you do not control — an auto-deployed preview of an external pull request, say — where the author of a page is not the party you want writing raw HTML into it. It corresponds to the docutils setting of the same name, which Sphinx never changes from its default.The switch gates the
rawdirective only, exactly as docutils’ does. HTML written directly in a Markdown document is not a directive and is unaffected by it; see Raw content for the separate, preview-only escaping that covers both.
Built-in extension ports¶
Added in version 0.31.0.
Some Sphinx extensions are ported into ubCode —
reimplemented natively so their markup renders for real,
both in the live preview and in ubc build.
This is different from Extending directives below,
which only declares a third-party directive so it stops warning
(its content is passed through, not rendered).
Enable a port by adding its Sphinx extension name to [parse].extensions,
exactly as you would in a Sphinx conf.py extensions list:
[parse]
extensions = ["sphinxcontrib.video"]
While a port is not enabled,
the markup it would provide stays unknown.
For a directive the usual unknown-directive warning fires,
naming the port and this [parse] extensions key
so the fix is discoverable from the diagnostic alone;
an unknown role (such as an extlinks role) is simply shown verbatim,
with no warning.
Available ports¶
sphinxcontrib.videoA port of sphinxcontrib-video, adding the
videodirective for embedding an HTML5<video>player... video:: media/demo.mp4 :width: 640 :muted: :loop:
In Markdown (MyST) the same directive is written as a fence:
```{video} media/demo.mp4 :width: 640 :muted: :loop: ```Local video files (and a
:poster:image) are copied into the build output alongside your other assets; remote URLs are referenced as-is and never downloaded. The full upstream option set is supported —:alt:,:autoplay:,:nocontrols:,:loop:,:muted:,:playsinline:,:controlslist:,:poster:,:preload:,:width:,:height:,:class:,:align:,:caption:and:figwidth:— plus an optional second source argument for format fallback.sphinx_syntax_exampleA port of sphinx-syntax-example, adding the
syntax-exampledirective, which shows a block of markup twice — once as its raw, highlighted source and once as the rendered result — for documentation that teaches a markup syntax. The optional argument is the title shown above the block (Examplewhen omitted), and:highlight:overrides the source language. See Directives for the directive itself.[parse] extensions = ["sphinx_syntax_example"] syntax_example_numbering = true
This port has one option of its own,
[parse].syntax_example_numbering(documented under Core options above): it numbers each document’s examplesExample N/Example N: <title>, and is off by default. Note the port name is the extension’s module name (an underscore, not the hyphenated distribution name), exactly as a Sphinxconf.pyspells it.sphinx.ext.extlinksA port of sphinx.ext.extlinks, which turns a repetitive URL pattern into a short role. Unlike the video port it defines no fixed directive: you declare one role per entry in a
[parse.extlinks]table, mapping a role name to a URL template — and an optional caption template — in which%sis replaced by the role’s text.[parse] extensions = ["sphinx.ext.extlinks"] [parse.extlinks.issue] url = "https://github.com/useblocks/ubcode/issues/%s" caption = "issue %s"
With that configuration the
issuerole links to the substituted URL — the first form below renders as issue 42 linking to.../issues/42:See :issue:`42` for the bug, or :issue:`the tracker <100>` for the epic.
The same role in Markdown (MyST):
See {issue}`42` for the bug, or {issue}`the tracker <100>` for the epic.captionis optional; when it is omitted the link text is the fully expanded URL.An explicit title — the
the tracker <100>form above — is used as the link text, and the target (100) fills the%sin the URL.Write a literal percent as
%%; each template may contain at most one%s. A template with more (or a stray%) is reported as a configuration warning and that role is skipped.A role name that would shadow a built-in role (
ref,pep, …) is refused with a configuration warning, so the built-in keeps working.
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" }