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:
arrayortable(default: each port’s own default)Switch built-in Sphinx-extension ports on or off (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 markup renders for real rather than being passed through.Most ports are already on — you only need this key to switch one off, or to switch on one of the few that are off by default. The per-port defaults are in the table under Built-in extension ports.
Two forms are accepted, and a list entry means exactly the same as
name = true:[parse] extensions = ["sphinx.ext.autosectionlabel"] # or, to switch ports off as well as on: [parse.extensions] "sphinx.ext.autosectionlabel" = true "sphinxcontrib.video" = false "sphinx.ext.todo" = { active = false }
The entry-table form (
{ active = false }) means the same as the plain boolean;activeis its only field, an entry table that omits it switches the port on, and any other key is reported (config.unknown_extension_option) and ignored — a misspeltactiv = falsewould otherwise silently leave the port on.Switching a port off does not make its markup unknown: it still parses, its options are still validated and completed, and each use site renders as a labelled placeholder showing its source and warns
directive.extension_disabled, naming the extension and this key. See What “off” looks like.Unknown names are ignored, so you can mirror a
conf.pyextensions list wholesale — entries ubCode ships no port for (myst_parser,sphinx.ext.napoleon, …) are simply inert. Three cases get an answer instead of silence:a name close to a known port’s name (for example
sphinxcontrib.vide, orsphinx.ext.videoforsphinxcontrib.video) is reported as a likely typo (config.unknown_extension), since it would otherwise switch nothing silently;sphinx_needsis built into ubCode and always active, so an entry switching it off is reported (config.extension_always_on) and ignored;a name ubCode knows but ships no port for — the
sphinx.ext.autodocfamily,sphinx.ext.graphviz— is reported when switched on (config.extension_unsupported), because listing it cannot make that markup render: onlysphinx-buildcan run those extensions.
Like every other code these can be suppressed, via
[lint] ignore = ["config.unknown_extension"](see Code matching and .* wildcards).Tip
If you also build with
sphinx-build, list your extensions explicitly. The defaults here are a ubCode-side courtesy; Sphinx needs each extension named in its ownextensionslist, so a project that relies on ubCode’s defaults will find the same markup failing in the Sphinx build.- 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] syntax_example_numbering = true
The option only has an effect while the
sphinx_syntax_exampleport is active, which it is by default (see Available ports below) — the port is what renders the directive. 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).
You do not have to list a port to use it.
Every port whose behaviour is triggered by a piece of markup is on by
default: write .. video:: and it renders.
[parse].extensions is the switchboard for changing that —
switching a port off, or switching on one of the few that start off.
Extension |
Default |
What it provides |
|---|---|---|
|
on |
the |
|
on |
the |
|
on |
the |
|
on |
the |
|
on |
the |
|
on |
the |
|
data |
link-shorthand roles — a populated |
|
off |
|
|
always |
the needs directives and roles; this one is ubCode and cannot be switched off |
Everything else a conf.py may list is inert.
Two families are worth naming because ubCode knows them by name and still
cannot render them: the sphinx.ext.autodoc family and
sphinx.ext.graphviz have no ubCode implementation at all
(their directives parse, and render as labelled placeholders),
so switching them on is reported (config.extension_unsupported) —
only sphinx-build can run them.
Two more built-ins are gated by their own configuration section rather
than by this key, and must not be listed here:
[intersphinx] (inventory-backed cross-project references) and
[codelinks] (source-code traceability).
Both are active as soon as their section has content —
the same data-implied rule [parse.extlinks] follows.
Switching a port off takes an entry, since the defaults are on:
[parse.extensions]
"sphinxcontrib.video" = false
# the entry-table form, for the same thing
"sphinx.ext.todo" = { active = false }
What “off” looks like¶
A switched-off port’s directives stay known. They parse, their options are validated, they are offered in completion and hover, and each use site:
renders as a labelled placeholder — a framed block showing the directive’s name, argument, options and body, so the source stays readable in the output;
warns
directive.extension_disabledat that site in the editor, naming the extension and this[parse] extensionskey;is counted on
ubc build html’s coverage radar (build.directive_unhandled), de-duplicated per directive name, with the same sentence.
“Off” therefore never means ubCode plays dumb about markup it can render;
it means “shown as a placeholder, and told why”.
Roles are the one exception, and it is a recorded gap:
an extlinks role whose port is switched off is shown as a placeholder chip
carrying the role name and its content, with no warning,
and sphinx_design’s :octicon: role keeps rendering
even with sphinx_design = false.
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.A source must name its file exactly — a
*wildcard extension (media/demo.*) is not resolved for a video source.ubc build htmlreportsbuild.video_glob_unsupportedand emits the reference as written (minus any?query/#fragment, which every asset reference is stripped of); nothing is copied for it. Wildcard extensions are an image feature: candidates are ranked by image type, which cannot choose a video file. A:poster:is an image, so:poster: media/thumb.*works normally.That warning is emitted while the page is rendered, and it has no
ubc checkcounterpart (the background index does not resolve video sources) — butubc build htmlreports it on every build, warm or fresh: a page’s render warnings are stored with its rendered body, so a build that reuses the page re-reports them. Note also that only the<stem>.*form is a wildcard extension:media/*.mp4is treated as a literal filename and fails to copy (build.image_read_failed), as it did before. 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] syntax_example_numbering = true # the port is on by default; this is how you switch it off [parse.extensions] sphinx_syntax_example = false
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.todoA port of sphinx.ext.todo, adding the
tododirective — an admonition for an outstanding task — andtodolist, which lists everytodoin the project... todo:: Document the new ``--strict`` flag. .. todo:: :class: blocking Decide whether the cache bump can be avoided.
In Markdown (MyST) the same directives are written as fences:
```{todo} Document the new `--strict` flag. ``` ```{todolist} ```Each
todogets an anchor —todo-1,todo-2, … in document order, or the:name:you give it — so atodolistlinks back to the original. Atodolistshows every project todo followed by that backlink, in source-path then document order.This port has two options of its own, both under
[parse]and both named exactly as their Sphinx configuration values are:[parse] todo_include_todos = true todo_link_only = false # the port is on by default; this is how you switch it off [parse.extensions] "sphinx.ext.todo" = false
todo_include_todosType:
boolean(default:true)Whether todos render at all. With it off, every
todoand everytodolistrenders nothing — exactly as if the markup were absent — which is how a build reads for an audience that should not see outstanding work.This corresponds to Sphinx’s
todo_include_todos, whose default isfalse. The defaults differ deliberately: in Sphinx an extension is often listed for reasons other than showing todos, whereas in ubCode a project that writestodomarkup is taken to want it rendered — hiding it is this one key, rather than the absence of an entry. A project sharing one source tree betweensphinx-buildand ubCode should set both values explicitly rather than rely on either default, since the Sphinx build shows nothing until its own value istrue.todo_link_onlyType:
boolean(default:false)Render a
todolistentry’s backlink on its own, without the surrounding “is located in <file>, line <n>” sentence. Corresponds to Sphinx’stodo_link_only, and shares its default.
Differences from a
sphinx-buildof the same sources worth knowing:inside a
todolist, each entry’s body is shown as plain text, one line per block — a bullet list’s items, and any nested blocks, each get their own line, but the bullets themselves and any inline markup are not reproduced, where Sphinx copies the block whole and shows a real list. The original block, one click away, renders in full;the backlink names the project-relative source path, where Sphinx interpolates an absolute filesystem path — so the built HTML is the same on every machine;
:class:adds to a todo’s styling, where Sphinx replaces it:.. todo:: :class: urgentkeeps the todo accent in ubCode and loses it entirely in a Sphinx build, because the extension sets its own default through that same option;listing order is by source path then document order. A Sphinx build’s order depends on which files it last re-read, so the two agree on a clean build and can differ after an incremental one;
:name:on atodocrashessphinx-build(an unhandled error inside the extension); ubCode treats it as an ordinary admonition name. Avoid it in a shared source tree.
todo_emit_warningsis not ported yet.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.extlinks.issue] url = "https://github.com/useblocks/ubcode/issues/%s" caption = "issue %s"
The table is all it takes — a populated
[parse.extlinks]switches the port on by itself, so there is no second key to remember. To keep the table but switch the roles off, say so explicitly:[parse.extensions] "sphinx.ext.extlinks" = false
That combination — a populated table with the port switched off — is reported (
config.extlinks_data_inert), because the roles then go unregistered and each use renders as a plain chip with nothing pointing at the entry responsible. It is worth knowing when the= falseis inherited: entries merge by name, so a base config that switches the port off keeps doing so for a child that only writes the table.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.
sphinx.ext.autosectionlabelA port of sphinx.ext.autosectionlabel, which lets you reference every section by its title with
:ref:, without writing an explicit.. _label:above it. Unlike the other ports it adds no directive or role: enabling it makes the labels exist, project-wide.[parse] extensions = ["sphinx.ext.autosectionlabel"] autosectionlabel_prefix_document = true autosectionlabel_maxdepth = 2
With
autosectionlabel_prefix_documenton (as above), a sectionOverviewinguide/intro.rstis referenced by document and title, and the reference links straight to that section:See :ref:`guide/intro:Overview` for the details.
The same reference in Markdown (MyST):
See {ref}`guide/intro:Overview` for the details.Titles are matched case- and whitespace-insensitively, exactly as
:ref:already matches explicit labels.This port has two options of its own, both under
[parse]and both named exactly as their Sphinx configuration values are:autosectionlabel_prefix_documentType:
boolean(default:false)Prefix each label with the document’s name (
doc/path:Title). Off by default — matching Sphinx — a section is referenced by its bare title alone (Overview), with no document prefix. Turning it on is recommended: bare titles easily collide across documents, and a colliding label resolves to only one of its sections. Corresponds to Sphinx’sautosectionlabel_prefix_document, and shares its default.autosectionlabel_maxdepthType:
integer(default: unset — no limit)Label only the top
Nsection levels of each document (1= every top-level section). Unset or0labels every section. Corresponds to Sphinx’sautosectionlabel_maxdepth, and shares its default.
Duplicate labels. Bare mode is the default, and repeated section titles (“Overview”, “Usage”, “Configuration”) are the norm rather than the exception, so enabling the port on an existing project can surface duplicates that were not there before. ubCode treats the two cases differently:
within one document — two same-titled sections in bare mode, or a section title colliding with an explicit
.. _label:in the same file — is resolved silently and deterministically. An explicit label written anywhere in that file wins over the automatic one, and among automatic labels the first section in the document wins.sphinx-builddoes warn here, and lets the last one it reads win instead;across documents — the same title in two files, or a section title colliding with an explicit
.. _label:written in another file — is reported asstd.duplicate_label, naming every contributing site. The reference resolves deterministically to the first contributing file in path order (Sphinx instead lets the last document it reads win).
sphinx-buildreports the cross-document cases too, asduplicate label <name>. If the port’s duplicates are noise for your project, silence that class alone:[lint] ignore = ["std.duplicate_label"]
This is the counterpart of Sphinx’s
suppress_warnings = ['autosectionlabel']: it is scoped to the port, so duplicates between explicitly authored targets keep reporting understd.duplicate_target.When both apply to one name — two explicit
.. _label:targets that a section title happens to share — both codes are reported: the explicit duplicate exists whether or not the port is enabled, so silencing the port’s class leaves it visible, and silencingstd.duplicate_targetleaves the port’s class visible.sphinx-buildlikewise emits two warnings for such a name.Other differences from a
sphinx-buildof the same sources worth knowing:in Markdown (MyST), a title containing inline markup (e.g.
# My **bold** title) is labelled by its plain text (My bold title), where Sphinx keeps the raw markup in the label name. Plain-text titles — the overwhelming majority — agree exactly.
Extending directives¶
The extend_directives section declares directives ubCode does not know,
so they parse without warning and their options are validated and completed.
Each directive is configured as a subsection:
Note
It declares NEW names; it cannot redefine a built-in one.
A name ubCode already has a directive for — including a
port’s name such as video or todo,
which are ordinary built-ins whether or not their port is on —
keeps its built-in parse specification, and the entry has no effect on it.
Use a different name, and if you want a built-in’s rendering to stop,
switch its port off through [parse].extensions
rather than redeclaring it.
[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" }