mdtool

command module
v0.0.0-...-130e1b4 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 7 Imported by: 0

README

mdtool

mdtool renders Markdown-based documents with Go templates, generates Markdown TOCs, and converts Markdown or HTML into PDF output. HTML output remains available as a debug path.

Toolchain

This repository is pinned for mise:

mise install

The repo sets GOPRIVATE=gitlab.com/pidrakin/* in .mise.toml and .envrc, and pins the local GoReleaser and SBOM tooling used by the release-check workflow.

Common development entrypoints are exposed as mise tasks:

mise run build
mise run vet
mise run test
mise run test-race
mise run release-check

mise run build writes the local binary to ./.tmp/mdtool with the same version/build-time linker variables used for local development.

Installation

Native binaries are released for:

  • linux/amd64
  • linux/arm64
  • darwin/arm64
  • windows/amd64

Homebrew uses the shared tap that also hosts dotfiles:

brew tap pidrakin/dotfiles
brew install --cask pidrakin/dotfiles/mdtool

If you previously installed the old formula, migrate with:

brew uninstall mdtool
brew install --cask pidrakin/dotfiles/mdtool

Container images are Linux-only multi-arch:

  • linux/amd64
  • linux/arm64

Pull the version tag you want:

docker pull registry.gitlab.com/pidrakin/mdtool:<tag>

Commands

# parse a template into markdown
mdtool template example.md

# render template output directly to HTML
mdtool template example.md --convert --html

# add or update a markdown TOC
mdtool toc example.md

# convert markdown or HTML to PDF
mdtool convert example.md

# eject the project config template into ./config.yaml
mdtool eject config.yaml

Default output files:

  • template example.md writes example.parsed.md
  • toc example.md writes example.toc.md
  • convert example.md writes example.pdf
  • convert example.md --html writes example.html

--dry-run is a strict no-write mode for mutating commands. It prints the planned action and does not emit rendered payloads.

Configuration

mdtool resolves configuration in this order:

  1. ./config.yaml
  2. $XDG_CONFIG_HOME/mdtool/config.yaml
  3. legacy $HOME/.config/mdtool.yaml
  4. /etc/mdtool/config.yaml
  5. embedded defaults

Config keys:

  • logTarget: log destination
  • browserBinary: optional explicit Chromium/Chrome binary path

To start from the embedded project config:

mdtool eject config.yaml

Template and TOC behavior

  • include is sandboxed to the root document directory tree. Absolute paths and path escapes outside that tree are rejected.
  • --template-data key=value splits on the first = only, so values may contain spaces.
  • template --toc fails if the source does not contain a [[TOC]] directive.
  • TOC directives document fromLevel and toLevel; the legacy fromHeading and toHeading spellings are still accepted.

PDF and HTML rendering

  • convert --html always emits one complete HTML document.
  • PDF generation uses a Chromium renderer driven from Go via CDP.
  • Math is hydrated in-browser with an embedded KaTeX runtime before HTML/PDF output is finalized.
  • Binary releases are built for linux/amd64, linux/arm64, darwin/arm64, and windows/amd64.
  • Container images bundle Chromium, required fonts, Java, and PlantUML for the supported Linux target and run as an unprivileged mdtool user by default.
  • Raw binaries expect a compatible Chrome/Chromium installation on the host.
  • The Homebrew delivery path is an unsigned cask with a postflight xattr -dr com.apple.quarantine workaround generated into the tap.
  • On Windows, browser auto-discovery checks standard Chrome and Edge install locations first and then falls back to PATH.
  • Documents that contain plantuml code fences also require the plantuml executable and a Java runtime in PATH; if they are missing, mdtool exits with a clear error.
  • MDTOOL_BROWSER or browserBinary can point to an explicit browser binary when auto-discovery is not sufficient.

The bounded renderer replacement evaluation for this cycle is documented in docs/renderer-feasibility.md.

Container runtime

Recommended secure invocation when the document only uses local assets:

mkdir -p out

docker run --rm \
  --user "$(id -u):$(id -g)" \
  --read-only \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --pids-limit=256 \
  --tmpfs /tmp:rw,nosuid,nodev,size=1g \
  --network=none \
  --mount type=bind,src="$PWD",dst=/src,ro \
  --mount type=bind,src="$PWD/out",dst=/out \
  registry.gitlab.com/pidrakin/mdtool:<tag> \
  convert /src/example.md --outfile /out/example.pdf

Runtime notes:

  • --tmpfs /tmp is required if you use --read-only, because mdtool stages source files and the Chromium profile under /tmp.
  • --network=none is appropriate only when the document does not intentionally load remote assets.
  • --user "$(id -u):$(id -g)" keeps output file ownership aligned with the calling user on bind mounts.
  • In containers, mdtool disables Chromium's internal sandbox and relies on the container runtime hardening flags instead. Keep --cap-drop=ALL, --security-opt=no-new-privileges, --read-only, and --network=none unless the document truly needs more access.
  • When the configured file log target is not writable under --read-only, mdtool falls back to stderr logging instead of exiting.
  • Files written under /tmp do not survive container exit when /tmp is a tmpfs. Use a separate writable bind mount or volume for output if you need the PDF after the process exits.
  • If you need tighter filesystem separation, mount sources read-only and mount a separate writable output directory or volume.

Release workflow

  • beta is the integration branch.
  • GitLab CI runs go test ./..., go vet ./..., go test -race ./..., goreleaser check, and a snapshot GoReleaser build.
  • Non-tag commits on release branches run semantic-release to update CHANGELOG.md and create tags.
  • Tag pipelines run the pinned GoReleaser release job and publish the native binaries, the Linux multi-arch container image, archive SBOMs, and the generated Homebrew cask artifact.
  • Tag pipelines prepare buildx plus binfmt so one Linux runner can publish linux/amd64 and linux/arm64 image variants plus the manifest tag.
  • The Homebrew cask is published to the shared pidrakin/homebrew-dotfiles tap after a successful tagged release. That step requires BREW_TAP_GITHUB_TOKEN.
  • The old formula in the tap should be manually disabled with replacement_cask: "mdtool" during the migration so existing users get a clear upgrade hint.
  • The bump job expects a protected, masked CI variable named SEMANTIC_RELEASE_PK containing the base64-encoded private SSH deploy key used to push the release commit and tag.
  • The corresponding public deploy key must be added to the project as a read-write deploy key, and it must be allowed to push to beta if beta is protected.

For a local release check:

bin/build.sh

That script starts a disposable local docker:dind daemon, runs the pinned GoReleaser container against it, executes goreleaser check, and then performs a snapshot release build with the same dockers_v2/SBOM path used in CI.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
cli
toc
cmd
toc
internal

Jump to

Keyboard shortcuts

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