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.py extensions list. 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 to title-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_role confval; a conf.py projection 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_language confval (whose Sphinx default is default; ubCode leaves it unset instead, so existing projects gain no language-class change). A conf.py projection 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_prolog confval; a conf.py projection 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_epilog confval; a conf.py projection loader that maps ubCode’s native keys onto Sphinx’s confval names is planned.

syntax_example_numbering

Type: boolean (default: false)

Number the syntax-example blocks of each document. When enabled, a block with no title argument is titled Example N and a block with one is titled Example N: <title>, where N counts 1, 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_example is listed in extensions (see Available ports below), since that is what makes the directive known. It corresponds to the syntax_example_numbering configuration value of the sphinx-syntax-example Sphinx extension, so a project sharing one source tree between sphinx-build and ubCode gets the same numbering from both — requires sphinx-syntax-example 0.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 raw directive passes its content through to the output. On by default, matching docutils and Sphinx: a raw block’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 a directive.raw_disabled warning — 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 disabled raw records 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 false for 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 raw directive 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.video

A port of sphinxcontrib-video, adding the video directive 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_example

A port of sphinx-syntax-example, adding the syntax-example directive, 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 (Example when 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 examples Example 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 Sphinx conf.py spells it.

sphinx.ext.extlinks

A 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 %s is 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 issue role 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.
  • caption is 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 %s in 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’ include globs (the [source].include default 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 rst and md infer their type automatically, so type only needs to be set for a table with a different name (e.g. [parse.parsers.notes] with type = "rst"). A non-canonical key without an explicit type falls 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’ include globs also drives file discovery itself (replacing the [source].include default). 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.

  • mystMyST 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 as deflist, dollarmath, colon_fence, or fieldlist) are enabled by default — enable the ones you need explicitly via extensions (see Markdown extensions below).

  • gfmGitHub Flavored Markdown, enabling the strikethrough, tasklist, and gfm_autolink extensions.

extensions

Type: array or object (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.

deflist

Definition lists — a term line followed by one or more : definition blocks.

dollarmath

Dollar math: $...$ inline and $$...$$ block (block math may carry a label). Tunable via dollarmath.allow_space and dollarmath.allow_digits (see Per-extension options).

fieldlist

reStructuredText-style field lists (:name: value).

colon_fence

Colon-delimited directives and admonitions (:::{note} ... :::), an alternative fence to backticks and tildes. Tunable via colon_fence.exact_match (see Per-extension options).

strikethrough

Strikethrough with ~~text~~ (and, optionally, single-tilde ~text~). (GFM default.) Tunable via strikethrough.single_tilde (see Per-extension options).

tasklist

Task-list checkboxes in list items (- [x] done, - [ ] todo). (GFM default.)

gfm_autolink

Autolinking of bare URLs and email addresses. (GFM default.)

alert

GitHub-style alerts in blockquotes — a [!TYPE] marker on the first line, where TYPE is one of NOTE, TIP, IMPORTANT, WARNING, or CAUTION (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_space

Type: boolean (default: true)

Allow spaces adjacent to $ in inline dollar-math.

dollarmath.allow_digits

Type: boolean (default: true)

Allow digits adjacent to $ in inline dollar-math.

strikethrough.single_tilde

Type: boolean (default: false)

Treat single-tilde ~text~ as strikethrough (in addition to the standard ~~text~~).

colon_fence.exact_match

Type: 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 winsnot 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/*.md or docs/**/*.md) are matched relative to the directory containing ubproject.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, and follow_links still apply — they configure the filesystem walk itself, before any routing happens.

  • include / extend_include are ignored (with a diagnostic). Move those globs onto the relevant parser’s include instead.

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" }