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 ¶
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.2.0"
Version is stamped into the provenance marker.
Variables ¶
This section is empty.
Functions ¶
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 directory prefixes that must never be entered. Each
// value is either absolute or relative to the resolved base. A path at
// or beneath one 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
// 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. Three things report so far: a front
// matter block that is not flat key: value, a container naming a kind
// that does not exist, 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 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. It accepts the brace-free "::: kind" form as an alias for "::: {.kind}", maps the shipped kinds onto their stylesheet classes, warns when a brace-free 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.
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 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.