main.zig
CLI entry point: options parsing, progress reporting, extract, render, and write output.
pub const codelinks = struct pub const PendingCodelink = struct A reference that resolved to an import binding, awaiting Phase B.
pub fn deinit(self: PendingCodelink, gpa: std.mem.Allocator) void pub const ResolvedCodelinks = struct pub fn deinit(self: ResolvedCodelinks, gpa: std.mem.Allocator) void pub const Binding = union(enum) A binding in scope: a same-file member or an import alias.
pub const BoundName = struct A binding plus the byte range of its own declaration name token, so the resolver can recognize a declaration site vs. a use.
pub const ScopeFrame = struct A byte range over which bindings are in effect. Searched innermost-first.
pub fn buildFileScopes(
gpa: std.mem.Allocator,
tree: Ast,
members: []const Ast.Node.Index,
parentPath: []const u8,
rangeStart: u32,
rangeEnd: u32,
frames: *std.ArrayList(ScopeFrame),
) std.mem.Allocator.Error!void Builds the scope frame stack for a container's Ast region: a root frame of members' bindings and imports scoped to [rangeStart, rangeEnd), plus one child frame per nested container or function. parentPath is the dotted path prefix for members.
The range matters: without it, two sibling containers declaring the same-named local (common with const BitSet = ...; across generic containers) would have both bindings compete for every use of that name in the whole file.
pub fn freeScopeFrames(gpa: std.mem.Allocator, frames: *std.ArrayList(ScopeFrame)) void Frees every frame's bindings, including owned strings.
pub fn resolveCodelinksForFile(gpa: std.mem.Allocator, tree: Ast, frames: []const ScopeFrame) !ResolvedCodelinks Like resolveCodelinksForDecl, scoped to the entire file (byte range [0, tree.source.len)) rather than one decl — for a whole-file wrapper Section whose own source is the full file, not a decl's trimmed slice. Returned offsets are absolute (relative to byte 0).
pub fn resolveCodelinksForDecl(
gpa: std.mem.Allocator,
tree: Ast,
declStartByte: u32,
declEndByte: u32,
frames: []const ScopeFrame,
) !ResolvedCodelinks Resolves every identifier use within declNode's source span against frames. Returned offsets are relative to declStartByte.
pub const ExtractError = error Errors surfaced while extracting a DocTree from source.
pub const DocCommentResult = struct Result of extractDocComment/extractFieldDocComment: the joined comment text, and whether it came from extractPlainCommentFallback rather than a real /// doc comment.
pub fn parseImportTargets(gpa: std.mem.Allocator, source: [:0]const u8) ExtractError![]ImportRef Parses source and returns the @import targets referenced by its top-level decls, without building a DocTree. Used by import-graph discovery to find edges before deciding whether a file is worth extracting at all.
pub fn freeImportTargets(gpa: std.mem.Allocator, refs: []ImportRef) void Frees a slice returned by importTargets/parseImportTargets.
pub fn extractFile(
gpa: std.mem.Allocator,
moduleName: []const u8,
sourcePath: []const u8,
source: [:0]const u8,
recursive: bool,
extras: bool,
collectTests: bool,
) ExtractError!model.DocTree Parses source and extracts a DocTree for moduleName. sourcePath is recorded on each Section verbatim, shown to the reader as-is — pass a document-root-relative path, not a raw CLI path.
pub fn collectTestSource(gpa: std.mem.Allocator, tree: Ast, members: []const Ast.Node.Index) ![]const u8 Collects every top-level test { ... } block's verbatim source, concatenated in source order with a blank line between blocks.
pub fn extractModuleDocComment(gpa: std.mem.Allocator, tree: Ast) !?[]const u8 Collects leading //! lines at the top of the file, if present.
pub const FieldsParamsErrors = struct The parts of a Section that come from a struct/union/fn decl's own members: fields, params, error-set members, and whether a fields-less struct downgrades to .namespace_decl (never for a union). Shared by collectMembers and imports.buildSection.
pub fn collectFieldsParamsErrors(
gpa: std.mem.Allocator,
tree: Ast,
kind: model.Kind,
fnNode: Ast.Node.Index,
containerNodes: ?[]const Ast.Node.Index,
declStartByte: u32,
extras: bool,
allowNamespaceDowngrade: bool,
) !FieldsParamsErrors fnNode is the decl's own content node — used for .fn_decl params and errors, and for the standalone-error-set check; ignored otherwise. containerNodes, when given, is fnNode's struct/union member nodes. allowNamespaceDowngrade is false for a type function's returned container, which keeps its own declared kind rather than downgrading.
pub fn collectFields(gpa: std.mem.Allocator, tree: Ast, members: []const Ast.Node.Index, declStartByte: u32) ![]model.Field Collects a container's field nodes into Fields, in source order.
pub fn collectParams(gpa: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, declStartByte: u32) ![]model.Param Collects a fn_decl/fn_proto* node's parameters into Params.
pub fn isErrorSetDecl(tree: Ast, node: Ast.Node.Index) bool Whether a top-level member is a standalone named error-set decl (pub const MyErrors = error{...};).
pub fn collectStandaloneErrorSet(gpa: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) ![]model.ErrorMember Collects a standalone error-set decl's own members (error{ A, B }) into ErrorMembers.
pub fn collectFnErrors(gpa: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) ![]model.ErrorMember Resolves a function's declared error-union return type (MyError!void) to its error-set decl and collects its members. Returns empty for an inferred error union (bare !T) or a plain return type with no !.
pub fn declLine(tree: Ast, token: Ast.TokenIndex) u32 Returns the 1-based source line a token starts on. tokenLocation's .line is 0-indexed, hence + 1.
pub fn containerMembers(gpa: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) !?[]const Ast.Node.Index Returns the member node list of a container-typed decl's initializer (struct/enum/union body), if the decl's value is itself a container. Copies members into a newly allocated slice (caller frees), since fullContainerDecl's compact form may borrow from scratch space.
pub fn hasFieldMember(tree: Ast, members: []const Ast.Node.Index) bool Whether any of members is a struct/union field, independent of --extras. Zig's convention for a real data type vs. a namespace.
pub const ImportRef = struct A top-level const/var decl whose initializer is @import("target").
pub fn importTargets(gpa: std.mem.Allocator, tree: Ast, members: []const Ast.Node.Index) ![]ImportRef Scans members for decls initialized by an @import(...) call and returns one ImportRef per match, in declaration order.
pub fn containerKind(tree: Ast, containerNode: Ast.Node.Index) ?model.Kind Classifies a container node (struct/union/enum/opaque) by its keyword token.
pub fn declKind(tree: Ast, node: Ast.Node.Index) model.Kind pub fn isPubDecl(tree: Ast, node: Ast.Node.Index) bool Whether a top-level container member is marked pub.
pub fn extractDocComment(gpa: std.mem.Allocator, tree: Ast, nameToken: Ast.TokenIndex, isPub: bool) !?DocCommentResult Collects contiguous /// lines immediately preceding a declaration. isPub: when false and none found, falls back to a leading // comment.
pub fn extractSignature(gpa: std.mem.Allocator, tree: Ast, node: Ast.Node.Index) ![]const u8 Renders a decl's signature: its source span up to the body's {.
pub fn sliceForNode(tree: Ast, node: Ast.Node.Index) []const u8 Returns the full source slice spanned by node, including leading indentation on the first token's line.
pub fn lineStart(source: [:0]const u8, pos: usize) usize Walks back from pos over spaces/tabs to the start of the indentation on that line, stopping at a newline or byte 0.
Simple module with one documented function.
pub fn add(a: i32, b: i32) i32 Adds two integers.
Stand-in for std/mem/Allocator.zig: the actual documented content, reached only via two hops of bare re-export. Real @This()-style files like Zig's own Allocator.zig have no nested decl of the same name inside them -- the file itself is what a chain bottoms out on.
pub fn create() void Stand-in for std/mem.zig: re-exports a decl that lives in its own file.
pub const Allocator = @import("codelink_chain_allocator.zig") Stand-in for std.zig: re-exports a submodule via a bare @import.
pub const mem = @import("codelink_chain_mem.zig") A file that imports the root and takes a two-hop dotted shortcut, matching the const Allocator = std.mem.Allocator; idiom used throughout Zig's own standard library.
Leaf module exposing a documented decl, reached only via @import.
pub const Allocator = struct Documented so it has its own page to link to.
Root of a small import fixture for cross-file codelinks.
pub const Allocator = mem.Allocator Re-exported through a dotted chain that crosses an @import.
See codelink_mention_target.zig for the implementation.
pub const note = "consult codelink_mention_target.zig for details" Fixture reproducing a crash where a filename mention's byte offset (valid against the whole decl source) was wrongly reused against the much shorter signature string.
pub const Fe = struct A struct whose doc comment mentions codelink_mention_target.zig deep into a long block of prose, well past where this struct's own short one-line signature would end if the mention's offset were reused there.
Just a documented decl for codelink_mention_root.zig to link to.
pub const Target = struct A module reached from the root via a relative re-export, holding the decl the package-self-import chain resolves to.
pub const SomeType = struct Root module for the package self-import test.
pub const zon_parse = @import("codelink_pkg_parse.zig") References the root by its own package name (@import("codelink_pkg_std"), not a relative .zig path), then hops through a relative re-export to reach a decl in a different file — mirroring const Allocator = std.mem.Allocator; in Zig's real standard library, where std is brought in via @import("std").
pub const Allocator = struct pub const Tz = struct pub fn deinit(self: *Tz) void pub const mem = @import("codelink_void_mem.zig") Leaf module reached only via a direct, suffix-less @import.
pub const Marker = struct Documented so it has its own page to link to.
Root of a fixture for whole-file source-pane codelinks.
pub const Leaf = @import("codelink_wholefile_leaf.zig") Module exposing a single documented struct.
pub const Point = struct A point in 2D space.
A generic container type, in its own file, instantiated directly by the root. Its returned struct and that struct's own methods must stay inlined on whichever page instantiates it — none of them is an @import boundary in its own right.
pub fn Container(comptime T: type) type Generic container over T.
Root module that instantiates a generic type imported from another file. Box is not an @import boundary itself (it's an ordinary decl on this page); its body's real sourceFile is the imported file, not this one.
pub const Box = generic_container.Container(u32) Instantiated container of u32.
pub const Plain = struct An ordinary same-file struct, for contrast.
pub fn touch(self: Plain) void A same-file method.
pub const T = struct A real decl whose bare name collides with the generic container's own comptime T: type parameter, over in generic_container.zig. That parameter must never itself resolve to a link (it shadows, it isn't a decl) — this collision is what let it fall through to this decl's bare-name table entry instead and render as a dead self-link in Container's own signature.
Leaf module with no further imports.
pub fn greet() void A documented function.
Left branch of a diamond import.
Right branch of a diamond import.
Root of a small import graph fixture.
pub const child = @import("import_graph_child.zig") pub const left = @import("import_graph_diamond_left.zig") pub const right = @import("import_graph_diamond_right.zig") pub fn ownDecl() void Declared directly on the root, not via an import.
Module with several small sibling structs, each holding its own nested decls, to exercise the compact two-member container shape repeatedly and at some depth.
pub const Alpha = struct First small container.
pub const one = 1 A constant on Alpha.
pub const two = 2 Another constant on Alpha.
pub const Beta = struct Second small container.
pub fn double(x: i32) i32 A function on Beta.
pub const Gamma = struct Third small container, nested two levels deep.
pub const Delta = struct Nested inside Gamma.
pub const value = 3 A constant on Delta.
pub fn triple(x: i32) i32 A function on Delta.
Module whose doc comments exercise the markdown dialect.
pub fn format(value: i32) i32 Formats a value using std.fmt.
Supports:
Example:
const s = format(42);
A module with only a top-level doc comment and no public declarations.
Module with a struct containing a nested struct.
pub const Outer = struct Outer container.
pub const Inner = struct Inner container, nested inside Outer.
pub const value = 42 A documented constant on the inner struct.
The Time Zone Information Format (TZif) https://datatracker.ietf.org/doc/html/rfc8536
pub const Transition = struct pub const Timetype = struct pub fn name(self: *const Timetype) [:0]const u8 pub fn isDst(self: Timetype) bool pub fn standardTimeIndicator(self: Timetype) bool pub fn utIndicator(self: Timetype) bool pub const Leapsecond = struct pub const Tz = struct pub fn parse(allocator: Allocator, reader: *Reader) !Tz pub fn deinit(self: *Tz) void Module with one undocumented function.
pub fn bare() void Discovers a --discover ns doc tree by following @import(...) targets from a root file, then builds model.Sections from the resulting walk.Graph — resolving .alias decls (re-exports) to their real target's content, the way Zig's own Autodoc does.
pub const WalkError = error Errors surfaced while walking an import graph.
pub const Reader = struct Reads a file's full contents into a null-terminated buffer, so walkTree can be tested against in-memory sources.
pub fn walkDisk(
gpa: std.mem.Allocator,
io: std.Io,
rootPath: []const u8,
recursive: bool,
extras: bool,
collectTests: bool,
progress: ?*progress_mod.Progress,
) WalkError!model.DocTree Walks the whole import graph and builds a model.DocTree rooted at rootPath, nested rather than a flat sibling list. progress, if given, is updated during discovery and again before tree-building.
pub fn walkTree(
gpa: std.mem.Allocator,
reader: Reader,
rootPath: []const u8,
recursive: bool,
extras: bool,
collectTests: bool,
progress: ?*progress_mod.Progress,
) WalkError!model.DocTree Discovers every file reachable from rootPath via @import (already-visited targets, including cycles, aren't re-visited), parses+walks each into a shared walk.Graph, then builds a model.DocTree from the root file, recursively resolving .alias.
pub fn resolveImport(gpa: std.mem.Allocator, importingFile: []const u8, importTarget: []const u8) ![]u8 Resolves an @import string against the importing file's own directory. Works equally on doc-root-relative display paths (as used by render.resolvePendingCodelinks) and on real disk paths (as used by discoverFile) — it's pure lexical path algebra, not filesystem access.
pub fn isRelativeImport(target: []const u8) bool Whether target is a relative .zig file path rather than a package name.
CLI entry point: options parsing, progress reporting, extract, render, and write output.
pub const options = struct pub const Format = enum Output document format.
pub const Split = enum Whether output is one file (none) or split per input file (file) or per item (item). --split ns is an accepted alias for file under --discover ns.
pub const SourceMode = enum How decl source is embedded in HTML output. md only supports none/inline_. tab requires --split item (page-level) or non-none split (page-source level).
pub const TestsMode = enum Same as SourceMode minus tab — used by --tests.
pub const CssMode = enum Whether CSS is embedded in the output or written to a sibling file.
pub const Theme = enum Which color scheme the generated CSS uses. auto follows the OS.
pub const Collapse = enum Which nested lists start collapsed. dir: --tree directory groups. ns: --discover ns per-file namespace roots. top: a page's outermost index list. all: everything collapsible. HTML-only.
pub const ItemOrder = enum How sections are ordered, recursively at every nesting level.
pub const DirOrder = enum Where directories sort relative to files in index/nav listings.
pub const Discover = enum Which strategy finds the files to document: fs walks the filesystem, ns follows @import(...) from a root file.
pub const ShowFlag = enum One category of content --show can turn on or off: file/linenum are a decl's source location, fields/parameters/errorsets are its own members, the rest list its children by kind as links. funcsigs is a rendering variant of functions (signature + doc comment instead of a plain link) and trumps it when both are shown; excluding functions always excludes funcsigs too — see parseShow.
pub const OmitDocFlag = enum A listing category --omitdoc can mark link-less. functions/values decls each have their own page, so marking them also suppresses that page entirely, leaving just the name + doc comment shown in the parent's listing. fields, errors, and params never had a page of their own to begin with, so they're accepted for symmetry but change nothing.
pub const defaultHead = "<link rel=\"shortcut icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAARVBMVEVHcEwAAA0BAQKWWAH7lhH/qAHZ2tv6jwT////8xn9SMQSzbAL/ogj49/X92quSlZoIGzr91J780Jb7rUf+5sf/wwD91aGX+9VwAAAAAXRSTlMAQObYZgAAAMpJREFUeNql0ssSgyAMQFEeiYEQUVra///U1tKFRGeY2ruRxZmRR8xWJujK1XQVoL4CsQORVFnoNgJ0G4BOHEC9UxNHgO+ISqWteAAgntkLUK3lmWOZNRC262pZiO6figKS3BLC4pJQKyqA3k4hTNbjJSCIj9RAeiCKAiDJ+8RuA463tUAHhJ211jXwWbLsAXq3zNO7OYT2XZzHPeA1qFYeg+EvRpscHvOni/r/LcbPrQdGAz1yGuihPYKuC6AAnQbFfMsEJ1E2xpgXnRgXb8riHD8AAAAASUVORK5CYII=\" />" Default --head value: a self-contained favicon <link> as a data URI.
pub const Options = struct Resolved CLI configuration shared by the extractor, renderers, and build.zig integration.
pub fn shows(opts: Options, flag: ShowFlag) bool Whether flag is one of the categories --show turned on.
pub fn collectsExtras(opts: Options) bool Whether extraction should collect a struct/fn/error-set's own members at all — true if any of the three is shown.
pub const OmitKind = enum A decl kind excludable via --omitkind. No directory variant — directory headings are always structural, never a documented item.
pub const ParseError = std.mem.Allocator.Error Allocator-failure-only error surfaced while parsing CLI arguments. Malformed input (unknown options/values, missing values, invalid combinations) is never fatal — it's recorded in Options.parseWarnings and a sane default is used instead.
pub fn parseArgs(gpa: std.mem.Allocator, args: []const []const u8) ParseError!Options Parses CLI arguments (excluding argv[0]) into Options. Resolves source/filename defaults from the final --format once the whole argument list is read. Malformed input never fails parsing: it's recorded in the returned Options.parseWarnings and a sane default is substituted, so the caller can always proceed.
pub const progress_mod = struct pub const Progress = struct pub fn update(self: *Progress, comptime fmt: []const u8, args: anytype) void pub fn clear(self: *Progress) void pub fn main(init: std.process.Init) !void Process entry point.
An abstract tree representation of a Markdown document.
pub const Node = struct pub const Index = enum(u32) pub const List = std.MultiArrayList(Node) pub const Tag = enum pub const Data = union pub const ListStart = enum(u30) The starting number of a list. This is either a number between 0 and 999,999,999, inclusive, or unordered to indicate an unordered list.
pub fn asNumber(start: ListStart) ?u30 pub const TableCellAlignment = enum(u2) pub const Children = struct Trailing: len times Node.Index
pub const ExtraIndex = enum(u32) pub const StringIndex = enum(u32) The index of a null-terminated string in string_bytes.
pub fn deinit(doc: *Document, allocator: Allocator) void pub fn render(doc: Document, writer: *std.Io.Writer) std.Io.Writer.Error!void Renders a document directly to a writer using the default renderer.
pub fn ExtraData(comptime T: type) type pub fn extraData(doc: Document, comptime T: type, index: ExtraIndex) ExtraData(T) pub fn extraChildren(doc: Document, index: ExtraIndex) []const Node.Index pub fn string(doc: Document, index: StringIndex) [:0]const u8 A Markdown parser producing Documents.
The parser operates at two levels: at the outer level, the parser accepts the content of an input document line by line and begins building the block structure of the document. This creates a stack of currently open blocks.
When the parser detects the end of a block, it closes the block, popping it from the open block stack and completing any additional parsing of the block's content. For blocks which contain parseable inline content, this invokes the inner level of the parser, handling the inline structure of the block.
Inline parsing scans through the collected inline content of a block. When it encounters a character that could indicate the beginning of an inline, it either handles the inline right away (if possible) or adds it to a pending inlines stack. When an inline is completed, it is added to a list of completed inlines, which (along with any surrounding text nodes) will become the children of the parent inline or the block whose inline content is being parsed.
pub fn init(allocator: Allocator) Allocator.Error!Parser pub fn deinit(p: *Parser) void pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void Accepts a single line of content. line should not have a trailing line ending character.
pub fn endInput(p: *Parser) Allocator.Error!Document Completes processing of the input and returns the parsed document.
pub fn extraData(p: Parser, comptime T: type, index: ExtraIndex) ExtraData(T) pub fn extraChildren(p: Parser, index: ExtraIndex) []const Node.Index Renders a Document to HTML or plain text.
pub fn Renderer(comptime Context: type) type A Markdown document renderer.
Each concrete Renderer type has a renderDefault function, with the intention that custom renderFn implementations can call renderDefault for node types for which they require no special rendering.
pub fn renderInlineNodeText(
doc: Document,
node: Node.Index,
writer: *Writer,
) Writer.Error!void Renders an inline node as plain text. Asserts that the node is an inline and has no non-inline children.
pub fn fmtHtml(bytes: []const u8) std.fmt.Alt([]const u8, formatHtml) Format-agnostic intermediate representation shared by all renderers.
pub const Kind = enum What kind of thing a Section documents. Directory headings are not a Kind — --tree grouping is synthesized from Section.fileLabel at render time.
pub const Field = struct A struct/union field, extracted for --extras content.
pub const Param = struct A function parameter, extracted for --extras content.
pub const ErrorMember = struct One error set member, extracted for --extras content.
pub const CodelinkTarget = struct One resolved identifier reference inside a Section's own source.
pub const PendingCodelinkTarget = struct A cross-file identifier reference whose target file is known but whose target Section.path isn't resolvable until the whole tree exists.
pub const Section = struct A single extracted declaration, plus its nested children.
pub fn anchorSlug(self: Section, gpa: std.mem.Allocator) ![]u8 HTML id / md fragment anchor for this section.
pub fn anchorSlugMd(self: Section, gpa: std.mem.Allocator) ![]u8 Anchor slug for Markdown output.
pub fn fileStem(self: Section, gpa: std.mem.Allocator) ![]u8 Filename stem for this section's page in split layout.
pub fn isWholeFileWrapper(self: Section) bool Whether this section's source/docComment/testSource describe a whole file.
pub fn slugifyMd(gpa: std.mem.Allocator, path: []const u8) ![]u8 Like slugify but lowercases, matching Markdown's auto-generated heading anchors.
pub const DocTree = struct A module's full set of extracted sections, ready for rendering.
pub fn deinit(self: *DocTree, gpa: std.mem.Allocator) void Frees every allocation owned by this tree.
pub fn fileWrapperSection(gpa: std.mem.Allocator, tree: DocTree, kind: Kind, displayName: []const u8, fileLabel: []const u8, locationPath: []const u8) !Section pub fn rawFileSection(gpa: std.mem.Allocator, moduleName: []const u8, displayName: []const u8, rawLabel: []const u8, content: []const u8) !Section Builds a synthetic per-file Section for a non-.zig input (--filetypes).
pub fn disambiguateSectionPaths(gpa: std.mem.Allocator, sections: []const Section) ![]Section Detects path collisions among top-level file-kind sections and disambiguates them using their real fileLabel. Caller owns the returned slice.
pub fn freeDisambiguatedSections(gpa: std.mem.Allocator, sections: []Section, originals: []const Section) void Frees a slice returned by disambiguateSectionPaths.
pub fn mergeTrees(
gpa: std.mem.Allocator,
moduleName: []const u8,
trees: []DocTree,
labels: MergeLabels,
) !DocTree Merges several file-level DocTrees into one, nesting each under a synthetic module section. Takes ownership of trees.
pub fn mergeNamespaceTrees(gpa: std.mem.Allocator, moduleName: []const u8, trees: []DocTree) !DocTree Merges several --discover ns root DocTrees into one, nesting each under a synthetic Kind.namespace wrapper. Takes ownership of trees.
pub const MergeLabels = struct How mergeTrees labels, kinds, and locates each merged tree's synthetic wrapper section.
pub fn fileDisplayName(gpa: std.mem.Allocator, rawLabel: []const u8, showExt: bool, showDirPrefix: bool) ![]u8 Formats a raw directory-relative label for display, per showExt/showDirPrefix.
pub fn stripZigExt(name: []const u8) []const u8 Strips a trailing .zig extension, if present.
pub fn freeSection(gpa: std.mem.Allocator, section: *Section) void Recursively frees a section and its children.
pub const SectionOrder = enum Mirrors options.ItemOrder; kept here to avoid a dependency on the CLI-parsing module.
pub const DirOrder = enum Mirrors options.DirOrder.
pub const OmitKind = enum Mirrors options.OmitKind.
pub const OmitDocFlag = enum Mirrors options.OmitDocFlag, minus none (an empty excluded slice serves that role).
pub fn markOmitDoc(sections: []Section, excluded: []const OmitDocFlag) void Marks docOnly on every function/value decl whose bucket is in excluded. Never deletes: the decl still gets a listing entry (name + doc comment, unlinked) in its parent's Functions/Values list. What disappears is the decl's own page/anchor and content block — nowhere to link it to, so nothing renders there (see Section.docOnly).
pub fn filterSections(gpa: std.mem.Allocator, sections: *[]Section, excluded: []const OmitKind) !void Recursively drops sections whose kind is in excluded. A dropped .file wrapper splices its children into its place; other kinds take their children with them. In-place.
pub fn filterPrivate(gpa: std.mem.Allocator, sections: *[]Section) !void Recursively drops sections whose isPub is false, along with their children. In-place.
pub fn filterEmpty(gpa: std.mem.Allocator, sections: *[]Section) !void Recursively drops a .file section once it has no doc comment and no children left. Never drops raw sections (--filetypes), which are always childless and doc-less.
pub fn sortSections(sections: []Section, order: SectionOrder) void Recursively sorts sections in place per order. code is a no-op (already AST order).
pub fn slugify(gpa: std.mem.Allocator, path: []const u8) ![]u8 Replaces non-alphanumeric bytes with "-", preserving "." as the path separator and case (HTML anchors are case-sensitive, unlike slugifyMd).
pub fn filenameSegment(gpa: std.mem.Allocator, name: []const u8) ![]u8 Filesystem-safe form of a single decl name for a page filename or directory segment; case is preserved since Zig identifiers are case-sensitive.
pub fn avoidWindowsReservedName(gpa: std.mem.Allocator, segment: []u8) ![]u8 Appends a trailing "~" if segment case-insensitively matches a Windows-reserved name.
pub fn pageDepth(pagePath: []const u8) usize Number of directory levels deep a page's output path sits.
pub fn relativeHref(gpa: std.mem.Allocator, fromPath: []const u8, toPath: []const u8, prettyUrls: bool) ![]u8 Builds a relative href from fromPath to toPath, both output-relative. Drops a trailing index.html when prettyUrls is set.
pub fn writePathLinks(
gpa: std.mem.Allocator,
writer: *std.Io.Writer,
path: []const u8,
rootLen: usize,
rootHref: ?[]const u8,
) !void Writes path as HTML with each dotted segment linked to its ancestor section's anchor.
pub fn writePathLinksMd(
gpa: std.mem.Allocator,
writer: *std.Io.Writer,
path: []const u8,
rootLen: usize,
rootHref: ?[]const u8,
) !void Writes path as Markdown with each dotted segment linked to its ancestor section's anchor.
pub fn writeIndexTree(
sections: []const Section,
prefixLen: usize,
context: anytype,
comptime onDir: fn (@TypeOf(context), []const u8) anyerror!void,
comptime onLeaf: fn (@TypeOf(context), Section) anyerror!void,
comptime onDirEnd: fn (@TypeOf(context)) anyerror!void,
) anyerror!void Groups sections (sorted by fileLabel) into a directory tree for --tree index listings.
pub fn forEachDirGroup(
sections: []const Section,
prefixLen: usize,
context: anytype,
comptime onDir: fn (@TypeOf(context), []const u8, []const Section) anyerror!void,
comptime onLeaf: fn (@TypeOf(context), Section) anyerror!void,
) anyerror!void Walks one level of sections, handing onDir the subdirectory's own slice — for callers that need to write that subdirectory's page too, not just link to it.
pub fn hasFileLabels(sections: []const Section) bool pub fn lessByFileLabel(_: void, a: Section, b: Section) bool pub fn sortByFileLabelOrder(sections: []Section, order: DirOrder) void Sorts sections by fileLabel so writeIndexTree/writeDirPages group correctly.
pub fn walk(
sections: []const Section,
context: anytype,
comptime visit: fn (@TypeOf(context), Section) anyerror!void,
) anyerror!void Depth-first traversal of sections, calling visit on each node.
pub fn isPrimitiveTypeName(text: []const u8) bool Whether text names a Zig primitive type (bool, f32, sized-int forms like u8/i32, etc). Primitives are never real decls, so a reference to one must never become a codelink.
pub const Page = struct One rendered output page.
pub fn deinit(self: *Page, gpa: std.mem.Allocator) void pub fn freePages(gpa: std.mem.Allocator, pages: *std.ArrayList(Page)) void pub const Format = enum pub fn write(
gpa: std.mem.Allocator,
fmt: Format,
tree: model.DocTree,
title: []const u8,
opts: options.Options,
tplDoc: []const u8,
tplSec: []const u8,
reporter: *progress_mod.Progress,
) ![]Page pub fn resolveLocLink(gpa: std.mem.Allocator, fmt: Format, opts: options.Options, fileLabel: []const u8, ownPath: []const u8, rootDirName: []const u8) !LocLink Resolves the directory-page link shown before a decl's bare filename in {file}. Both fields empty when nothing should link (single-file input with no fileLabel, or a root-level file with opts.fileDir off). Caller owns both fields via LocLink.deinit.
pub const Registry = struct Maps a file's fileLabel to the slug its page (and, under --split item, its decls' folder) is built from, resolving collisions with sibling files/directories. Built once per write() call so every page's filename agrees on where a file's page lives.
pub fn deinit(self: *Registry, gpa: std.mem.Allocator) void pub fn slugFor(self: Registry, fileLabel: []const u8) []const u8 Registered slug for fileLabel, or its .zig-stripped form if unregistered.
pub fn declSegmentFor(self: Registry, gpa: std.mem.Allocator, parentDeclPath: []const u8, name: []const u8) !?[]const u8 Registered path segment for name under parentDeclPath, or null if unseen (single-page mode).
pub fn slugForFileSection(self: Registry, gpa: std.mem.Allocator, section: model.Section) ![]u8 Registered slug for a top-level whole-file-wrapper section. Always freshly allocated, unlike slugFor.
pub fn buildRegistry(gpa: std.mem.Allocator, sections: []const model.Section, fmt: Format, opts: options.Options) !Registry Builds sections' Registry. Also walks into every decl's own children (whatever --split item's recursion will later append segments onto) so two sibling decls whose names only differ by case — a real collision on a case-insensitive filesystem — are resolved once, up front, the same way two files already are.
pub fn pageFilename(gpa: std.mem.Allocator, fmt: Format, section: model.Section, ownDeclPath: []const u8, opts: options.Options, registry: Registry) ![]u8 Output-relative path for a section's own page. A whole-file wrapper uses its registered slug as <slug>/index<ext> or <slug><ext> depending on opts.dirUrls. An item-kind section uses ownDeclPath directly, becoming a folder if it has children, else following opts.dirUrls like the file case.
pub fn appendDeclPathSegment(gpa: std.mem.Allocator, parentDeclPath: []const u8, name: []const u8, registry: Registry) ![]u8 Builds parentDeclPath's child path for name, preferring the registry's collision-resolved segment; falls back to computing one directly when unregistered (single-page mode).
pub const FileRootIndex = struct Maps a file's sourceFile display path to its own file-root Section — a real one built by extraction, or a synthetic stand-in at virtualRoot for a root file with no wrapper of its own. byModuleName covers Zig's package-name self-import (@import("std") from inside std itself). byDottedPath covers --discover ns's alias shortcuts: a decl that's itself a cross-file re-export is built there as an empty stand-in carrying only aliasTargetPath. Pointers borrow from tree/virtualRoot; the index must not outlive either.
pub fn deinit(self: *FileRootIndex) void pub fn buildFileRootIndex(gpa: std.mem.Allocator, tree: model.DocTree, virtualRoot: *model.Section) !FileRootIndex pub fn resolvePendingCodelinks(gpa: std.mem.Allocator, sections: []model.Section, fileRoots: *const FileRootIndex) !void Resolves every pendingCodelinkTargets entry in sections (and their children) via resolveImportChain, merging hits into codelinkTargets alongside same-file references extraction already resolved. Also links any other file's bare filename mentioned in a comment or string literal (see scanFilenameMentions). Leaves unresolvable references unlinked.
pub fn writePageLinkList(gpa: std.mem.Allocator, fmt: Format, writer: *std.Io.Writer, sections: []const model.Section, fromPath: []const u8, parentDeclPath: []const u8, parentPage: []const u8, parentSourceFile: []const u8, opts: options.Options, registry: Registry, pages: ?*const PageIndex) !void Writes a flat <ul>/list of links, one per section, to that section's own page. docOnly sections render unlinked with their doc comment shown instead, since they have no page of their own.
pub fn writeIndexItemOpen(gpa: std.mem.Allocator, writer: *std.Io.Writer, section: model.Section, showPub: bool) !void Writes an index <li>'s opening tag: item-<kind> class always, plus item-pub/item-priv and a prefix label for anything but a plain whole-file wrapper.
Builds the --search feature's index: one entry per documented decl, giving the client-side script a name, breadcrumb path, resolved href, and plain-text doc comment without re-parsing rendered HTML.
pub const Entry = struct One search result's worth of data. href is root-relative, resolved by the client script against its own <script> tag's URL at runtime.
pub fn freeEntries(gpa: std.mem.Allocator, entries: []Entry) void pub fn buildIndex(gpa: std.mem.Allocator, tree: model.DocTree, opts: options.Options) ![]Entry Builds the full-site index for tree under opts. Assumes --format html. Caller owns the result (freeEntries).
pub fn normalizeWhitespace(gpa: std.mem.Allocator, md: []const u8) ![]u8 Collapses doc-comment whitespace to single spaces and trims the ends, leaving markdown syntax untouched.
pub const clientJs = @embedFile("assets/search.js") --search on's client-side widget script, written unmodified to search.js.
pub fn toJs(gpa: std.mem.Allocator, entries: []const Entry) ![]u8 Serializes entries as window.ZDI = [...];, the content of search-index.js. Each entry is a positional 4-element array to keep the file small.
Renders a decl's source as HTML, per options.SourceMode, syntax-highlighted via std.zig.Tokenizer. No JS: highlighting and the line-number gutter are pure CSS/HTML.
pub const ResolvedSpan = struct Resolves the identifier token starting at byte offset start to its documenting href, plus the byte offset one past the last token the resolved reference covers (a resolved reference can span multiple tokens, e.g. array_list.Aligned). Position-based rather than name-based, since the same identifier can resolve differently at different offsets (shadowing). Used only for tokenized source; prose uses ProseLinkResolver instead.
pub const LinkResolver = struct pub fn resolve(self: LinkResolver, start: u32) ?ResolvedSpan pub const ProseLinkResolver = struct Resolves a bare identifier name (not a byte position) to an href, for doc-comment prose and other non-tokenized text with no source position to key off.
pub fn resolve(self: ProseLinkResolver, name: []const u8) ?[]const u8 pub fn write(gpa: std.mem.Allocator, writer: *std.Io.Writer, source: []const u8, mode: options.SourceMode, raw: bool, links: ?LinkResolver, startLine: u32) !void Writes source as an HTML fragment matching mode. Always escapes. links, when given, wraps resolvable identifiers in a link (ignored when raw). startLine is the 1-based line the gutter should begin counting from. Callers should skip this entirely for binary content rather than pass it in.
pub fn writeTokens(writer: *std.Io.Writer, source: [:0]const u8) !void Writes source as highlighted, HTML-escaped Zig source using std.zig.Tokenizer. Never links identifiers — see writeTokensLinked for that.
pub fn writeTokensLinked(writer: *std.Io.Writer, source: [:0]const u8, links: ?LinkResolver) !void Like writeTokens, but also links a resolvable identifier or string literal (an @import(...) target) to its docs. A resolved reference spanning multiple tokens (e.g. array_list.Aligned) is folded into a single <a>, each token still individually syntax-highlighted inside it.
pub fn isFnNameToken(prevTag: ?std.zig.Token.Tag, nextTag: std.zig.Token.Tag) bool Reports whether an identifier token is a function name: immediately preceded by the fn keyword (a declaration's own name) or immediately followed by ( (a call expression). nextTag is the very next token after the identifier — callers get it via a cloned tokenizer peek, since std.zig.Tokenizer.next() has no lookahead of its own. There's always a next token (.eof at worst), but prevTag is genuinely absent for the very first token in a source.
pub fn classFor(tag: std.zig.Token.Tag, text: []const u8, isFnName: bool) ?[]const u8 Maps a token's tag and text to a CSS class, or null for tokens left unstyled. isFnName marks an identifier as a function name (declaration or call site) — callers with token-sequence context (see writeOneToken's caller) decide that and it overrides the default tok-id an identifier would otherwise get.
pub fn writeEscaped(writer: *std.Io.Writer, text: []const u8) !void HTML-escapes <, >, and &.
pub fn writeEscapedAttr(writer: *std.Io.Writer, text: []const u8) !void HTML-escapes an attribute value: as writeEscaped, plus ".
Default CSS shared by --css embed and --css external output.
pub fn css(gpa: std.mem.Allocator, opts: options.Options, theme: options.Theme) ![]u8 Default stylesheet. theme controls whether colors follow prefers-color-scheme (.auto) or are fixed. Only includes CSS chunks for features opts actually has enabled. Caller owns the returned slice.
A deliberately minimal templating engine: {variable} substitution and nothing else — no conditionals, no loops, no escaping syntax inside the template itself. Values are supplied pre-rendered (already HTML/Markdown, already escaped where the caller wants escaping) and substituted verbatim; the engine does no escaping of its own. A literal { not immediately followed by a known variable name and } is passed through unchanged (so templates can contain, say, a {} Zig snippet in a comment without the engine tripping over it — anything not exactly matching a provided key is left as-is rather than treated as an error).
A variable may also carry an inline format="..." attribute: {name format="<div>{name}</div>"}. When present and the variable's value is non-empty, the format string is rendered as a sub-template — name rebound to the raw value, every other variable still available — instead of substituting the raw value directly. An empty value renders the whole {name format="..."} span as nothing, same as an empty plain {name} would. Within the format string, \" and \n are unescaped before rendering, so a wrapper can embed quoted HTML attributes or a literal newline without breaking out of the tag's own quotes.
When render is given a padBlock (Markdown mode), literal newlines in the template source are stripped before parsing — so a .tpl file can wrap across lines for readability — and a bare \n outside any tag becomes a real newline in the output. A format="..." attribute's own \n/\" escapes are unaffected by this, since they're decoded separately once that attribute renders.
pub const Var = struct One {name} → value substitution.
pub const PadBlockFn = *const fn (std.mem.Allocator, []const u8) std.mem.Allocator.Error![]u8 A render's padBlock callback: pads a bare substitution's value for a particular output format's block conventions.
pub fn padMdBlock(gpa: std.mem.Allocator, value: []const u8) std.mem.Allocator.Error![]u8 Pads value with a trailing newline if non-empty and not already terminated. Pass as render's padBlock for Markdown; null for HTML.
pub fn render(gpa: std.mem.Allocator, template: []const u8, vars: []const Var, padBlock: ?PadBlockFn) ![]u8 Renders template, replacing every matched {name}/{name format="..."} with its value. Unmatched spans are copied through unchanged. padBlock, when non-null, applies only to a bare {name} substitution, never one inside a format="..." string.
pub const htmlDoc = @embedFile("templates/htmldoc.tpl") pub const htmlSec = @embedFile("templates/htmlsec.tpl") pub const mdDoc = @embedFile("templates/mddoc.tpl") pub const mdSec = @embedFile("templates/mdsec.tpl") Integration tests: extract each fixture and exercise the split renderers' page/file-splitting logic. Templates (.tpl files) are user-overridable and may be blanked or rewritten arbitrarily, so no test may assume anything about what a template renders — assertions stick to page counts, .filename paths, tree/registry structure, and search.buildIndex entries (all computed independently of .tpl content), never Page.contents.
A cross-file identifier/scope graph for --discover ns, ported from Zig's own Autodoc (lib/docs/wasm/{Walk,Decl}.zig). Parses every reachable file into one shared graph, resolves identifiers against per-file Scopes, and classifies each decl into a Category — including .alias, so a re-export resolves through to its real target decl rather than showing the one-line alias.
Renamed to this project's camelCase convention; index types are plain u32s into owned ArrayLists; no modules/multi-package support, no doctest extraction. Otherwise a close, node-tag-for-node-tag port.
pub const Error = std.mem.Allocator.Error pub const AddFileError = Error || error Graph.addFile's own error set: allocation failure, or the source failed to parse as valid Zig.
pub const Category = union(enum) What a decl's value expression resolves to. Mirrors Autodoc's own Category — keeps alias as a distinct case callers chase themselves via Decl.resolveAlias, since a decl's own identity is meaningful even when its content is an alias.
pub const File = struct One parsed, walked file. Owns its own Ast and per-file resolution tables; does not own the Decls it contributes (those live in the shared Graph.decls).
pub fn deinit(self: *File, gpa: std.mem.Allocator) void pub const Index = enum(u32) pub fn get(i: File.Index, graph: *const Graph) *File pub fn path(i: File.Index, graph: *const Graph) []const u8 pub fn displayPath(i: File.Index, gpa: std.mem.Allocator, graph: *const Graph) ![]u8 This file's path as it should be shown to a reader — relative to the root file's own directory, /-separated. Falls back to path verbatim if relative resolution fails.
pub fn findRootDecl(i: File.Index, graph: *const Graph) Decl.Index pub const Decl = struct pub const Index = enum(u32) pub fn get(i: Decl.Index, graph: *const Graph) *Decl pub const ExtraInfo = struct pub fn extraInfo(decl: *const Decl, graph: *const Graph) ExtraInfo pub fn categorize(decl: *const Decl, graph: *Graph) Error!Category pub fn resolveAliasIndex(index: Decl.Index, graph: *Graph) Error!struct Follows .alias until a non-alias Category (or a cycle, guarded by Graph.maxAliasChase) is reached.
pub fn getChild(decl: *const Decl, graph: *Graph, name: []const u8) Error!?Decl.Index Looks up a direct child of decl by name — a member of its namespace/container, or (transparently) of whatever it's an alias for.
pub fn children(decl: *const Decl, graph: *Graph, gpa: std.mem.Allocator) Error![]Decl.Index Lists every direct child of decl's namespace/container, in source order — transparently chasing .alias first, same as getChild. Re-derives the member list directly from the container's own AST node, which preserves source order without scanning the whole graph's decl list.
pub const Graph = struct The shared graph every walked file's decls live in. One Graph per --discover ns invocation.
pub fn init(gpa: std.mem.Allocator) Graph pub fn deinit(self: *Graph) void pub fn addFile(self: *Graph, path: []const u8, source: [:0]const u8) AddFileError!File.Index Parses source and walks its whole body, registering every decl and building this file's identifier/scope resolution tables. path is duplicated and owned by the graph. source itself is not retained past this call — this makes its own durable copy first and parses that.
pub fn findFileByPath(self: *const Graph, path: []const u8) ?File.Index Finds a file already added to this graph by its resolved path, if any. Used to resolve @import("...") targets that were already discovered under a different import name/site.
pub fn resolveImportString(self: *const Graph, fromFile: File.Index, importString: []const u8) ?File.Index Resolves a raw @import("importString") argument, as seen from fromFile, to the File.Index it targets. Returns null if the target isn't (yet, or ever) part of this graph.
pub const Scope = struct A lexical scope during the walk: the top scope (file root), a local var/const binding, or a namespace (struct/union/enum/opaque body, including the file root once it's known to be one).
pub fn getChild(scope: *Scope, name: []const u8) ?Ast.Node.Index pub fn lookup(startScope: *Scope, ast: *const Ast, name: []const u8) ?Ast.Node.Index
Builds a
model.DocTreefrom a Zig source file viastd.zig.Ast, including same-file codelink resolution (codelinks) while theAstis alive.