On this page
This page demonstrates every HTML element and CSS class that selfdoc's minimal theme styles. Use it for design iteration -- open this file in a browser, edit the CSS, and see every component update at once.
#Theme Showcase
#Typography
#Headings
#Heading Level 1
#Heading Level 2
#Heading Level 3
#Heading Level 4
#Heading Level 5
#Heading Level 6
#Inline Elements
This paragraph contains bold text, italicized text, and inline code like parse_directives(). You can also use external links and internal links to navigate the documentation.
selfdoc resolves :::directive blocks in Markdown templates into content extracted from source code. It supports Python, Go, and TypeScript/JavaScript through language-specific extractors that handle all five built-in directives: module, schema, test, cli, and config.
#Lists
#Unordered List
- Directive parser -- state machine that tracks fenced code blocks
parse_directivesextracts directive blocks from Markdownresolve_directivesreplaces directives with resolver output
- Resolver -- dispatches to the correct language extractor
- Custom directives from
selfdoc.jsonare checked first - Falls back to built-in language-specific extractors
- Custom directives from
- Build pipeline -- scans, resolves, converts, and writes output
- HTML converter -- built-in Markdown-to-HTML with no dependencies
#Ordered List
- Create a
selfdoc.jsonconfiguration file in your project root - Write Markdown templates in your
docs/directory - Add
:::directiveblocks where you want code content injected - Run
selfdoc buildto generate your documentation site
#Step Guide
- Install selfdoc using
pip install selfdocornpm install -g selfdoc. The npm package is a thin Node wrapper that delegates topython3 -m selfdoc. - Initialize your project with
selfdoc init. This creates aselfdoc.jsonconfig file and adocs/directory with a starter template. - Run the development server with
selfdoc serve. It uses SSE-based live reload with mtime polling so you see changes instantly as you edit templates.
#Code
#Plain Code Block
selfdoc build --output _site
selfdoc serve --port 8080
selfdoc check --coverage#Code with Language
from selfdoc.directives import parse_directives, resolve_directives
from selfdoc.resolver import make_resolver
def build_docs(config):
"""Build documentation from Markdown templates."""
resolver = make_resolver(config)
for template_path in config.docs_dir.glob("*.md"):
content = template_path.read_text()
resolved = resolve_directives(content, resolver)
output = md_to_html(resolved)
write_output(output, template_path)#Diff Highlighting
def resolve_directives(content, resolver):
- directives = parse_directives(content)
- for directive in directives:
- content = content.replace(directive.raw, resolver(directive))
+ directives = list(parse_directives(content))
+ for name, arg, body in directives:
+ replacement = resolver(name, arg, body)
+ content = content.replace(body, replacement)
return content#Code Annotations
def _slugify(text):
text = re.sub(r"<[^>]+>", "", text) 1
text = text.lower()
text = text.replace(" ", "-") 2
text = re.sub(r"[^a-z0-9-]", "", text) 3
return text.strip("-")#Code Tabs
import ast
def extract_module(source_path: str) -> str:
"""Extract module-level docstring using the ast module."""
tree = ast.parse(open(source_path).read())
return ast.get_docstring(tree) or ""package extractor
import "go/doc"
// ExtractModule returns the package-level documentation comment.
func ExtractModule(pkgPath string) string {
pkg, _ := doc.NewFromFiles(fset, files, pkgPath)
return pkg.Doc
}import * as ts from "typescript";
export function extractModule(sourcePath: string): string {
const sourceFile = ts.createSourceFile(
sourcePath,
readFileSync(sourcePath, "utf-8"),
ts.ScriptTarget.Latest,
);
// Extract leading comment from the source file
return getLeadingComment(sourceFile) ?? "";
}#Long Code Block
def resolve_directives(content: str, resolver: Callable[[str, str, str], str], source_paths: list[str] | None = None) -> str:
"""Replace all :::directive blocks in content with resolver output. Handles nested fenced code blocks correctly."""
directives = list(parse_directives(content))
for name, arg, body in sorted(directives, key=lambda d: content.index(d[2]), reverse=True): # reverse to preserve offsets
replacement = resolver(name, arg, body)
content = content[:content.index(body)] + replacement + content[content.index(body) + len(body):]
return content#Blocks
#Blockquote
selfdoc keeps your documentation in sync with your code. When you change a function signature, the docs update automatically on the next build. No more stale examples or outdated API references.
#Nested Blockquote
selfdoc extracts documentation from source code automatically.
The key insight is that code IS the documentation. By extracting directly from source, docs never go stale.
#Blockquote with Code
To use a custom directive, add it to your
selfdoc.json:{ "directives": { "changelog": "scripts/extract_changelog.py" } }
#Admonitions
Note
The directive parser uses a state machine to correctly handle fenced code blocks nested inside directives. This prevents false matches on lines that look like directive markers but are actually inside code examples.
Tip
Use selfdoc check --coverage to see how many of your public symbols are documented. The coverage report only works for Python projects using the ast-based extractor.
Warning
File writes to shared state use atomic writes (write to a temporary file, then os.replace). If you bypass selfdoc's build pipeline and write output files directly, you risk partial writes on crash.
Caution
Removing a directive from your template without removing the corresponding source code will cause selfdoc check to report decreased coverage. Always verify coverage after major refactors.
Important
All external calls (subprocess invocations, network requests) must have explicit timeouts. The deploy module enforces this for both Cloudflare Pages (via wrangler CLI) and GitHub Pages (via git push).
#Table
| Directive | Description | Python | Go |
|---|---|---|---|
module | Module-level docstring | ast | regex |
schema | Data class or struct definition | ast | regex |
test | Test function source code | ast | regex |
cli | CLI argument parser definition | ast | regex |
config | Configuration loading logic | ast | regex |
#Wide Table
| Directive | Python | Go | TypeScript | Description | Output Format |
|---|---|---|---|---|---|
module | ast.get_docstring | doc.Package | Leading comment | Module-level docstring | Markdown text |
schema | ast dataclass | struct fields | interface props | Data structure definition | Fenced code block |
test | ast function | func Test* | describe/it | Test function source | Fenced code block |
cli | argparse | flag.Parse | commander | CLI argument parser | Fenced code block |
config | ast loader | json.Unmarshal | dotenv parse | Configuration loading | Fenced code block |
custom | selfdoc.json | selfdoc.json | selfdoc.json | User-defined directive | Custom template |
module | ast.parse | go/parser | ts.createSourceFile | Full AST parsing | Structured data |
schema | dataclasses | reflect | zod/io-ts | Runtime validation | JSON Schema |
test | pytest | testing.T | jest/vitest | Test framework | Test report |
cli | click | cobra | yargs | Alternative CLI lib | Help text |
#API Entry Cards
#parse_directives
def parse_directives(content: str) -> list[tuple[str, str, str]]Extract all :::name arg / ::: directive pairs from a Markdown document. Returns a list of (name, arg, body) tuples. Correctly handles fenced code blocks to avoid false matches inside code examples.
#resolve_directives
def resolve_directives(content: str, resolver: Callable) -> strReplace all directive blocks in content with the output of resolver(name, arg, body). The resolver is typically created by make_resolver(config) which dispatches to the appropriate language extractor.
#md_to_html
def md_to_html(text: str) -> strConvert Markdown text to HTML. Handles headings, code blocks (with tabs and annotations), inline code, paragraphs, unordered and ordered lists, links, bold, italic, tables, blockquotes, and admonitions. No external dependencies.
#Glossary
- Directive
- A
:::name arg/:::block in a Markdown template that selfdoc replaces with content extracted from source code during the build process. - Extractor
- A language-specific module that reads source files and returns structured content for a given directive. Python uses
ast; Go and TypeScript use regex-based parsing. - Resolver
- The component that maps a directive name and argument to the correct extractor, checking custom directives from
selfdoc.jsonbefore falling back to built-in extractors. - Template
- A Markdown file in the
docs/directory containing directive blocks. Templates are processed duringselfdoc buildto produce the final HTML documentation pages.
#Collapsible Sections
Click to expand: Full build pipeline
- Load configuration from
selfdoc.json - Create a resolver from the configuration
- Walk
docs/for.mdtemplates - Parse frontmatter and resolve all directives
- Convert resolved Markdown to HTML
- Post-process: add image dimensions, generate TOC
- Write output files, generate auxiliary assets
- Compress with gzip and optional brotli
def build(dir_path: str = ".") -> dict[str, str]:
config = load_config(dir_path)
resolver = make_resolver(config)
html_files = generate_html(markdown_files, config)
return html_files#Images
#Search Dialog
Press Cmd+K (or Ctrl+K) to open the search dialog, or click the button below to open it manually for preview.