selfdoc demo v0.3.0
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

#Ordered List

  1. Create a selfdoc.json configuration file in your project root
  2. Write Markdown templates in your docs/ directory
  3. Add :::directive blocks where you want code content injected
  4. Run selfdoc build to generate your documentation site

#Step Guide

  1. Install selfdoc using pip install selfdoc or npm install -g selfdoc. The npm package is a thin Node wrapper that delegates to python3 -m selfdoc.
  2. Initialize your project with selfdoc init. This creates a selfdoc.json config file and a docs/ directory with a starter template.
  3. 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

python
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

diff
 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

python
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

python
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 ""
go
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
}
typescript
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

python
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

DirectiveDescriptionPythonGo
moduleModule-level docstringastregex
schemaData class or struct definitionastregex
testTest function source codeastregex
cliCLI argument parser definitionastregex
configConfiguration loading logicastregex

#Wide Table

DirectivePythonGoTypeScriptDescriptionOutput Format
moduleast.get_docstringdoc.PackageLeading commentModule-level docstringMarkdown text
schemaast dataclassstruct fieldsinterface propsData structure definitionFenced code block
testast functionfunc Test*describe/itTest function sourceFenced code block
cliargparseflag.ParsecommanderCLI argument parserFenced code block
configast loaderjson.Unmarshaldotenv parseConfiguration loadingFenced code block
customselfdoc.jsonselfdoc.jsonselfdoc.jsonUser-defined directiveCustom template
moduleast.parsego/parserts.createSourceFileFull AST parsingStructured data
schemadataclassesreflectzod/io-tsRuntime validationJSON Schema
testpytesttesting.Tjest/vitestTest frameworkTest report
cliclickcobrayargsAlternative CLI libHelp text

#API Entry Cards

#parse_directives

python
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

python
def resolve_directives(content: str, resolver: Callable) -> str

Replace 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

python
def md_to_html(text: str) -> str

Convert 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.json before falling back to built-in extractors.
Template
A Markdown file in the docs/ directory containing directive blocks. Templates are processed during selfdoc build to produce the final HTML documentation pages.

#Collapsible Sections

Click to expand: Full build pipeline
  1. Load configuration from selfdoc.json
  2. Create a resolver from the configuration
  3. Walk docs/ for .md templates
  4. Parse frontmatter and resolve all directives
  5. Convert resolved Markdown to HTML
  6. Post-process: add image dimensions, generate TOC
  7. Write output files, generate auxiliary assets
  8. Compress with gzip and optional brotli
python
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

selfdoc build pipeline diagram

#Search Dialog

Press Cmd+K (or Ctrl+K) to open the search dialog, or click the button below to open it manually for preview.

#Page Not Found

Page not found

The page you are looking for does not exist.

Go to the homepage

Design Settings

Layout
Sidebar position
Sidebar width
Show TOC
Content width
Typography
Font kind
Font size
Code font size
Heading transform
Heading weight
Heading separator
Spacing
Density
Color
Light / Dark
Accent color
Topbar color
Sidebar background
Gradient strip
Components
Border radius
Code block style
Table style
Link underline
Admonition style
CSS Preview