base-markdown
A CommonMark-compliant Markdown parser for Rust with pull events, a source-backed AST, extensions, and HTML and Markdown renderers. It builds on markdown-rs, with compact CommonMark parsing and a single source-backed document representation.
Motivation
Socketry's documentation tooling needs to inspect, edit, and render Markdown sections as structured data. This fork builds on markdown-rs with a pull-based event parser, a single source-backed document AST with editing helpers, shared HTML and Markdown renderers, and optional syntax extensions for richer documentation.
Usage
The library supports Rust 1.73 and later.
For direct conversion from Markdown source to HTML:
let html = to_html;
assert_eq!;
Enable GFM constructs with Options::gfm():
use ;
let html = to_html_with_options;
See the API documentation for pull events, document editing, parser options, and frontmatter, the architecture guide for the parser design, and the performance comparison for benchmarks against other Markdown engines.
Releasing
Prepare a release with cargo bake cargo:version:patch (or minor, major, or bump --version X.Y.Z), commit it with a Release: vVERSION trailer, then run cargo bake cargo:release and open a pull request. After review and merge, GitHub Actions publishes the release when the configured crates-io environment approves it, then creates or updates the matching GitHub Release from releases.md. Follow the shared Releasing skill for the standard process.
Releases
See releases.md for the full release history.
v0.1.2
- Replace a line ending before inline HTML only when the HTML could start an HTML block there, as
mdast-util-to-markdowndoes. Line endings before other tags, such as<span>or</span>, are now kept, and a hard break before them is no longer lost. - Keep a hard break before inline HTML that could start an HTML block by indenting the next line four spaces, so it continues the paragraph. Previously the break's line ending was replaced with a space, leaving a literal backslash.
v0.1.1
- Keep inline HTML after text unchanged when serializing Markdown. Previously each serialization added a space before inline HTML in paragraphs, including those in block quotes and list items, so repeated normalization kept growing the gap. Only a line ending directly before inline HTML is now replaced with a space.
- Write every line ending in serialized Markdown as LF. CRLF and lone CR line endings inside code, HTML, math, front matter and titles were previously copied through, which left mixed line endings in otherwise LF output.
- Return inline subtrees from
to_markdown_nodewithout a trailing line ending, as cmark does. Text, emphasis, links, code and other phrasing content, including inline opaque nodes, serialize as*b*rather than*b*\n; non-empty documents and block subtrees still end with a line ending. This changes output for callers serializing inline fragments.
v0.1.0
- Publish as
base-markdownin thebase-rsorganization, with version numbering restarted at0.1.0. Releases require aRelease: vVERSIONGit trailer on the final release commit; version changes alone no longer trigger publication. - Add a pull-based semantic event contract:
EventParser, a direct HTML writer (render_events,EventHtmlRenderer), and an incrementalAstBuilderthat consumes events within a caller-supplied budget. The default HTML API and document traversal share the same writer. Ordinary source values retain byte ranges; editing a value detaches it into owned storage. Escaped punctuation and the following ordinary text now form a single text event and AST text node. Authored definition events, including duplicates, are kept in the event stream, while lookup uses a separate first-definition-wins index. - Make the source-backed
Documentarena the sole AST, with checked subtree editing, heading and section helpers, lazy authored-syntax metadata (NodeSyntax), and validated arena serialization. Markdown serialization accepts documents or subtree IDs. Removemdast,to_mdast, and the legacyRenderer,HtmlRenderer, andMarkdownRendererAPIs. This is a breaking API and serialized-AST format change; see the migration guide incontext/document.md, which also covers heading spans, frontmatter values, and resolved reference destinations. - Remove MDX support, including the
Constructs::mdxandParseOptions::mdxpresets, MDX construct fields, parser callbacks and exports, hidden MDX identifier/source-map helpers, and JSX/expression/ESM AST variants. Braces and JavaScript import/export statements are ordinary Markdown; angle-bracket syntax follows CommonMark HTML rules. This is a breaking API change. - Name individual extensions by their syntax: remove
gfm_from construct flags and parser and HTML options. For example,gfm_tablebecomestableandgfm_strikethrough_single_tildebecomesstrikethrough_single_tilde; serialized configuration keys drop the correspondinggfmprefix. Thegfm()presets and extension behavior are unchanged. This is a breaking API and configuration-format rename; seecontext/extensions.mdfor migration examples. EventParser::newnow returns the parser directly andto_html_with_optionsreturnsString; remove?,.unwrap()or.expect(...)from those calls. Checked event consumers, document validation and Markdown serialization retain their errors. This is a breaking API change; see the migration incontext/pull-parser.md.- Simplify diagnostics to borrowed or owned reasons, static identifiers, and byte spans, removing public unist coordinates and the hidden location utility.
- Remove the hidden public
decode_namedanddecode_numericcompatibility helpers and their HTML 4 entity table. This is a breaking API removal; Markdown character-reference decoding continues to use HTML 5. - Remove the no-op
logfeature; callers must stop requestingfeatures = ["log"]. Remove the runtimeregexdependency. - Correct parsing: opt-in heading IDs use normalized semantic text, including multiline code and math spans; mixed-list closing tags balance against their semantic openings, correcting previously mismatched closures after unclosed fences; sibling lists after unclosed fences, container indentation tabs, and empty unclosed code bodies are handled correctly; failed multiline frontmatter attempts restore line positions; adjacent setext headings stay balanced when earlier underline text joins a paragraph; retained HTML blank lines stop at their enclosing container boundary, preserving sibling list items when HTML consumes the separating newline; emphasis adjacent to escaped delimiters, and cases such as
[a]*a**_after a defined reference, resolve correctly; collapsed spaces stay significant in reference labels, so[a b]and[ab]resolve independently; multiline reference labels inside containers resolve, and tables are recognized after headers or rows beginning with syntax markers. Separators inserted before container closing tags use the document's line-ending style. GFM documents now share the CommonMark corrections for list tightness, literal code endings, emphasis boundaries, and incomplete fences in lazy paragraphs. Documents with same-line reference definitions now use the same container rules as plain CommonMark documents for list tightness, indentation tabs, and contiguous indented code. - Correct Markdown serialization: an attached ordered item serialized on its own uses the correct marker, Unicode digits are no longer escaped as ordered-list markers, and Unicode lookalikes in URL schemes serialize as resource links rather than invalid autolinks.
- Improve parsing, rendering, and serialization throughput and large-document scaling, with fewer allocations and AArch64 NEON scanning for inline candidates and HTML escaping (with a portable scalar fallback). The convenience HTML API reserves up to 4 KiB of output capacity in advance. Rendered output, source spans, diagnostics, duplicate-definition precedence, first-call footnote numbering, expanded-tab label limits, stable node IDs, and edit-context hints are otherwise unchanged.
See Also
markdown-rs— the upstream parser.- CommonMark — the Markdown specification.
Contributing
Please open an issue or pull request on GitHub.
Agent Context
Run cargo bake agent:context:install to install shared context and skills. Use cargo bake agent:context:list and cargo bake agent:context:show to discover relevant guides, follow agents.md if present, and apply skills under .agents/skills/. The installer preserves repository-owned agents.md; it does not create or regenerate that file.