Skip to content
internal/resolver
On this page

Dispatching one directive to whatever can answer it: the content directives first, then the project's own custom scripts, then the language extractors.

#internal/resolver

#internal/resolver

Package resolver dispatches one directive to whatever can answer it.

A resolver is built for a project and called once per directive. It tries the content directives first (they are language-agnostic), then the project's own custom directives, and then the language extractors -- which for a multi-language project means deciding which language owns the path the directive names.

#Custom directives run out of process

The directives config key maps a directive name to a script, and the contract that key has always had is Python's: the script defines resolve(attrs, config, body) and returns Markdown. That contract is kept, so the script is still loaded and called by Python: an embedded driver (driver.py) is handed to python3, the script's path as its argument and one JSON object on standard input, and the Markdown it prints is what replaces the directive.

The driver runs as a declared read through the effects handle, so a --dry-run resolves directives like any other run.

A script that cannot be loaded, one with no callable resolve, one that raises, and a machine with no python3 are each a hard error naming the directive and the script. The Python this replaces swallowed all four into an inline note on the page; a page that says "custom directive 'api' failed" where its API reference belongs is not a page anybody wanted published, and the note was as easy to miss as any other paragraph.

#Directives compiled into the binary

The same config key also accepts a [BuiltinDirective]: a function this binary carries, registered by the caller that owns it and resolved in process at the point a script would have been. The registrars are the site-level directives an assembled site's home project carries, which render from the assembly's manifests -- state a build is handed and no config document can hold. They arrived as shipped Python shim scripts before there was one binary to compile them into.

#Resolver

Go go
type Resolver struct

Resolver resolves directives for one project.

It is not safe for concurrent use: LastSourceEntry is per-call state that a second caller would overwrite.

#BuiltinDirective

Go go
type BuiltinDirective func(attrs map[string]string, body []string) (string, error)

BuiltinDirective resolves one directive in process, from whatever state the registering caller captured.

It is the second value the "directives" config key accepts. A string names a script, which the Python driver loads and calls; a BuiltinDirective is a directive compiled into this binary and handed to [MakeResolver] by the caller that owns it, at the one point in the dispatch order the scripts occupy. The site-level directives of an assembled site's home project are the registrars: they render from the assembly's manifests, which the home project's build receives and no config document can hold.

Both values live under one key because a directive name is either known to the catalog or declared there, and the name set the config declares is what decides which markers a page may carry. A separate key would leave a registered directive unknown to that check and refused before it ever reached a resolver.

#MakeResolver

Go go
func MakeResolver(config map[string]any, baseDir string, handle *effects.Handle) (*Resolver, error)

MakeResolver builds the resolver for a project.

config is the loaded selfdoc.json, baseDir the project root every relative path is resolved against, and handle the effects handle the custom-directive driver runs under.

#Resolver.Resolve

Go go
func (r *Resolver) Resolve(name string, attrs map[string]string, body []string) (string, error)

Resolve resolves one directive into the Markdown that replaces it.

A directive that cannot be answered from the project's own files renders an error marker and no error: one bad directive degrades one region of one page instead of failing the build. An error is the hard-error family -- a directive that reads source code in a project that declares none, an ambiguous path, a custom directive that failed -- where a marker would hide the fact that the page lost the content it asked for.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search