selfdoc v0.2.0

#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.

  1. Load config from selfdoc.json
  2. Scan docs/ directory for .md template files
  3. For each template, resolve directives using language-specific extractor
  4. Convert resolved markdown to HTML
  5. Write HTML to output directory
  6. Copy non-.md files (images, CSS, etc.) to output

Args:

Returns:

#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:

Returns:

#_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:

#DeployError

Raised when a deploy operation fails.

#_resolve_cloudflare_env

def _resolve_cloudflare_env()

Bridge CF_ env vars to CLOUDFLARE_ for wrangler.

Our canonical env var names use the CF_ prefix (CF_ACCOUNT_ID, CF_PAGES_API_TOKEN). Wrangler expects CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN. This function bridges the gap.

#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:

Raises:

#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:

Raises:

#_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:

Returns:

#generate_html

def generate_html(markdown_files, project_name=None, version=None, has_custom_css=False)

Convert Markdown files to static HTML.

Args:

Returns:

#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

.

Expects lines like:

Header1Header2
Cell1Cell2

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:

Returns:

Raises:

#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:

Returns:

#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:

#resolve_python

def resolve_python(name, arg, body, source_paths, base_dir)

Dispatch a directive to the appropriate Python extraction handler.

Args:

Returns:

#_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: [TestClassName or test_function_name]

#_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:

#resolve_go

def resolve_go(name, arg, body, source_paths, base_dir)

Dispatch a directive to the appropriate Go extraction handler.

Args:

Returns:

#_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: [TestFuncName]

#_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: [TypeName] If TypeName is omitted, extracts the first exported struct found.

#_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:

#_extract_usage_constants

def _extract_usage_constants(source)

Find string constants/functions that return usage text.

Looks for patterns like:

#_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:

#resolve_typescript

def resolve_typescript(name, arg, body, source_paths, base_dir)

Dispatch a directive to the appropriate TypeScript/JS extraction handler.

Args:

Returns:

#_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: [TestName]

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:

#_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.