Documentation
¶
Overview ¶
Package md2html converts Markdown documents to HTML with tree-level transforms that reach hand-written raw HTML as well as generated markup.
Index ¶
- Constants
- func Convert(src []byte, opt Options) ([]byte, error)
- func InstallSkill(parent string) ([]string, error)
- func IsLangTag(s string) bool
- func IsMarkdownPath(p string) bool
- func IsOurs(path string) (bool, error)
- func Marker() string
- type CrawlOptions
- type CrawlResult
- type Doc
- type Link
- type LinkKind
- type Options
- type Transform
- func Alerts() Transform
- func Builtins() []Transform
- func Chips() Transform
- func Containers(warn func(string)) Transform
- func ExternalLinks() Transform
- func HeadingAnchors() Transform
- func LinkAttrs(warn func(string)) Transform
- func LinkRewrite(m map[string]string) Transform
- func SectionLinks() Transform
- func TOC() Transform
- func TableScroll() Transform
- type Warning
- type WriteResult
Constants ¶
const MarkerPrefix = "<!-- generated by https://github.com/AdamF-G/md2html"
MarkerPrefix is the stable portion of the provenance marker. Detection matches this prefix only, so files written by older versions are still recognized. It carries the full repository URL rather than the bare tool name, so output from an unrelated tool also called md2html is never mistaken for ours.
const Version = "v0.6.0"
Version is stamped into the provenance marker.
Variables ¶
This section is empty.
Functions ¶
func InstallSkill ¶ added in v0.3.0
InstallSkill writes the Claude Code authoring skill into a "md2html-authoring" directory below parent, creating it as needed, and returns the paths written in a stable order.
parent is a skills directory — "<something>/.claude/skills". Whether that something ought to exist is the caller's question to answer, not this one's: the CLI refuses to invent a missing .claude, because a skill installed under a directory nobody reads is worse than no skill at all.
Every file carries this tool's provenance marker, so re-installing over an earlier version is silent while a copy someone has edited is refused by name. The refusal is whole: every destination is checked before anything is written, so a run that refuses leaves the directory exactly as it found it rather than holding one file from this version beside one the user wrote. That check is the reason this lives here rather than in the command — a caller assembling the same install from a bag of file contents would have to know to do it, and a caller who forgot would get half-written skill directories with no sign anything was wrong.
func IsLangTag ¶ added in v0.6.0
IsLangTag reports whether s is shaped like a BCP 47 language tag, the check Options.Lang is held to. It is exported so a caller taking a language from its own user, as the CLI's --lang does, can refuse a bad one up front instead of getting a warning per document.
func IsMarkdownPath ¶
IsMarkdownPath reports whether p names a Markdown document.
Types ¶
type CrawlOptions ¶
type CrawlOptions struct {
// Entries are the starting points: files, directories, or both.
Entries []string
// OutDir is the output root. Empty means in-place.
OutDir string
// Depth bounds how many directory levels a directory entry seeds.
// 0 seeds only files directly inside it; -1 is unlimited. It never
// affects link traversal; LinkDepth, below, bounds that instead.
Depth int
// NoMdLinks leaves document links unrewritten.
NoMdLinks bool
// NoAssets leaves asset links unrewritten.
NoAssets bool
// Exclude lists what must never be entered. A value containing *, ? or
// [ is a filepath.Match pattern tested against each file and directory
// name below the resolved base, so "AUDIT_*" names documents at any
// depth; any other value is a directory prefix, either absolute or
// relative to base. A path at or beneath a prefix, or with a name
// matching a pattern, is never seeded, never followed as a link target,
// and never written to; a link pointing at one keeps its href exactly as
// written, the same handling a link escaping base gets in in-place mode.
//
// This exists for subtrees some other tool already owns — a slide-deck
// renderer, a vendored dependency's own generated docs, a frozen
// archive. Without it the only way to keep the crawler out of one is to
// move it out of the source tree, which is rarely possible.
Exclude []string
// LinkDepth bounds how far link-following may travel from a seed,
// counted in hops: a document a seed links to is one hop, one it links
// to in turn is two.
//
// It reads exactly as Depth does: a non-negative value is the bound
// itself, and -1 is unlimited. 0 therefore follows no links at all,
// which is what makes a zero-valued CrawlOptions coherent rather than
// merely conservative — Depth's zero already seeds a directory's own
// files and none of its subdirectories, so both fields describe the
// smallest possible run, and the CLI asks for unlimited on both.
LinkDepth int
}
CrawlOptions configures a discovery pass.
type CrawlResult ¶
type CrawlResult struct {
// Docs is the emit set: every document reachable from the entry points.
Docs []Doc
// Base is the deepest directory containing every entry point.
Base string
// External lists the resolved absolute paths of documents pulled in
// from outside Base.
External []string
// Warnings are the non-fatal problems found during the crawl.
Warnings []Warning
}
CrawlResult is the outcome of a discovery pass.
func Crawl ¶
func Crawl(opt CrawlOptions) (*CrawlResult, error)
Crawl discovers every document reachable from the entry points.
type Doc ¶
type Doc struct {
// Src is the resolved absolute source path.
Src string
// Out is the absolute destination path for the generated HTML.
Out string
// LinkMap maps an href exactly as written in this document to its
// replacement. Populated by buildLinkMaps.
LinkMap map[string]string
}
Doc is one document in the emit set.
type Link ¶
type Link struct {
// Href is the value exactly as written in the source.
Href string
// Kind classifies the link target.
Kind LinkKind
// Abs is the resolved absolute path, for LinkDoc and LinkAsset only.
// Any #fragment has been stripped.
Abs string
// Node is the element carrying the link, so a transform can rewrite it.
Node *html.Node
// Attr is the attribute on Node carrying the link ("href" or "src").
Attr string
}
Link is one href or src found in a document.
type Options ¶
type Options struct {
// Fragment emits Artifact shape (marker, title, style, body) instead of
// a full HTML document.
Fragment bool
// Title overrides the derived title. Empty means derive from the first
// <h1>, falling back to SourcePath's base name.
Title string
// SourcePath is the absolute path of the source document. Used for the
// title fallback and diagnostics.
SourcePath string
// CSS replaces the embedded default stylesheet. Empty uses the default.
CSS string
// MermaidURL is the ES module a page containing a mermaid diagram
// imports at view time. Empty uses the pinned CDN build.
//
// It is the only thing a generated page ever fetches, so this is the
// knob for a docs build that must not reach a CDN: point it at a copy
// you serve yourself. Nothing else changes — a page with no diagram
// still imports nothing, and Fragment output never imports at all,
// because Artifacts render mermaid themselves.
MermaidURL string
// Lang is the page's language, written as <html lang>. A document's own
// "lang:" front matter key overrides it; empty means "en". A value not
// shaped like a BCP 47 language tag is warned about and skipped in
// favor of the next source. Fragment output has no <html> element, so
// it ignores both.
Lang string
// Transforms to run. Nil means the default list.
//
// A list supplied here is used as given, except that any builtin in it
// that reports diagnostics is rebuilt against Warn — so appending to
// Builtins() keeps every warning a default conversion would have
// raised, without the caller naming the sink twice. The caller's own
// slice is never written to.
//
// Transforms and LinkMap are two alternative routes to link rewriting,
// not complementary ones: either place LinkRewrite in Transforms
// yourself, or set LinkMap and let Convert do it. Doing both rewrites
// every href twice — see LinkMap.
Transforms []Transform
// LinkMap maps an href exactly as written in the source to its
// replacement. Populated by the crawler; nil for standalone conversion.
//
// Setting this makes Convert append LinkRewrite(LinkMap) to the
// transform list itself, so callers must not also put LinkRewrite in
// Transforms. Two passes over one map corrupt output whenever a
// replacement is itself a key: a document linking both ./a.md (mapped
// to a.html) and an existing ./a.html asset (mapped to the original
// file) would have the first rewrite turned into the second.
LinkMap map[string]string
// Warn, when non-nil, receives one message per non-fatal problem found
// while converting this document. Things that report so far: a front
// matter block that is not flat key: value, a language that is not a
// language tag, a container naming a kind that does not exist or
// missing the definition list it is defined to hold, and a fig fence
// whose body does not parse or does not validate.
//
// Convert never writes to stderr itself: it is a library, and the CLI
// emits every document's output in parallel, so a transform printing
// directly would interleave with other documents' lines. The callback
// is invoked synchronously on the calling goroutine, so a caller may
// append to an unsynchronized per-document slice.
//
// Every source reaches it by the same route whatever Transforms holds:
// Convert raises the front matter warning itself, the fig fence warning
// comes from the renderer, and a transform that reports is rebuilt
// against this sink before it runs (see Transforms).
Warn func(string)
}
Options controls a single document conversion.
type Transform ¶
type Transform struct {
// Name identifies the transform, e.g. for logging or diagnostics.
Name string
// Fn is the function applied to the parsed HTML tree.
Fn func(*html.Node) error
}
Transform mutates a parsed HTML tree in place. Fn receives the synthetic root node whose children are the document's top-level elements.
func Alerts ¶ added in v0.3.0
func Alerts() Transform
Alerts converts GitHub alert blockquotes into the shipped containers.
> [!WARNING] > Overwrites state.
becomes exactly what "::: warning" produces.
This is the one callout spelling GitHub, Obsidian, Typora and Pandoc's gfm and commonmark_x readers all understand, and the only one that renders correctly in the place md2html's input actually lives: a repository. "::: warning" shows up on GitHub as the literal text "::: warning".
It degrades the right way too. A renderer that does not know the syntax shows an ordinary blockquote with a visible marker, rather than the unstyled div a stray ::: fence leaves behind.
func Builtins ¶
func Builtins() []Transform
Builtins returns the transforms enabled by default.
It takes no arguments and reports nothing: it is the documented public door for callers assembling their own transform list (see README), and changing its signature would break them. A list built from it still reports — Convert rebuilds the entries in warnAware against Options.Warn before running them — so the nil sink here costs a caller nothing.
func Chips ¶
func Chips() Transform
Chips turns a fixed set of bracketed tokens into small styled badges, everywhere inline Markdown is rendered — body text and headings alike, since a status marker on a heading is the case the convention exists for.
A heading's marker is kept out of its slug by HeadingAnchors, which is why this transform must run before it.
func Containers ¶
Containers normalizes fenced containers. Acceptance of the brace-free "::: kind" and label "kind[Title]" forms happens upstream, in the parser; this transform maps a kind name handed to it via data-fence-kind onto the shipped kind's element and stylesheet classes, warns when that name is not one of them, and drops the fence library's internal data-fence attribute.
warn may be nil.
func ExternalLinks ¶
func ExternalLinks() Transform
ExternalLinks marks off-site links so they open in a new tab without leaking the referring window. rel="noopener noreferrer" is added to any rel tokens the link already has, and a target the author chose is left alone.
func HeadingAnchors ¶
func HeadingAnchors() Transform
HeadingAnchors gives every heading a stable id and a linkable anchor. An id already present — from a {#custom-id} attribute — is left alone.
Status chips are excluded from the slug: a marker is metadata about the section, not part of its name, and relabeling one later must not rot an anchor other documents already link to.
func LinkAttrs ¶ added in v0.6.0
LinkAttrs applies Pandoc's link_attributes: a {#id .class key=value} block written directly after a link or an image sets those attributes on the <a> or <img>, and is removed from the text.
goldmark has no parser for it, so the block reaches the tree as the start of the text node after the element. Working on the tree is also what lets an aria-* attribute through: goldmark's own attribute allowlist drops them, and this sets attributes directly.
The block must touch the element, as in Pandoc; with a space between them it is prose. One holding no attributes, or a name that is not a safe attribute name, stays as literal text, as "[x]{}" does for a bracketed span.
href, src and srcset are refused, with a warning. LinkRewrite looks links up by their target exactly as the source wrote it, so a block replacing one would route the link around the .md-to-.html rewrite. The block itself can never become part of that key: it is a separate text node, never in the attribute.
warn may be nil.
func LinkRewrite ¶
LinkRewrite replaces hrefs and srcs using a map keyed by the link exactly as written in the source document. Links absent from the map are left untouched, which is how remote URLs, fragments, and deliberately unrewritten links survive.
func SectionLinks ¶
func SectionLinks() Transform
SectionLinks autolinks bare "§N.M" references to the heading in the same document numbered N.M.
It is a transform rather than a goldmark extension because it needs the finished heading set, ids included, to resolve against — which only exists after HeadingAnchors has run.
Left alone: a § inside a code span, code block or existing link (handled by rewriteText, via splitMatches only ever touching prose text nodes); a § whose number matches no heading here; and a possessive reference scoping the section to another document, as in "the design doc's §7". That last rule is narrow — it catches the possessive phrasing and nothing else — so a cross-document reference written any other way still needs a code span to opt out.
func TOC ¶
func TOC() Transform
TOC replaces a marker paragraph with a flat list of the current document's own headings.
Flat, not nested by heading level: a document that jumps h2 to h4 would otherwise produce either invalid list nesting or a silently wrong tree. Level travels as a class on the list item, so the stylesheet can indent without the markup having to be a hierarchy.
This is a table of contents for one page and nothing more. Cross-document navigation, a sidebar and a site index stay out of scope — see docs/specs/2026-09-11-extended-content-model.md, item 6.
func TableScroll ¶
func TableScroll() Transform
TableScroll wraps every table in a horizontally scrollable container, so wide tables never force the page body to scroll sideways. It reaches hand-written tables in raw HTML as well as generated ones.
type Warning ¶
type Warning struct {
// Src is the document the warning was raised while processing, or, for
// a warning with no document to point at (an --exclude value that
// matches nothing on disk), the offending value itself.
Src string
// Message describes the problem.
Message string
}
Warning is a non-fatal problem found during a run.
type WriteResult ¶
type WriteResult int
WriteResult reports what SafeWrite did.
const ( // WriteCreated means no file existed at the destination. WriteCreated WriteResult = iota // WriteOverwritten means an earlier generated file was replaced. WriteOverwritten // WriteRefused means a file we did not write was left untouched. WriteRefused )
func SafeWrite ¶
func SafeWrite(path string, data []byte) (WriteResult, error)
SafeWrite writes data to path, refusing to destroy any file this tool did not generate. Parent directories are created as needed.
A refusal is a WriteRefused result, not an error: the caller continues with other files and reports the refusal in the run summary.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
md2html
command
Command md2html converts Markdown documentation trees to HTML.
|
Command md2html converts Markdown documentation trees to HTML. |
|
internal
|
|
|
fences
Package fences implements the ::: fenced container syntax.
|
Package fences implements the ::: fenced container syntax. |