Skip to content

docs: Accept backslash-escaped less-than sign in mkdocs - #8076

Open
wenzeslaus wants to merge 4 commits into
OSGeo:mainfrom
wenzeslaus:mkdocs-escape-lt
Open

wenzeslaus wants to merge 4 commits into
OSGeo:mainfrom
wenzeslaus:mkdocs-escape-lt

Conversation

@wenzeslaus

Copy link
Copy Markdown
Member

The MkDocs manual shows a stray backslash wherever the Markdown source tries to escape a less-than sign (<), for example in the r.watershed author line \<markus.metz.giswork at gmail.com\> (see screenshot below).

The escape is valid CommonMark and renders fine on GitHub, but Python-Markdown used by MkDocs does not treat < as escapable. While this is simply a choice one way or the other (based on the selected standard), I suggest aligning with GitHub here, although my general preference would be a simpler text (no slash). This adds a MkDocs hook which adds < to the escapable characters, which fixes core and addon pages without editing the sources.

This is a second custom hook we are adding to MkDocs, but first custom escape.

The issue in rendered documentation (screenshot)

image

Markdown as rendered by GitHub (screenshot)

image
Details (AI generated)

Cause

The \< comes from the pandoc HTML-to-Markdown conversion (utils/grass_html2md.sh). Pandoc writes CommonMark/GFM, where any ASCII punctuation can be escaped with a backslash. MkDocs uses Python-Markdown, which follows the original Markdown rules. Those rules list \, `, *, _, {}, [], (), #, +, -, ., ! (Python-Markdown adds >), but not <. The maintainer confirms that only this subset is escapable (Python-Markdown/markdown#1464). So \<x\> renders as \<x>.

GitHub renders these files correctly, and so does g.md2man, which already treats \< as an escape (utils/g.md2man/gmd.py). Only the HTML manual is affected.

Change

The new hook man/mkdocs/scripts/hook_escape.py registers a small Python-Markdown extension (EscapeLessThanExtension) that adds < to md.ESCAPED_CHARS. Python-Markdown provides this mechanism for extensions, and its built-in tables extension uses it to add |, the same way smarty adds the quote characters. The hook is listed in mkdocs.yml and installed by both the Makefile and the CMake build.

Addon docs are built with the core man/mkdocs config (.github/workflows/documentation.yml), so addon pages are fixed as well.

Tested with a full local build of the manual (make build-mkdocs with the pinned requirements). No page in the built site contains a backslash before an escaped less-than sign. Results from a minimal project with mkdocs 1.6.1 and Python-Markdown 3.11:

Source Before After
\<markus.metz.giswork at gmail.com\> \<markus.metz.giswork at gmail.com> <markus.metz.giswork at gmail.com>
x \< 5 x \< 5 x < 5
\<x\> in a table cell \<x> <x>
`code \<x\>` code \<x\> unchanged, code spans are literal

Affected lines

These are lines that render a stray backslash with stock Python-Markdown. There are no cases inside code spans or fenced code.

  • grass: 46 lines in 30 files
  • grass-addons: 31 lines in 18 files

Examples:

File Source
raster/r.watershed/front/r.watershed.md \<markus.metz.giswork at gmail.com\>
lib/pngdriver/pngdriver.md Per Henrik Johansen \<*phj (at) norgit.no*\>
lib/cairodriver/cairodriver.md Lars Ahlzen \<*lars (at) ahlzen.com*\>
vector/v.random/v.random.md [\<darrell@mccauley-usa.com\>](mailto:darrell@mccauley-usa.com)
vector/v.clean/v.clean.md length is shorter than *thresh* or *thresh* \< 0
vector/v.surf.idw/v.surf.idw.md data points for 0 \< *p* \< 1
general/g.parser/g.parser.md "GIS_FLAG\_\<NAME\>" for flags and "GIS_OPT\_\<NAME\>"
lib/init/grass.md \[\[\[**\<GISDBASE\>/**\]**\<PROJECT\>/**\]
db/drivers/odbc/grass-odbc.md | GRASS module \<-\> | \<--\> | ODBC Interface |
ps/ps.map/ps.map.md "\<raster name\> in mapset \<mapset name\>"
raster/r.thin/r.thin.md if the object is \<= n pixels thick
addons v.feature.algebra Christoph Simon \<ciccio at kiosknet com.br\>
addons v.in.survey *ERROR: Vector layer \<layer\> exists. ...*
addons r.bioclim The names of the output maps are \<output\>bio01, \<output\>bio02,
addons r.bearing.distance 0; 0 \< θ ≤ 22.5; 22.5 \< θ ≤ 45; ...
addons i.sentinel.preproc (15.00 \> lat \<= 45.00), Tropical (-15.00 \> lat \<= 15.00)

Alternatives considered

  1. Fix the source lines (46 in grass, 31 in addons). I prepared and render-tested a patch for this. Each line can be changed in one of these ways:

    • &lt; entity: \<markus...\> becomes &lt;markus...&gt;. This works in every renderer, but HTML entities make the Markdown harder to read for a character this common.
    • Bare < where it cannot start an HTML tag, as the original Markdown rules suggest. This works when < is followed by a space, digit, = or - (x < 5, <= 0.5, <->). It does not work for <name>, <output>bio01 or <user at host>, because Python-Markdown treats those as raw HTML and they disappear from the page.
    • Code spans for placeholders, such as `<layer>` or `opt_<name>=<value>`. These are often better semantically, but it is a larger editorial change.

    Each of these only fixes the existing lines. New docs written for GitHub/CommonMark, or converted with pandoc, would bring \< back unless a lint check is added.

  2. pymdownx.escapeall (suggested in A line ending that is preceded by a backslash is not parsed as a hard line break Python-Markdown/markdown#1464). This makes every backslash an escape, which goes further than CommonMark and breaks existing docs:

    • %APPDATA%\postgresql\pgpass.conf in grass-pg.md would render as %APPDATA%postgresqlpgpass.conf.
    • C:\\Program Files\\Java\\... in addons r.maxent.setup and m.printws would lose its backslashes.
    • It would likely interfere with the LaTeX in addons r.runoff and r.timeofconcentration.
  3. Post-process pandoc output in utils/grass_html2md.sh. The conversion is already done, so this would only affect future conversions and would not fix the existing files.

Suggested squash-merge commit message

docs: Accept backslash-escaped less-than sign in mkdocs
Markdown docs written for CommonMark (pandoc conversion, GitHub) use
\< to escape the less-than sign, e.g., in author emails as
\<user at example.com\> or in comparisons like x \< 0. Python-Markdown,
used by MkDocs, follows the original Markdown rules where < is not
escapable, so the backslash was rendered in the HTML manual.

Add a MkDocs hook which registers a small Python-Markdown extension
adding < to ESCAPED_CHARS, the same mechanism the tables extension uses
for |. This fixes the rendering for core and addons without changing the
sources.

Written with the help of Claude Code.

The hook and this description were written with Claude Code (Claude Opus 5.5) and reviewed by the author of the PR.

Markdown docs written for CommonMark (pandoc conversion, GitHub) use
\< to escape the less-than sign, e.g., in author emails as
\<user at example.com\> or in comparisons like x \< 0. Python-Markdown,
used by MkDocs, follows the original Markdown rules where < is not
escapable, so the backslash was rendered in the HTML manual.

Add a MkDocs hook which registers a small Python-Markdown extension
adding < to ESCAPED_CHARS, the same mechanism the tables extension uses
for |. This fixes the rendering for core and addons without changing the
sources.

Written with the help of Claude Code.
The docstring shows CommonMark escapes like \<name\>. A raw string
lets the source show them as they are instead of doubled backslashes.

Written with the help of Claude Code.
The module docstring now describes the general purpose of the hook,
the CommonMark versus Python-Markdown difference in backslash escapes,
and the class docstring explains the less-than sign specifically. The
note about the tables extension is removed as it is not needed to
understand the code.

Written with the help of Claude Code.
The hook module is about backslash escapes in general, so name it
hook_escape.py rather than after the less-than sign only.

Written with the help of Claude Code.

@petrasovaa petrasovaa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incompatible with moving to Zensical, see #7938.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake docs Python Related code is in Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants