md-tools

module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT-0

README

Markdown Tools

Small, sharp tools for manipulating markdown. I've tailored them to my specific tastes for authoring and presenting plain text. Maybe they'll be helpful for you too.[^1]

Installation

There is a Homebrew formula for md-tools in danhorst/homebrew-tap. To install these tools with that formula, use:

brew tap danhorst/tap && brew install danhorst/tap/md-tools

If you don't want to use Homebrew and have golang set up already, ./bin/md-install builds and installs all the executables into your $GOPATH. Pre-built binaries are not available at this time.

Usage

These utilities are built with the Unix Philosophy in mind. They accept text via STDIN and output to STDOUT. This let's you chain them with Unix pipes (|). Use the -w FILE flag to replace the contents of FILE instead of printing to STDOUT. Use -i FILE to read from STDIN and write the result to FILE — useful at the end of a pipe chain (e.g. mdsplit X | mdtable -i X).

The commands are (mostly) set up in pairs, each responsible for applying or reverting a style convention:

  • mdref converts inline-style links to a tidy list of numbered reference-style links at the bottom of the document. Most of the tooling out there to do manipulation like this—pandoc et. al.—use a text for the link reference, not a number.
  • mdinline converts all reference-style links to inline links.
Annotations
  • mdfnt renumbers footnote references ([^label]) to sequential integers in order of first appearance, updating the corresponding definitions.
  • mdsidenote converts markdown footnotes into HTML literals for sidenotes that can be styled with Tufte CSS (or a derivative).
  • mdfootnote attempts to convert HTML markup for sidenotes back into markdown footnotes.

The pair round-trips, and keeps doing so if you run mdsplit, mdjoin, or mdwrap in between. One thing doesn't survive: a reference-style link inside a footnote gets inlined on the way out, because mdsidenote resolves it and drops the label, so mdfootnote has nothing to rebuild the reference from. Everything else comes back.

If you have documents written before the marker moved onto the preceding line, mdfootnote FILE | mdsidenote converts them.

[!NOTE] If you add a new markdown footnote to a document you've already run mdsidenote on, it numbers from 1 independently of the sidenotes—so you get two notes labelled "1". Re-run the conversion from the footnote form rather than mixing the two.

Sentence structure
  • mdsplit takes paragraphs where all the sentences aren't separated by new lines (like iA Writer expects) and splits each sentence onto it's own line.
  • mdjoin takes text written in one sentance per line (the way I like to do it in vim) and gloms them together into contiguous paragraphs.
Tables
  • mdtable normalizes GFM table column widths so all cells in each column are padded to equal width, making tables visually aligned in plain text.

Hard wrapping

  • mdwrap wraps body text to 60 characters. Specify an arbitrary column count with the-c flag.
  • mdunwrap removes hard wrapping and returns text into contiguous paragraphs.

Rendering

  • mdhtml renders a markdown document to standalone HTML.

GFM is rendered as you'd expect: tables with per-column alignment, task lists, strikethrough, autolinks, and footnotes. GitHub alerts (> [!NOTE] and friends) become callouts carrying GitHub's class names, so a stylesheet written for GitHub's markup applies. Raw HTML passes through untouched—that's what lets mdsidenote output survive—so mdhtml is a renderer, not a sanitizer.

The default output is a complete document with an embedded stylesheet derived from Tufte CSS, sized to the sidenote markup mdsidenote produces. Use -css FILE to supply your own stylesheet, -no-css to omit it entirely, or -fragment to emit just the body content for pasting into a template. The document title comes from -title, defaulting to the input file's name. YAML frontmatter is stripped rather than rendered.

-inline-svg replaces referenced .svg files with their contents inline, so the document stands on its own with no external assets. Identifiers are namespaced per diagram, so the same file can appear twice without its internal styles colliding. References resolve against the input file's directory, or -base DIR when reading from STDIN.

Inlining moves an SVG from an image—where the browser runs no scripts and loads nothing external—into the live document, where it can do both. So mdhtml refuses to inline an SVG carrying a script, an event handler, a foreignObject, a javascript: URL, or a reference to another document, and refuses anything it can't parse. Pass -unsafe-svg when you know the file. Treat that check as a guard against accident, not a guarantee: what actually keeps you safe is only inlining SVG you generated.[^2]

Remote references are never fetched—that would make the output depend on the network—so they stay <img> tags and get a note on STDERR.

Unlike the other tools, mdhtml refuses -w — it would replace your markdown source with HTML. Use -i FILE or a shell redirect instead.

Colophon

[!NOTE] I don't really know golang or have much experience with how to properly parse and manipulate text files. The heavy lifting was done by Claude Code in Zed.

Does that make this a "vibe coding" project? Sort of. I've been looking at the source, telling the agent to do things, controlling git commits, and maintaining tight control over the fixtures that define success. The results aren't stellar but they solve problems I've had for a long time and never got around to coding a solution myself.

There's an examples/showcase.md that exercises everything mdhtml renders on one page. bin/mdhtml --inline-svg examples/showcase.md > showcase.html is the quickest way to see what the default styling looks like.

Although I used Claude Code, the instructions are all in AGENTS.md. There is a utility script (bin/agent-setup) that takes care of symlinking AGENTS.md to CLAUDE.md.

[^1]: Assuming you don't just tell an LLM to do all your formatting for you—what a waste of tokens!

[^2]: Sanitizing the SVG instead was tempting, but getting it wrong fails silently and leaves you believing the output is clean. Refusing is honest about what it does and doesn't know.

Directories

Path Synopsis
cmd
mdfnt command
mdfnt renumbers footnote references ([^label]) to sequential integers in order of first appearance, and updates corresponding definitions.
mdfnt renumbers footnote references ([^label]) to sequential integers in order of first appearance, and updates corresponding definitions.
mdfootnote command
mdfootnote converts Tufte CSS sidenotes back to Markdown footnotes.
mdfootnote converts Tufte CSS sidenotes back to Markdown footnotes.
mdhtml command
mdhtml renders a Markdown document to standalone HTML.
mdhtml renders a Markdown document to standalone HTML.
mdinline command
mdinline converts reference-style Markdown links to inline links.
mdinline converts reference-style Markdown links to inline links.
mdjoin command
mdjoin joins Markdown sentences into single-line paragraphs.
mdjoin joins Markdown sentences into single-line paragraphs.
mdref command
mdref converts inline Markdown links to reference-style links.
mdref converts inline Markdown links to reference-style links.
mdsidenote command
mdsidenote converts Markdown footnotes to Tufte CSS sidenotes.
mdsidenote converts Markdown footnotes to Tufte CSS sidenotes.
mdsplit command
mdsplit splits Markdown paragraphs into one sentence per line.
mdsplit splits Markdown paragraphs into one sentence per line.
mdtable command
mdtable normalizes GFM table column widths so every cell in a column is padded to equal width, making tables visually aligned in plain text.
mdtable normalizes GFM table column widths so every cell in a column is padded to equal width, making tables visually aligned in plain text.
mdunwrap command
mdunwrap unwraps Markdown paragraphs to single long lines.
mdunwrap unwraps Markdown paragraphs to single long lines.
mdwrap command
mdwrap wraps Markdown paragraphs to a specified width (default 60).
mdwrap wraps Markdown paragraphs to a specified width (default 60).
internal
cli
Package cli provides common I/O utilities for md-tools binaries.
Package cli provides common I/O utilities for md-tools binaries.
markdown
Package markdown provides shared utilities for Markdown parsing and tokenization.
Package markdown provides shared utilities for Markdown parsing and tokenization.
render
Package render converts Markdown into HTML documents.
Package render converts Markdown into HTML documents.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL