#selfdoc
Auto-generate API documentation from source code using inline directives
#API Reference
#selfdoc
selfdoc: Code-aware static site generator with directive-based content extraction.
#_detect_version
def _detect_version()
Detect package version, preferring pyproject.toml over installed metadata.
Order: pyproject.toml in the source tree (accurate during editable installs) -> importlib.metadata (works for regular installs) -> "unknown".
#main
def main()
Entry point for the selfdoc CLI.
#selfdoc.build
Build pipeline for selfdoc: template scanning, directive resolution, HTML output.
#_stub_resolver
def _stub_resolver(name, arg, body)
Placeholder resolver that produces a visible unresolved marker.
#_detect_project_version
def _detect_project_version(dir_path)
Detect project version from pyproject.toml or package.json.
Returns the version string, or an empty string if not found.
#build
def build(dir_path='.', config=None)
Build docs from templates + directives.
- Load config from selfdoc.json
- Scan docs/ directory for .md template files
- For each template, resolve directives using language-specific extractor
- Convert resolved markdown to HTML
- Write HTML to output directory
- Copy non-.md files (images, CSS, etc.) to output
Args:
dir_path: Project root directory.config: Pre-loaded config dict (if None, loads from selfdoc.json).
Returns:
- Dict of {output_path: True} for files written.
#selfdoc.check
Check command -- validate directives and report documentation coverage.
Scans docs/ templates for directives, attempts to resolve each one, and reports per-directive status (OK or FAILED). For Python projects, also computes coverage: how many public symbols are referenced by :::module directives vs. the total public symbols in source files.
#DirectiveResult
Result of validating a single directive.
#CoverageStats
Coverage of public symbols by :::module directives.
#CheckResult
Aggregate result of check_docs().
#check_docs
def check_docs(dir_path='.', config=None)
Validate all directives in docs templates and report coverage.
Scans docs/ for .md templates, parses directives, attempts to resolve each one, and computes coverage for Python projects.
Args:
dir_path: Project root directory.config: Pre-loaded config dict (if None, loads from selfdoc.json).
Returns:
- CheckResult with per-directive results and optional coverage stats.
#_compute_python_coverage
def _compute_python_coverage(config, base_dir, referenced_modules)
Count public symbols in source files vs. those referenced by directives.
A "public symbol" is a top-level function or class whose name does not start with underscore. Each :::module directive that resolves to a file counts all public symbols in that file as "documented".
#_extract_public_symbols
def _extract_public_symbols(filepath)
Parse a Python file and return names of public top-level functions/classes.
#_resolve_module_to_relpath
def _resolve_module_to_relpath(arg, source_paths, base_dir)
Resolve a module argument to a path relative to base_dir.
Mirrors the resolution logic in extractors/python.py but returns the relative path instead of the absolute path.
#print_results
def print_results(result)
Print check results to stdout in a human-readable format.
#selfdoc.cli
CLI interface for selfdoc.
#_detect_language
def _detect_language()
Auto-detect project language from project files.
Returns (language, source_paths) tuple or (None, None) if undetectable.
#_detect_main_module
def _detect_main_module()
Detect the main module name for the starter template.
#_cmd_init
def _cmd_init(args)
Initialize selfdoc in the current project.
#_cmd_build
def _cmd_build(args)
Build the documentation site.
#_cmd_serve
def _cmd_serve(args)
Serve the documentation site locally with SSE-based live reload.
#_cmd_deploy
def _cmd_deploy(args)
Deploy the documentation site.
#_detect_version
def _detect_version()
Detect project version from pyproject.toml or package.json.
#_cmd_check
def _cmd_check(args)
Check documentation coverage and consistency.
#run
def run()
Parse arguments and dispatch to the appropriate subcommand.
#selfdoc.config
Config loader for selfdoc.json.
#ConfigError
Raised when selfdoc.json is present but invalid.
#_validate_deploy
def _validate_deploy(deploy)
Validate the deploy section if present.
Raises ConfigError if provider is unrecognized or required fields are missing.
#load_config
def load_config(dir_path='.')
Load and validate selfdoc.json from dir_path.
Returns the validated config dict, or None if selfdoc.json does not exist. Raises ConfigError on malformed or invalid configuration.
#selfdoc.deploy
Deploy providers for selfdoc documentation sites.
Supports:
- Cloudflare Pages (via wrangler CLI)
- GitHub Pages (via git force-push to gh-pages branch)
#DeployError
Raised when a deploy operation fails.
#deploy_cloudflare_pages
def deploy_cloudflare_pages(output_dir, project_name, version)
Deploy to Cloudflare Pages using the Wrangler CLI.
Requires wrangler (Cloudflare CLI) to be installed and authenticated (via wrangler login or CLOUDFLARE_API_TOKEN env var).
Args:
output_dir: Path to the built HTML output directory.project_name: Cloudflare Pages project name.version: Version string for the commit message.
Raises:
DeployError: If wrangler is not installed or the deploy fails.
#deploy_github_pages
def deploy_github_pages(output_dir, version)
Deploy to GitHub Pages by force-pushing output to gh-pages branch.
Uses a temporary directory to build a fresh commit without touching the current working tree. Creates a .nojekyll file to prevent Jekyll processing on GitHub.
Args:
output_dir: Path to the built HTML output directory.version: Version string for the commit message.
Raises:
DeployError: If git operations fail or remote is unreachable.
#_run_git
def _run_git(args, cwd)
Run a git command in the given directory, raising DeployError on failure.
#selfdoc.directives
Directive parser for :::name arg blocks in markdown files.
Directive syntax: :::name arg optional body lines :::
Directives inside fenced code blocks ( or ~~~) are ignored. Unclosed directives at EOF raise DirectiveError.
#DirectiveError
Raised when a directive is malformed (e.g. unclosed at EOF).
#Directive
A parsed directive block.
#parse_directives
def parse_directives(content: str) -> list[Directive]
Extract all directive blocks from markdown content.
Returns a list of Directive dataclass instances in document order. Directives inside fenced code blocks are ignored. Raises DirectiveError if a directive is opened but never closed.
#resolve_directives
def resolve_directives(content: str, resolver: callable) -> str
Replace each directive block with the output of resolver(name, arg, body).
Non-directive content passes through unchanged. Directives inside fenced code blocks are left as-is (they are not directives).
#selfdoc.html
Convert Markdown files to static HTML with a built-in minimal converter.
No external dependencies -- handles headings, code blocks, inline code, paragraphs, lists, links, bold/italic, and tables.
#_slugify
def _slugify(text)
Convert heading text to a URL-friendly slug for deep linking.
Strips HTML tags first, then: lowercase, spaces to hyphens, remove non-alphanumeric characters except hyphens.
#get_css
def get_css(theme_name='minimal')
Return the CSS content for the named theme.
Args:
theme_name: Name of the theme to load (default "minimal").
Returns:
- The CSS content as a string.
#generate_html
def generate_html(markdown_files, project_name=None, version=None, has_custom_css=False)
Convert Markdown files to static HTML.
Args:
markdown_files: Dict mapping relative paths to MD content.project_name: Project name for titles and sidebar.version: Version string for display (optional).has_custom_css: Whether a custom.css file exists for the project.
Returns:
- Dict mapping file paths (.html) to HTML content.
#md_to_html
def md_to_html(text)
Convert Markdown text to HTML.
Handles: headings, code blocks, inline code, paragraphs, unordered lists, ordered lists, links, bold, italic, tables.
#_parse_table
def _parse_table(table_lines)
Parse markdown table lines into an HTML
| Header1 | Header2 |
|---|---|
| Cell1 | Cell2 |
The separator line (containing only |, -, and spaces) is detected and used to separate header from body rows.
#_inline_format
def _inline_format(text)
Apply inline formatting: links, bold, italic, inline code.
#_escape_html
def _escape_html(text)
Escape HTML special characters.
#_md_to_html_path
def _md_to_html_path(md_path)
Convert a .md path to .html.
#_build_nav
def _build_nav(markdown_files)
Build navigation items from the markdown file list.
Returns list of dicts: {"label": str, "path": str (html path)}
#_render_nav
def _render_nav(nav_items, prefix, current_path='')
Render the sidebar navigation HTML as a flat list of links.
#_extract_title
def _extract_title(md_content, fallback)
Extract the first heading from markdown content as the page title.
#_wrap_page
def _wrap_page(body_html, nav_html, title, project_name, version, css_href='style.css', custom_css_href=None)
Wrap converted HTML body in the full page template.
#selfdoc.resolver
Resolver factory -- dispatches directives to language-specific extractors.
#_load_custom_directive
def _load_custom_directive(script_path, name)
Dynamically import a custom directive script and return its module.
Args:
script_path: Absolute path to the Python script.name: Directive name (used as the module name for importlib).
Returns:
- The loaded module, or None if loading fails.
Raises:
FileNotFoundError: If the script file does not exist.AttributeError: If the module has no 'resolve' callable.Exception: Any other import/load error.
#make_resolver
def make_resolver(config, base_dir='.')
Create a resolver function for the project's language.
Custom directives from config["directives"] take priority over built-in language extractors. Each entry maps a directive name to a relative path (from project root) of a Python script that exports resolve(arg, config).
Args:
config: Validated config dict from selfdoc.json.base_dir: Project root directory (for resolving relative paths).
Returns:
- A callable(name, arg, body) -> str that resolves directives to markdown.
#extractors
#selfdoc.extractors
#selfdoc.extractors.python
Python source extractor -- resolves directives by extracting from .py files.
Uses stdlib ast for parsing, no external dependencies. Handles:
- :::module -- extract module docstrings, functions, classes
- :::test -- extract test source code
- :::schema -- extract dataclass fields or JSON schema
- :::cli -- extract CLI help/usage info
- :::config -- extract config file contents as tables
#resolve_python
def resolve_python(name, arg, body, source_paths, base_dir)
Dispatch a directive to the appropriate Python extraction handler.
Args:
name: Directive name (module, test, schema, cli, config).arg: Directive argument (path, module name, etc.).body: Body lines of the directive block.source_paths: List of source directories from selfdoc.json.base_dir: Project root directory.
Returns:
- Markdown string with the extracted content.
#_handle_module
def _handle_module(arg, body, source_paths, base_dir)
Extract module docstring, functions, and classes.
Resolves dotted.path or file path to a .py file, parses with ast, and formats the result as markdown.
#_resolve_module_path
def _resolve_module_path(arg, source_paths, base_dir)
Try to resolve a module argument to an actual .py file path.
Tries: dotted-to-path conversion within each source path, then direct path.
#_format_docstring
def _format_docstring(docstring)
Transform Google-style docstring sections into markdown.
Detects section headers like Args:, Returns:, Raises: followed by indented name: description lines and converts them to bold headers with bullet lists so the markdown converter renders them as structured HTML instead of collapsing whitespace.
#_match_section_header
def _match_section_header(stripped)
If stripped is a recognized section header like Args:, return the header name. Otherwise return None.
#_is_param_line
def _is_param_line(text)
Check if a line looks like name: description or name (type): description.
#_split_param_line
def _split_param_line(text)
Split name: description into (name, description).
Also handles name (type): description.
#_format_function
def _format_function(node, heading_level=2)
Format a function/method node as markdown.
Skips private items (leading _) unless they have a docstring.
#_format_class
def _format_class(node)
Format a class node as markdown, including its public methods.
#_build_signature
def _build_signature(node)
Build a human-readable function signature string from ast.arguments.
#_annotation_str
def _annotation_str(node)
Convert an annotation AST node to a string, or empty string if None.
#_handle_test
def _handle_test(arg, body, source_paths, base_dir)
Extract test source code from a test file.
arg format:
#_extract_node_source
def _extract_node_source(source_lines, node)
Extract source lines for an AST node, stripping common indent.
#_handle_schema
def _handle_schema(arg, body, source_paths, base_dir)
Extract schema information from JSON or Python dataclass.
arg format: - path/to/file.json -> render JSON keys as table - dotted.module ClassName -> extract dataclass fields
#_schema_from_json
def _schema_from_json(file_path, base_dir)
Render a JSON file as a documented table of keys, types, and values.
#_json_type_name
def _json_type_name(value)
Get a human-readable type name for a JSON value.
#_json_value_repr
def _json_value_repr(value)
Get a compact representation of a JSON value for table display.
#_schema_from_dataclass
def _schema_from_dataclass(module_path, class_name, source_paths, base_dir)
Extract dataclass/class fields with types and defaults from source.
#_extract_class_fields
def _extract_class_fields(class_node, source)
Extract fields from a class (dataclass or regular class with annotations).
Produces a markdown table with Field, Type, Default, and Description columns.
#_get_inline_comment
def _get_inline_comment(source_lines, lineno)
Extract an inline # comment from a source line (1-based lineno).
#_format_default
def _format_default(default_str)
Format a default value for table display.
#_handle_cli
def _handle_cli(arg, body, source_paths, base_dir)
Extract CLI help/usage information from a module.
For v1: extracts the module docstring and any string constants named HELP or USAGE, formatted as a code block.
#_handle_config
def _handle_config(arg, body, source_paths, base_dir)
Extract config file contents as a documented table.
Supports JSON and TOML. Detects format from file extension.
#_config_from_json
def _config_from_json(full_path, display_path)
Parse JSON config and render as a key-value table.
#_config_from_toml
def _config_from_toml(full_path, display_path)
Parse TOML config and render as a key-value table.
#_flatten_toml
def _flatten_toml(data, prefix, rows)
Recursively flatten TOML data into table rows.
#selfdoc.extractors.go
Go source extractor -- resolves directives by extracting from .go files.
Uses regex-based parsing (no Go toolchain required). Handles:
- :::module -- extract package doc, exported funcs/types/consts/vars
- :::test -- extract test source code
- :::schema -- extract struct fields as a table
- :::cli -- extract usage constants and flag.* calls
- :::config -- extract config file contents as tables (JSON/TOML/YAML)
#resolve_go
def resolve_go(name, arg, body, source_paths, base_dir)
Dispatch a directive to the appropriate Go extraction handler.
Args:
name: Directive name (module, test, schema, cli, config).arg: Directive argument (path, package name, etc.).body: Body lines of the directive block.source_paths: List of source directories from selfdoc.json.base_dir: Project root directory.
Returns:
- Markdown string with the extracted content.
#_handle_module
def _handle_module(arg, body, source_paths, base_dir)
Extract package doc, exported funcs, types, consts, and vars.
arg is a package directory path (e.g. "internal/commit"). Finds all .go files in that directory (excluding _test.go), extracts the package doc comment and all exported declarations.
#_resolve_package_dir
def _resolve_package_dir(arg, source_paths, base_dir)
Resolve a package path argument to an actual directory.
Tries each source_path prefix, then the base_dir directly.
#_extract_package_doc
def _extract_package_doc(file_contents)
Extract the package name and doc comment from Go source files.
The package doc is the contiguous // comment block immediately above the package declaration. Returns (package_name, doc_string).
#_collect_comment_block_above
def _collect_comment_block_above(lines, target_line_idx)
Collect contiguous // comment lines immediately above target_line_idx.
Skips blank lines between the comment block and the declaration. Returns the comment text with // prefixes stripped.
#_extract_exported_declarations
def _extract_exported_declarations(source)
Extract all exported declarations from a Go source file.
Returns a list of dicts with keys: kind, name, signature, doc.
#_extract_const_block
def _extract_const_block(lines, block_start_idx, declarations, seen_names)
Extract exported constants from a const (...) block.
The doc comment for the entire block is attached to the first exported constant. Individual constants may also have their own // comments.
#_extract_var_block
def _extract_var_block(lines, block_start_idx, declarations, seen_names)
Extract exported variables from a var (...) block.
#_handle_test
def _handle_test(arg, body, source_paths, base_dir)
Extract test source code from a Go test file.
arg format:
#_resolve_file_path
def _resolve_file_path(file_path, source_paths, base_dir)
Resolve a file path relative to base_dir or source_paths.
#_extract_go_function
def _extract_go_function(source, func_name)
Extract a complete function from Go source by name.
Uses brace-counting to find the function body boundaries.
#_handle_schema
def _handle_schema(arg, body, source_paths, base_dir)
Extract struct type fields as a markdown table.
arg format:
#_extract_structs
def _extract_structs(source)
Extract all exported struct type declarations from Go source.
Returns a list of dicts: {name, doc, fields: [{name, type, tag, comment}]}.
#_parse_struct_field
def _parse_struct_field(field_line, lines, line_idx)
Parse a single struct field line.
Returns {name, type, tag, comment} or None if not a field.
#_format_struct_table
def _format_struct_table(struct_info)
Format a struct's fields as a markdown table.
#_handle_cli
def _handle_cli(arg, body, source_paths, base_dir)
Extract CLI usage/help text and flag definitions from Go source.
Looks for:
- String constants named usage, helpText, usageText (case-insensitive match)
- flag.StringVar, flag.BoolVar, etc. calls
#_extract_usage_constants
def _extract_usage_constants(source)
Find string constants/functions that return usage text.
Looks for patterns like:
- const usage =
... - func usageText() string { return
...} - Any variable/const with "usage" or "help" in the name containing a string
#_extract_flag_calls
def _extract_flag_calls(source)
Extract flag.XxxVar and flag.Xxx calls from Go source.
Returns list of {name, type, default, desc}.
#_handle_config
def _handle_config(arg, body, source_paths, base_dir)
Extract config file contents as a documented table.
Supports JSON and TOML. Detects format from file extension. Delegates to the same logic as the Python extractor.
#_config_from_json
def _config_from_json(full_path, display_path)
Parse JSON config and render as a key-value table.
#_config_from_toml
def _config_from_toml(full_path, display_path)
Parse TOML config and render as a key-value table.
#_flatten_toml
def _flatten_toml(data, prefix, rows)
Recursively flatten TOML data into table rows.
#_json_type_name
def _json_type_name(value)
Get a human-readable type name for a JSON value.
#_json_value_repr
def _json_value_repr(value)
Get a compact representation of a JSON value for table display.
#selfdoc.extractors.typescript
TypeScript/JavaScript source extractor -- resolves directives by extracting from .ts/.js files.
Uses regex-based parsing (no external dependencies). Handles:
- :::module -- extract module-level JSDoc, exported functions/classes/interfaces/types
- :::test -- extract test blocks (describe/it/test) by name
- :::schema -- extract interface/type fields as table
- :::cli -- extract help/usage strings or module-level JSDoc
- :::config -- extract JSON/JSONC/TOML config files as tables
#resolve_typescript
def resolve_typescript(name, arg, body, source_paths, base_dir)
Dispatch a directive to the appropriate TypeScript/JS extraction handler.
Args:
name: Directive name (module, test, schema, cli, config).arg: Directive argument (path, type name, etc.).body: Body lines of the directive block.source_paths: List of source directories from selfdoc.json.base_dir: Project root directory.
Returns:
- Markdown string with the extracted content.
#_resolve_file_path
def _resolve_file_path(arg, source_paths, base_dir)
Resolve a file path argument to an actual TS/JS file on disk.
Tries the arg as-is (relative to base_dir and each source path), then with common extensions appended.
#_read_file
def _read_file(filepath)
Read a file and return its contents, or raise an error string.
#_parse_jsdoc_text
def _parse_jsdoc_text(raw_jsdoc)
Parse raw JSDoc content (the lines between /* and /).
Returns a dict with: - description: the main description text - params: list of {name, description} - returns: return description or None - tags: list of {tag, name, description} for other tags
#_find_jsdoc_before
def _find_jsdoc_before(source, pos)
Find the JSDoc block that ends right before pos in the source.
Looks backwards from pos for a / that's part of a / ... / block, allowing only whitespace between the end of the JSDoc and pos.
#_format_jsdoc_as_markdown
def _format_jsdoc_as_markdown(jsdoc)
Format a parsed JSDoc dict as markdown text.
#_handle_module
def _handle_module(arg, body, source_paths, base_dir)
Extract module-level JSDoc and exported declarations from a TS/JS file.
#_extract_module_jsdoc
def _extract_module_jsdoc(source)
Extract the first JSDoc block that appears before any declaration.
A module-level JSDoc is a /* / block at the top of the file, before any import/export/declaration.
#_extract_exports
def _extract_exports(source)
Extract all exported declarations with their JSDoc and signatures.
Returns a list of dicts with: name, signature, jsdoc (parsed or None).
#_extract_name_from_signature
def _extract_name_from_signature(sig)
Extract the declaration name from an export signature string.
#_handle_test
def _handle_test(arg, body, source_paths, base_dir)
Extract test source code from a test file.
arg format:
For TS/JS test files, looks for describe("TestName", ...), it("TestName", ...), or test("TestName", ...) blocks.
#_extract_test_block
def _extract_test_block(source, target_name)
Find a describe/it/test block by name and extract its source.
Searches for patterns like: describe("Name", ... it("Name", ... test("Name", ... Then tracks brace depth to find the end of the block.
#_handle_schema
def _handle_schema(arg, body, source_paths, base_dir)
Extract interface or type definition fields as a markdown table.
arg format: - path/to/file.json -> render JSON keys as table - path/to/file.ts TypeName -> extract interface/type fields - path/to/file.ts -> if only one interface, extract it
#_schema_from_json
def _schema_from_json(file_path, base_dir)
Render a JSON file as a documented table of keys, types, and values.
#_schema_from_ts
def _schema_from_ts(source, type_name, display_path)
Extract interface or type fields from TypeScript source as a markdown table.
#_extract_brace_block
def _extract_brace_block(source, open_brace_pos)
Extract the content between matched braces starting at open_brace_pos.
Returns the content between { and } (exclusive), or None if unmatched.
#_parse_interface_fields
def _parse_interface_fields(body)
Parse fields from an interface/type body string.
Each field looks like: fieldName: Type; fieldName?: Type; Possibly preceded by a JSDoc comment or inline comment.
#_find_inline_comment
def _find_inline_comment(line)
Find the position of an inline // comment, ignoring those inside strings.
Returns the index of the // or None if no inline comment found.
#_handle_cli
def _handle_cli(arg, body, source_paths, base_dir)
Extract CLI help/usage information from a TS/JS file.
Looks for:
- Module-level JSDoc comment
- const help = "..." or const usage = "..." string constants
- yargs/commander setup patterns
#_handle_config
def _handle_config(arg, body, source_paths, base_dir)
Extract config file contents as a documented table.
Supports JSON, JSONC (strips // and / / comments), and TOML.
#_config_from_json
def _config_from_json(full_path, display_path)
Parse JSON config and render as a key-value table.
#_config_from_jsonc
def _config_from_jsonc(full_path, display_path)
Parse JSONC config (strip comments) and render as a key-value table.
#_strip_jsonc_comments
def _strip_jsonc_comments(text)
Strip // and / / comments from JSONC text, respecting strings.
#_config_from_toml
def _config_from_toml(full_path, display_path)
Parse TOML config and render as a key-value table.
#_flatten_toml
def _flatten_toml(data, prefix, rows)
Recursively flatten TOML data into table rows.
#_render_json_table
def _render_json_table(data)
Render a JSON dict as a markdown key-value table.
#_json_type_name
def _json_type_name(value)
Get a human-readable type name for a JSON value.
#_json_value_repr
def _json_value_repr(value)
Get a compact representation of a JSON value for table display.