Repository navigation
docs: Accept backslash-escaped less-than sign in mkdocs - #8076
Open
wenzeslaus wants to merge 4 commits into
Open
wenzeslaus wants to merge 4 commits into
wenzeslaus wants to merge 4 commits into
Conversation
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
requested changes
Oct 18, 2026
petrasovaa
left a comment
Contributor
There was a problem hiding this comment.
This seems incompatible with moving to Zensical, see #7938.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)
Markdown as rendered by GitHub (screenshot)
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.pyregisters a small Python-Markdown extension (EscapeLessThanExtension) that adds<tomd.ESCAPED_CHARS. Python-Markdown provides this mechanism for extensions, and its built-intablesextension uses it to add|, the same waysmartyadds the quote characters. The hook is listed inmkdocs.ymland installed by both the Makefile and the CMake build.Addon docs are built with the core
man/mkdocsconfig (.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:
\<markus.metz.giswork at gmail.com\>\<markus.metz.giswork at gmail.com><markus.metz.giswork at gmail.com>x \< 5x \< 5x < 5\<x\>in a table cell\<x><x>`code \<x\>`code \<x\>Affected lines
These are lines that render a stray backslash with stock Python-Markdown. There are no cases inside code spans or fenced code.
Examples:
\<markus.metz.giswork at gmail.com\>Per Henrik Johansen \<*phj (at) norgit.no*\>Lars Ahlzen \<*lars (at) ahlzen.com*\>[\<darrell@mccauley-usa.com\>](mailto:darrell@mccauley-usa.com)length is shorter than *thresh* or *thresh* \< 0data points for 0 \< *p* \< 1"GIS_FLAG\_\<NAME\>" for flags and "GIS_OPT\_\<NAME\>"\[\[\[**\<GISDBASE\>/**\]**\<PROJECT\>/**\]| GRASS module \<-\> | \<--\> | ODBC Interface |"\<raster name\> in mapset \<mapset name\>"if the object is \<= n pixels thickChristoph Simon \<ciccio at kiosknet com.br\>*ERROR: Vector layer \<layer\> exists. ...*The names of the output maps are \<output\>bio01, \<output\>bio02,0; 0 \< θ ≤ 22.5; 22.5 \< θ ≤ 45; ...(15.00 \> lat \<= 45.00), Tropical (-15.00 \> lat \<= 15.00)Alternatives considered
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:
<entity:\<markus...\>becomes<markus...>. This works in every renderer, but HTML entities make the Markdown harder to read for a character this common.<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>bio01or<user at host>, because Python-Markdown treats those as raw HTML and they disappear from the page.`<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.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.confin 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.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
The hook and this description were written with Claude Code (Claude Opus 5.5) and reviewed by the author of the PR.