Migrating to TMark¶
TeXSmith 0.7 parses its Markdown with TMark, a CommonMark parser with a specified extension set, instead of Python-Markdown and its plugin stack. Every spelling TeXSmith 0.6 understood is still accepted, but most of them are now deprecated sugar: the parser warns, and the canonical spelling is what the documentation teaches and what the printer emits.
Nothing in your corpus breaks on the day you upgrade. This page tells you what to change, how to change it in one command, and which behaviours differ because the parser changed.
One command¶
tmark lint reports every deprecated spelling with its replacement, and
--fix rewrites them:
tmark lint report.md # list what would change
tmark lint --fix --diff report.md # preview the rewrite as a unified diff
tmark lint --fix --stdout report.md > fixed.md
tmark lint --fix report.md # rewrite the file in place
--fix only applies the safe fixes — the mechanical one-to-one rewrites of
the table below. Without --stdout or --diff the files are rewritten in
place, so run it on a clean working tree.
A whole tree at once:
tmark lint --fix docs/**/*.md
Run tmark lint --strict afterwards: what remains is either a genuine
diagnostic or one of the sugars kept indefinitely.
What changes¶
Every row is accepted today. "fmt" in the Horizon column means the spelling
is scheduled for removal once tmark fmt has shipped long enough to have
rewritten the corpus; "indefinite" means it is part of the dialect's
compatibility promise and is not going away — usually because MkDocs Material
or Pandoc renders it natively.
Inline constructs¶
| Legacy | Canonical | Horizon |
|---|---|---|
[^key] citation |
@key |
fmt; [^1] stays a footnote; meaning-preserving — both are the short, parenthetical citation, and --fix inserts a space when the [^key] hugs the word before it |
^[k1,k2] citation |
@[k1; k2] |
fmt; ^[…] then becomes an inline footnote; same meaning-preserving rewrite, with the same hugging-space fix |
[@key, p. 3] (Pandoc) |
@[key, p. 3] |
indefinite (Pandoc import) |
@https://doi.org/10.… |
@doi:10.… |
indefinite (sugar) |
[](gls:term) |
@gls:term |
fmt |
{latex}[\clearpage] |
{raw latex}(\clearpage) |
fmt |
{typst}[…], {html}[…] |
{raw typst}(…), {raw html}(…) |
fmt |
{margin}[…] |
{aside}[…] |
fmt |
{margin}[…]{l} / {r} / {o} / {i} |
{aside side=left}[…] / right / outer / inner |
fmt |
#{prefix:key} counter marker |
#(prefix:key) |
fmt |
{index:registry}[…] |
{index registry=…}[…] |
fmt |
{index}[…]{b} |
{index main=true}[…] |
fmt |
{index}[…]{i} |
content markup: {index}[*term*] |
fmt |
{: .thin #id} attribute list |
{.thin #id} |
fmt |
[=9/20 "Review"] |
[=45% "Review"] |
fmt |
Blocks¶
| Legacy | Canonical | Horizon |
|---|---|---|
/// latex … /// |
a ```latex raw fence |
fmt |
latex render fence (draft 2) |
```latex raw |
never shipped |
/// caption with an indented attrs: line |
a Figure: / Table: / Listing: caption line |
fmt |
/// figure-caption |
Figure: … {#fig:x} |
fmt |
--8<-- "file" |
{include}(file) |
fmt |
margin |
aside |
fmt |
Table: line before the table |
Table: line after the table |
indefinite (Pandoc accepts both) |
!!! note / ??? note callouts |
note {title="…" collapsed=true} |
indefinite (Material renders them) |
=== "Windows" content tabs |
tabs + tab {title=Windows} |
indefinite (Material renders them) |
<div markdown> |
div |
indefinite (the only container a Python-Markdown site renders) |
bare ```mermaid fence |
```mermaid image |
indefinite (MkDocs renders it) |
Front matter¶
| Legacy | Canonical | Horizon |
|---|---|---|
top-level bibliography: |
press.sources.bibliography |
fmt |
top-level crossrefs: |
press.sources.crossrefs |
fmt |
top-level counters: |
press.declare.counters |
fmt |
top-level glossary: |
press.declare.glossary |
fmt |
top-level acronyms: |
press.declare.acronyms |
fmt |
top-level admonitions: |
press.declare.admonitions |
fmt |
admonitions.<type>.icon / .color |
press.callouts.<type> |
fmt |
press.callout_style, press.admonition_style |
press.callouts.style |
fmt |
--no-promote-title |
title: null |
indefinite |
title, authors, date, lang and id stay at the root of the front
matter: that is where MkDocs, Pandoc and editors read them. Only the keys
TeXSmith owns move under press:, and even those may still sit at the root —
press is a namespace that keeps them out of a static-site generator's way,
not a requirement.
Before:
---
title: Revue firmware
counters:
fw: {name: Constat, format: "FW-{n:02d}"}
bibliography:
ein05: https://doi.org/10.1002/andp.19053221004
crossrefs:
fwrev: build/firmware-review.refs.json
press:
callout_style: classic
---
After:
---
title: Revue firmware
press:
callouts:
style: classic
declare:
counters:
fw: {name: Constat, format: "FW-{n:02d}"}
sources:
bibliography:
ein05: https://doi.org/10.1002/andp.19053221004
crossrefs:
fwrev: build/firmware-review.refs.json
---
MkDocs plugin options¶
| Legacy | Canonical | Horizon |
|---|---|---|
save_html: true |
— | removed in 0.7 |
save_html saved the rendered HTML of every page next to the book. Nothing
reads it any more: since the TMark migration the PDF is built from each page's
Markdown, so the snapshot recorded a document the book did not come from. The
option is gone rather than ignored, and MkDocs says so — Plugin 'texsmith'
option 'save_html': Unrecognised configuration name — so a configuration that
still carries it is told, and the build carries on.
What the book was built from is written unconditionally: one
<build_dir>/<folder>/sources/**.md per page, the page's Markdown with its
metadata and the site's declarations back in front of it, exactly what the
reader parsed. That is the file to read when a PDF and a page disagree.
Behaviour changes that are not bugs¶
These are not deprecations: they are places where a CommonMark parser reads
your bytes differently from Python-Markdown. tmark lint --fix cannot repair
them, because in each case the old reading was the accident.
- Lazy continuation lines
- CommonMark continues a paragraph inside a block quote or a list item even
when the continuation line carries no marker. A line following
> quotewith no>joins the quote instead of starting a new paragraph. Put a blank line where you meant a break. - List indentation
- A nested list item must be indented to the content column of its parent
(three spaces after
1., two after-), not by an arbitrary four. Lists indented by four spaces under a-parent become indented code blocks. Conversely, a continuation paragraph inside a list item needs the content column too. _inside words- CommonMark never opens emphasis inside a word, so
snake_case_nameandfile_name_herestay literal. Python-Markdown'sbetteremdid the same, but the plainmarkdownbehaviour did not: documents that relied ona_b_crendering as emphasis now show the underscores. - HTML blocks
- CommonMark's seven HTML-block kinds decide where an HTML block ends. Raw
HTML is kept as typed and dropped by the paged writers. A
<div>whose body must be parsed as Markdown needs themarkdownattribute, and the canonical spelling isdivanyway. - Entity handling
- Named and numeric HTML entities (
,—) are decoded to the character they name, in every backend. An ampersand that is not an entity stays an ampersand; write&when you mean the character in HTML output. - Attribute lists need a host
{#id .class}attaches to the element immediately before it (or to the heading it terminates). A brace group with nothing to attach to is literal text and raisesattributes-no-host. Where you want attributes on a piece of running text, wrap it in an anonymous span:[this claim]{#claim:one}. The Python-Markdown spelling{: …}is accepted and deprecated.\@and\#escapes@before a word and#before[or(are now sigils. They are guarded —@never fires inside an e-mail address, a URL, a word or code, and#[or#(followed by a non-space does not occur in prose — but where you do need the literal character, escape it:\@,\#.
Two further deviations from GFM are deliberate and long-standing in TeXSmith:
__text__ is small caps, not a second bold, and --- on its own line is a
divider that the paged templates render as a page break. Both are disabled by
the strict profile.
A migration session¶
# 1. See what the corpus looks like today.
tmark lint docs/**/*.md 2>&1 | tee /tmp/before.txt
# 2. Preview the mechanical rewrites.
tmark lint --fix --diff docs/**/*.md | less
# 3. Apply them on a clean tree, then review the diff.
tmark lint --fix docs/**/*.md
git diff
# 4. What remains needs a human: read it with --strict.
tmark lint --strict docs/**/*.md
Then rebuild the documents and compare the PDFs. The constructs most likely to
move are the ones the CommonMark rules above touch — nested lists and block
quotes — not the ones --fix rewrote.
See also Diagnostics for the diagnostic codes and how to promote a warning to a hard failure.