diff --git a/Justfile b/Justfile index dfbff50dd..9b23fffcd 100644 --- a/Justfile +++ b/Justfile @@ -130,6 +130,11 @@ apitest server_url *args: interop *args: uv run --group test pytest tests/interop/ -v --no-cov --timeout=120 -x --tb=short --log-cli-level=INFO "$@" +# Generate reference pages and navigation from spec docstrings +[group('docs')] +docs-generate: + uv run python tools/generate_docs.py + # Build documentation site with mkdocs [group('docs')] docs *args: diff --git a/docs/reference/crypto/index.md b/docs/reference/crypto/index.md new file mode 100644 index 000000000..6d29725a0 --- /dev/null +++ b/docs/reference/crypto/index.md @@ -0,0 +1,3 @@ +# Cryptographic primitives used by the protocol specification. + +::: lean_spec.spec.crypto diff --git a/docs/reference/crypto/koalabear.md b/docs/reference/crypto/koalabear.md new file mode 100644 index 000000000..05afa8976 --- /dev/null +++ b/docs/reference/crypto/koalabear.md @@ -0,0 +1,3 @@ +# Core definition of the KoalaBear prime field Fp. + +::: lean_spec.spec.crypto.koalabear diff --git a/docs/reference/crypto/merkleization.md b/docs/reference/crypto/merkleization.md new file mode 100644 index 000000000..7d28677e5 --- /dev/null +++ b/docs/reference/crypto/merkleization.md @@ -0,0 +1,3 @@ +# Merkleization primitives and hash-tree-root dispatch for SSZ. + +::: lean_spec.spec.crypto.merkleization diff --git a/docs/reference/crypto/poseidon.md b/docs/reference/crypto/poseidon.md new file mode 100644 index 000000000..6cfd0d2da --- /dev/null +++ b/docs/reference/crypto/poseidon.md @@ -0,0 +1,3 @@ +# Specification of the Poseidon permutation over the KoalaBear field. + +::: lean_spec.spec.crypto.poseidon diff --git a/docs/reference/crypto/xmss/constants.md b/docs/reference/crypto/xmss/constants.md new file mode 100644 index 000000000..96862eec9 --- /dev/null +++ b/docs/reference/crypto/xmss/constants.md @@ -0,0 +1,3 @@ +# Cryptographic constants and configuration presets for the XMSS spec. + +::: lean_spec.spec.crypto.xmss.constants diff --git a/docs/reference/crypto/xmss/containers.md b/docs/reference/crypto/xmss/containers.md new file mode 100644 index 000000000..a14fcac1a --- /dev/null +++ b/docs/reference/crypto/xmss/containers.md @@ -0,0 +1,3 @@ +# Generalized XMSS containers. + +::: lean_spec.spec.crypto.xmss.containers diff --git a/docs/reference/crypto/xmss/encoding.md b/docs/reference/crypto/xmss/encoding.md new file mode 100644 index 000000000..6f5490ae6 --- /dev/null +++ b/docs/reference/crypto/xmss/encoding.md @@ -0,0 +1,3 @@ +# Message-to-codeword pipeline for the Generalized XMSS scheme. + +::: lean_spec.spec.crypto.xmss.encoding diff --git a/docs/reference/crypto/xmss/field.md b/docs/reference/crypto/xmss/field.md new file mode 100644 index 000000000..35585f747 --- /dev/null +++ b/docs/reference/crypto/xmss/field.md @@ -0,0 +1,3 @@ +# Field-element decomposition and secure sampling for the Generalized XMSS scheme. + +::: lean_spec.spec.crypto.xmss.field diff --git a/docs/reference/crypto/xmss/index.md b/docs/reference/crypto/xmss/index.md new file mode 100644 index 000000000..f81f78870 --- /dev/null +++ b/docs/reference/crypto/xmss/index.md @@ -0,0 +1,3 @@ +# Generalized XMSS hash-based signature scheme. + +::: lean_spec.spec.crypto.xmss diff --git a/docs/reference/crypto/xmss/interface.md b/docs/reference/crypto/xmss/interface.md new file mode 100644 index 000000000..b6a802c47 --- /dev/null +++ b/docs/reference/crypto/xmss/interface.md @@ -0,0 +1,3 @@ +# Public interface for the Generalized XMSS signature scheme. + +::: lean_spec.spec.crypto.xmss.interface diff --git a/docs/reference/crypto/xmss/merkle.md b/docs/reference/crypto/xmss/merkle.md new file mode 100644 index 000000000..af5091465 --- /dev/null +++ b/docs/reference/crypto/xmss/merkle.md @@ -0,0 +1,3 @@ +# Sparse Merkle subtrees for the top-bottom traversal of an XMSS key. + +::: lean_spec.spec.crypto.xmss.merkle diff --git a/docs/reference/crypto/xmss/poseidon.md b/docs/reference/crypto/xmss/poseidon.md new file mode 100644 index 000000000..dffb105a7 --- /dev/null +++ b/docs/reference/crypto/xmss/poseidon.md @@ -0,0 +1,3 @@ +# Poseidon hash engine in compression and sponge modes for the Generalized XMSS scheme. + +::: lean_spec.spec.crypto.xmss.poseidon diff --git a/docs/reference/crypto/xmss/prf.md b/docs/reference/crypto/xmss/prf.md new file mode 100644 index 000000000..83a45e35b --- /dev/null +++ b/docs/reference/crypto/xmss/prf.md @@ -0,0 +1,3 @@ +# SHAKE128-based pseudorandom function for deterministic key derivation. + +::: lean_spec.spec.crypto.xmss.prf diff --git a/docs/reference/crypto/xmss/types.md b/docs/reference/crypto/xmss/types.md new file mode 100644 index 000000000..5c43f79a2 --- /dev/null +++ b/docs/reference/crypto/xmss/types.md @@ -0,0 +1,3 @@ +# Base types for the XMSS signature scheme. + +::: lean_spec.spec.crypto.xmss.types diff --git a/docs/reference/forks/index.md b/docs/reference/forks/index.md new file mode 100644 index 000000000..4e90fb857 --- /dev/null +++ b/docs/reference/forks/index.md @@ -0,0 +1,3 @@ +# Multi-fork dispatch layer for leanSpec consensus specification. + +::: lean_spec.spec.forks diff --git a/docs/reference/forks/lstar/aggregation.md b/docs/reference/forks/lstar/aggregation.md new file mode 100644 index 000000000..06a69f681 --- /dev/null +++ b/docs/reference/forks/lstar/aggregation.md @@ -0,0 +1,3 @@ +# Lstar fork — attestation aggregation. + +::: lean_spec.spec.forks.lstar.aggregation diff --git a/docs/reference/forks/lstar/block_production.md b/docs/reference/forks/lstar/block_production.md new file mode 100644 index 000000000..923efad5d --- /dev/null +++ b/docs/reference/forks/lstar/block_production.md @@ -0,0 +1,3 @@ +# Lstar fork — proposer-side block building. + +::: lean_spec.spec.forks.lstar.block_production diff --git a/docs/reference/forks/lstar/config.md b/docs/reference/forks/lstar/config.md new file mode 100644 index 000000000..6d1e30cae --- /dev/null +++ b/docs/reference/forks/lstar/config.md @@ -0,0 +1,3 @@ +# Chain and Consensus Configuration Specification + +::: lean_spec.spec.forks.lstar.config diff --git a/docs/reference/forks/lstar/containers/aggregation.md b/docs/reference/forks/lstar/containers/aggregation.md new file mode 100644 index 000000000..32708fe7b --- /dev/null +++ b/docs/reference/forks/lstar/containers/aggregation.md @@ -0,0 +1,3 @@ +# Post-quantum signature aggregation proofs wrapping the Rust prover. + +::: lean_spec.spec.forks.lstar.containers.aggregation diff --git a/docs/reference/forks/lstar/containers/attestation.md b/docs/reference/forks/lstar/containers/attestation.md new file mode 100644 index 000000000..142a4a28c --- /dev/null +++ b/docs/reference/forks/lstar/containers/attestation.md @@ -0,0 +1,3 @@ +# Attestation vote envelopes: signed, aggregated, and their list form. + +::: lean_spec.spec.forks.lstar.containers.attestation diff --git a/docs/reference/forks/lstar/containers/block.md b/docs/reference/forks/lstar/containers/block.md new file mode 100644 index 000000000..7c1a8dc22 --- /dev/null +++ b/docs/reference/forks/lstar/containers/block.md @@ -0,0 +1,3 @@ +# Block container family carrying attestations and the merged block proof. + +::: lean_spec.spec.forks.lstar.containers.block diff --git a/docs/reference/forks/lstar/containers/checkpoint.md b/docs/reference/forks/lstar/containers/checkpoint.md new file mode 100644 index 000000000..f4276f917 --- /dev/null +++ b/docs/reference/forks/lstar/containers/checkpoint.md @@ -0,0 +1,3 @@ +# Casper-FFG checkpoints and the attestation vote they anchor. + +::: lean_spec.spec.forks.lstar.containers.checkpoint diff --git a/docs/reference/forks/lstar/containers/genesis.md b/docs/reference/forks/lstar/containers/genesis.md new file mode 100644 index 000000000..722d1e75e --- /dev/null +++ b/docs/reference/forks/lstar/containers/genesis.md @@ -0,0 +1,3 @@ +# Chain configuration committed into the consensus state. + +::: lean_spec.spec.forks.lstar.containers.genesis diff --git a/docs/reference/forks/lstar/containers/identifiers.md b/docs/reference/forks/lstar/containers/identifiers.md new file mode 100644 index 000000000..04a5438f8 --- /dev/null +++ b/docs/reference/forks/lstar/containers/identifiers.md @@ -0,0 +1,3 @@ +# Scalar identifiers naming validators, subnets, and the registry index space. + +::: lean_spec.spec.forks.lstar.containers.identifiers diff --git a/docs/reference/forks/lstar/containers/index.md b/docs/reference/forks/lstar/containers/index.md new file mode 100644 index 000000000..b15c9e6fb --- /dev/null +++ b/docs/reference/forks/lstar/containers/index.md @@ -0,0 +1,3 @@ +# Container types for the Lean consensus specification. + +::: lean_spec.spec.forks.lstar.containers diff --git a/docs/reference/forks/lstar/containers/interval.md b/docs/reference/forks/lstar/containers/interval.md new file mode 100644 index 000000000..880ddf336 --- /dev/null +++ b/docs/reference/forks/lstar/containers/interval.md @@ -0,0 +1,3 @@ +# Interval time unit for the Lean consensus specification. + +::: lean_spec.spec.forks.lstar.containers.interval diff --git a/docs/reference/forks/lstar/containers/participation.md b/docs/reference/forks/lstar/containers/participation.md new file mode 100644 index 000000000..0c814b0cf --- /dev/null +++ b/docs/reference/forks/lstar/containers/participation.md @@ -0,0 +1,3 @@ +# Validator participation bitfields over the registry index space. + +::: lean_spec.spec.forks.lstar.containers.participation diff --git a/docs/reference/forks/lstar/containers/state.md b/docs/reference/forks/lstar/containers/state.md new file mode 100644 index 000000000..73f2ea0e8 --- /dev/null +++ b/docs/reference/forks/lstar/containers/state.md @@ -0,0 +1,3 @@ +# Consensus state and the justification/finalization accounting it tracks. + +::: lean_spec.spec.forks.lstar.containers.state diff --git a/docs/reference/forks/lstar/containers/store.md b/docs/reference/forks/lstar/containers/store.md new file mode 100644 index 000000000..42443c8fa --- /dev/null +++ b/docs/reference/forks/lstar/containers/store.md @@ -0,0 +1,3 @@ +# Fork-choice store: the node's local view of the chain. + +::: lean_spec.spec.forks.lstar.containers.store diff --git a/docs/reference/forks/lstar/containers/validator.md b/docs/reference/forks/lstar/containers/validator.md new file mode 100644 index 000000000..b9b9ef0c9 --- /dev/null +++ b/docs/reference/forks/lstar/containers/validator.md @@ -0,0 +1,3 @@ +# The validator registry tracked in the consensus state. + +::: lean_spec.spec.forks.lstar.containers.validator diff --git a/docs/reference/forks/lstar/errors.md b/docs/reference/forks/lstar/errors.md new file mode 100644 index 000000000..8017af06f --- /dev/null +++ b/docs/reference/forks/lstar/errors.md @@ -0,0 +1,3 @@ +# Rejection reasons and the typed error that carries them. + +::: lean_spec.spec.forks.lstar.errors diff --git a/docs/reference/forks/lstar/fork_choice.md b/docs/reference/forks/lstar/fork_choice.md new file mode 100644 index 000000000..f7e4c0557 --- /dev/null +++ b/docs/reference/forks/lstar/fork_choice.md @@ -0,0 +1,3 @@ +# Lstar fork — fork choice: store, LMD-GHOST, attestation handling. + +::: lean_spec.spec.forks.lstar.fork_choice diff --git a/docs/reference/forks/lstar/index.md b/docs/reference/forks/lstar/index.md new file mode 100644 index 000000000..d1ad4b10b --- /dev/null +++ b/docs/reference/forks/lstar/index.md @@ -0,0 +1,3 @@ +# Lstar fork + +::: lean_spec.spec.forks.lstar diff --git a/docs/reference/forks/lstar/signatures.md b/docs/reference/forks/lstar/signatures.md new file mode 100644 index 000000000..8cc004a9b --- /dev/null +++ b/docs/reference/forks/lstar/signatures.md @@ -0,0 +1,3 @@ +# Lstar fork — block signature verification. + +::: lean_spec.spec.forks.lstar.signatures diff --git a/docs/reference/forks/lstar/slot.md b/docs/reference/forks/lstar/slot.md new file mode 100644 index 000000000..e6756c4ce --- /dev/null +++ b/docs/reference/forks/lstar/slot.md @@ -0,0 +1,3 @@ +# Slot primitive shared by the consensus and crypto layers. + +::: lean_spec.spec.forks.lstar.slot diff --git a/docs/reference/forks/lstar/spec.md b/docs/reference/forks/lstar/spec.md new file mode 100644 index 000000000..891c7eb29 --- /dev/null +++ b/docs/reference/forks/lstar/spec.md @@ -0,0 +1,3 @@ +# Lstar fork — identity and construction facade. + +::: lean_spec.spec.forks.lstar.spec diff --git a/docs/reference/forks/lstar/state_transition.md b/docs/reference/forks/lstar/state_transition.md new file mode 100644 index 000000000..addab4a9c --- /dev/null +++ b/docs/reference/forks/lstar/state_transition.md @@ -0,0 +1,3 @@ +# Lstar fork — state transition. + +::: lean_spec.spec.forks.lstar.state_transition diff --git a/docs/reference/forks/lstar/timeline.md b/docs/reference/forks/lstar/timeline.md new file mode 100644 index 000000000..bcf7c1ef6 --- /dev/null +++ b/docs/reference/forks/lstar/timeline.md @@ -0,0 +1,3 @@ +# Lstar fork — interval ticking and time progression. + +::: lean_spec.spec.forks.lstar.timeline diff --git a/docs/reference/forks/lstar/validator_duties.md b/docs/reference/forks/lstar/validator_duties.md new file mode 100644 index 000000000..990b44ac8 --- /dev/null +++ b/docs/reference/forks/lstar/validator_duties.md @@ -0,0 +1,3 @@ +# Lstar fork — validator duties: proposal head and production. + +::: lean_spec.spec.forks.lstar.validator_duties diff --git a/docs/reference/forks/protocol.md b/docs/reference/forks/protocol.md new file mode 100644 index 000000000..6ca4f3add --- /dev/null +++ b/docs/reference/forks/protocol.md @@ -0,0 +1,3 @@ +# Fork protocol interface for leanSpec consensus. + +::: lean_spec.spec.forks.protocol diff --git a/docs/reference/forks/registry.md b/docs/reference/forks/registry.md new file mode 100644 index 000000000..d480c6720 --- /dev/null +++ b/docs/reference/forks/registry.md @@ -0,0 +1,3 @@ +# Registry of registered forks, ordered oldest to newest. + +::: lean_spec.spec.forks.registry diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 000000000..891bf2c2b --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,3 @@ +# Protocol specification: per-fork consensus rules and registry. + +::: lean_spec.spec diff --git a/docs/reference/observability/index.md b/docs/reference/observability/index.md new file mode 100644 index 000000000..2b06f8c91 --- /dev/null +++ b/docs/reference/observability/index.md @@ -0,0 +1,3 @@ +# Vendor-neutral observability for the Lean specification. + +::: lean_spec.spec.observability diff --git a/docs/reference/observability/observer.md b/docs/reference/observability/observer.md new file mode 100644 index 000000000..b92184e5e --- /dev/null +++ b/docs/reference/observability/observer.md @@ -0,0 +1,3 @@ +# Telemetry observer protocol and its process-wide singleton. + +::: lean_spec.spec.observability.observer diff --git a/docs/reference/ssz/bitfields.md b/docs/reference/ssz/bitfields.md new file mode 100644 index 000000000..fa86ad461 --- /dev/null +++ b/docs/reference/ssz/bitfields.md @@ -0,0 +1,3 @@ +# SSZ bitfield types. + +::: lean_spec.spec.ssz.bitfields diff --git a/docs/reference/ssz/boolean.md b/docs/reference/ssz/boolean.md new file mode 100644 index 000000000..5a4688187 --- /dev/null +++ b/docs/reference/ssz/boolean.md @@ -0,0 +1,3 @@ +# SSZ boolean type — true or false serialized as a single byte. + +::: lean_spec.spec.ssz.boolean diff --git a/docs/reference/ssz/byte_arrays.md b/docs/reference/ssz/byte_arrays.md new file mode 100644 index 000000000..32aeea38c --- /dev/null +++ b/docs/reference/ssz/byte_arrays.md @@ -0,0 +1,3 @@ +# SSZ byte array types. + +::: lean_spec.spec.ssz.byte_arrays diff --git a/docs/reference/ssz/collections.md b/docs/reference/ssz/collections.md new file mode 100644 index 000000000..5a4307291 --- /dev/null +++ b/docs/reference/ssz/collections.md @@ -0,0 +1,3 @@ +# SSZ vector and list collections. + +::: lean_spec.spec.ssz.collections diff --git a/docs/reference/ssz/container.md b/docs/reference/ssz/container.md new file mode 100644 index 000000000..f81d842f2 --- /dev/null +++ b/docs/reference/ssz/container.md @@ -0,0 +1,3 @@ +# SSZ Container Type. + +::: lean_spec.spec.ssz.container diff --git a/docs/reference/ssz/exceptions.md b/docs/reference/ssz/exceptions.md new file mode 100644 index 000000000..79cee04ef --- /dev/null +++ b/docs/reference/ssz/exceptions.md @@ -0,0 +1,3 @@ +# Exception hierarchy for the SSZ type system. + +::: lean_spec.spec.ssz.exceptions diff --git a/docs/reference/ssz/index.md b/docs/reference/ssz/index.md new file mode 100644 index 000000000..0d656fee5 --- /dev/null +++ b/docs/reference/ssz/index.md @@ -0,0 +1,3 @@ +# SSZ primitive types and (de)serialization for the Lean Ethereum specification. + +::: lean_spec.spec.ssz diff --git a/docs/reference/ssz/ssz_base.md b/docs/reference/ssz/ssz_base.md new file mode 100644 index 000000000..463f9fafb --- /dev/null +++ b/docs/reference/ssz/ssz_base.md @@ -0,0 +1,3 @@ +# Abstract bases for the SSZ type system. + +::: lean_spec.spec.ssz.ssz_base diff --git a/docs/reference/ssz/uint.md b/docs/reference/ssz/uint.md new file mode 100644 index 000000000..7f8edb7c1 --- /dev/null +++ b/docs/reference/ssz/uint.md @@ -0,0 +1,3 @@ +# Unsigned Integer Type Specification. + +::: lean_spec.spec.ssz.uint diff --git a/mkdocs.yml b/mkdocs.yml index 3a41eb66f..1ee401971 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,7 +44,7 @@ plugins: members_order: source separate_signature: true filters: ["!^_"] - docstring_section_style: google + docstring_style: google markdown_extensions: - admonition @@ -59,7 +59,67 @@ markdown_extensions: nav: - Home: index.md - + - Reference: + - Overview: reference/index.md + - crypto: + - Overview: reference/crypto/index.md + - koalabear: reference/crypto/koalabear.md + - merkleization: reference/crypto/merkleization.md + - poseidon: reference/crypto/poseidon.md + - xmss: + - Overview: reference/crypto/xmss/index.md + - constants: reference/crypto/xmss/constants.md + - containers: reference/crypto/xmss/containers.md + - encoding: reference/crypto/xmss/encoding.md + - field: reference/crypto/xmss/field.md + - interface: reference/crypto/xmss/interface.md + - merkle: reference/crypto/xmss/merkle.md + - poseidon: reference/crypto/xmss/poseidon.md + - prf: reference/crypto/xmss/prf.md + - types: reference/crypto/xmss/types.md + - forks: + - Overview: reference/forks/index.md + - lstar: + - Overview: reference/forks/lstar/index.md + - aggregation: reference/forks/lstar/aggregation.md + - block_production: reference/forks/lstar/block_production.md + - config: reference/forks/lstar/config.md + - containers: + - Overview: reference/forks/lstar/containers/index.md + - aggregation: reference/forks/lstar/containers/aggregation.md + - attestation: reference/forks/lstar/containers/attestation.md + - block: reference/forks/lstar/containers/block.md + - checkpoint: reference/forks/lstar/containers/checkpoint.md + - genesis: reference/forks/lstar/containers/genesis.md + - identifiers: reference/forks/lstar/containers/identifiers.md + - interval: reference/forks/lstar/containers/interval.md + - participation: reference/forks/lstar/containers/participation.md + - state: reference/forks/lstar/containers/state.md + - store: reference/forks/lstar/containers/store.md + - validator: reference/forks/lstar/containers/validator.md + - errors: reference/forks/lstar/errors.md + - fork_choice: reference/forks/lstar/fork_choice.md + - signatures: reference/forks/lstar/signatures.md + - slot: reference/forks/lstar/slot.md + - spec: reference/forks/lstar/spec.md + - state_transition: reference/forks/lstar/state_transition.md + - timeline: reference/forks/lstar/timeline.md + - validator_duties: reference/forks/lstar/validator_duties.md + - protocol: reference/forks/protocol.md + - registry: reference/forks/registry.md + - observability: + - Overview: reference/observability/index.md + - observer: reference/observability/observer.md + - ssz: + - Overview: reference/ssz/index.md + - bitfields: reference/ssz/bitfields.md + - boolean: reference/ssz/boolean.md + - byte_arrays: reference/ssz/byte_arrays.md + - collections: reference/ssz/collections.md + - container: reference/ssz/container.md + - exceptions: reference/ssz/exceptions.md + - ssz_base: reference/ssz/ssz_base.md + - uint: reference/ssz/uint.md extra: social: - icon: fontawesome/brands/github diff --git a/tools/generate_docs.py b/tools/generate_docs.py new file mode 100644 index 000000000..d724e5991 --- /dev/null +++ b/tools/generate_docs.py @@ -0,0 +1,186 @@ +""" +Generate MkDocs reference pages from leanSpec Python specification docstrings. + +The generator walks the ``lean_spec.spec`` package, emits one MkDocs page per +module using the ``mkdocstrings`` ``:::`` directive, and rewrites the ``nav`` +section of ``mkdocs.yml`` so the reference documentation stays in sync with the +Python source and no manual markdown duplication is required. + +Usage:: + + uv run python tools/generate_docs.py +""" + +from __future__ import annotations + +import re +import shutil +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +SOURCE_ROOT = REPOSITORY_ROOT / "src" +SPEC_PACKAGE = "lean_spec.spec" +SPEC_PACKAGE_DIRECTORY = SOURCE_ROOT / SPEC_PACKAGE.replace(".", "/") +REFERENCE_DIRECTORY = REPOSITORY_ROOT / "docs" / "reference" +DOCS_DIRECTORY = REPOSITORY_ROOT / "docs" +MKDOCS_CONFIGURATION = REPOSITORY_ROOT / "mkdocs.yml" + +MODULE_DOCSTRING_PATTERN = re.compile( + r'^\s*(?:r|u|f|rf|fr)?"""(.*?)"""', + re.DOTALL, +) + + +def discover_source_files() -> list[Path]: + """ + Return the spec package source files in deterministic order. + + Package ``__init__`` files are kept as section pages. Private modules whose + names start with an underscore are omitted from the reference. + """ + return [ + source_file + for source_file in sorted(SPEC_PACKAGE_DIRECTORY.rglob("*.py")) + if source_file.name == "__init__.py" or not source_file.stem.startswith("_") + ] + + +def import_path(source_file: Path) -> str: + """Compute the dotted Python import path for a source file.""" + path_parts = list(source_file.relative_to(SPEC_PACKAGE_DIRECTORY).with_suffix("").parts) + if path_parts and path_parts[-1] == "__init__": + path_parts = path_parts[:-1] + if not path_parts: + return SPEC_PACKAGE + return f"{SPEC_PACKAGE}.{'.'.join(path_parts)}" + + +def reference_page_path(source_file: Path) -> Path: + """Compute the generated Markdown page path for a source file.""" + path_parts = list(source_file.relative_to(SPEC_PACKAGE_DIRECTORY).with_suffix("").parts) + if source_file.name == "__init__.py": + path_parts = path_parts[:-1] + ["index"] + return REFERENCE_DIRECTORY.joinpath(*path_parts).with_suffix(".md") + + +def module_title(source_file: Path) -> str: + """Extract a display title from the module docstring first line.""" + docstring_match = MODULE_DOCSTRING_PATTERN.search(source_file.read_text(encoding="utf-8")) + if docstring_match: + for line in docstring_match.group(1).splitlines(): + stripped_line = line.strip() + if stripped_line: + return stripped_line + return import_path(source_file).rsplit(".", 1)[-1] + + +def page_reference(reference_page: Path) -> str: + """Compute the MkDocs page reference relative to the docs directory.""" + return reference_page.relative_to(DOCS_DIRECTORY).as_posix() + + +def render_page(title: str, module: str) -> str: + """Render a single reference page.""" + return f"# {title}\n\n::: {module}\n" + + +def build_navigation_tree(page_entries: list[tuple[list[str], str]]) -> dict: + """Build a nested tree from flat page entries.""" + navigation_tree: dict = {} + for path_parts, reference in page_entries: + navigation_node = navigation_tree + for path_part in path_parts: + navigation_node = navigation_node.setdefault(path_part, {}) + navigation_node["__page__"] = reference + return navigation_tree + + +def serialize_navigation_node(navigation_node: dict, node_name: str) -> dict: + """ + Serialize a navigation node into a single MkDocs nav entry. + + A node with no children is a leaf page. A node with children becomes a + section whose own page is exposed as an ``Overview`` child. + """ + child_nodes = {name: value for name, value in navigation_node.items() if name != "__page__"} + node_page = navigation_node.get("__page__") + + if not child_nodes: + return {node_name: node_page} if node_page is not None else {node_name: []} + + section_children: list = [] + if node_page is not None: + section_children.append({"Overview": node_page}) + for child_name in sorted(child_nodes): + section_children.append(serialize_navigation_node(child_nodes[child_name], child_name)) + + return {node_name: section_children} + + +def format_navigation_entries(entries: list, indent: int = 0) -> list[str]: + """Format nested nav entries as indented YAML lines.""" + lines: list[str] = [] + indentation = " " * indent + for entry in entries: + for name, value in entry.items(): + if isinstance(value, str): + lines.append(f"{indentation}- {name}: {value}") + else: + lines.append(f"{indentation}- {name}:") + lines.extend(format_navigation_entries(value, indent + 4)) + return lines + + +def rewrite_navigation(navigation_lines: list[str]) -> None: + """Replace the ``nav`` block of ``mkdocs.yml`` with generated entries.""" + configuration_text = MKDOCS_CONFIGURATION.read_text(encoding="utf-8") + navigation_block = "\n".join(navigation_lines) + new_navigation = f"nav:\n{navigation_block}\n" + navigation_pattern = re.compile(r"(?m)^nav:.*?(?=^[a-zA-Z_][a-zA-Z0-9_]*:\s)", re.DOTALL) + updated_configuration = navigation_pattern.sub( + lambda _: new_navigation, configuration_text, count=1 + ) + MKDOCS_CONFIGURATION.write_text(updated_configuration, encoding="utf-8") + + +def main() -> None: + """Generate reference pages and update the MkDocs navigation.""" + if REFERENCE_DIRECTORY.exists(): + shutil.rmtree(REFERENCE_DIRECTORY) + REFERENCE_DIRECTORY.mkdir(parents=True) + + page_entries: list[tuple[list[str], str]] = [] + + for source_file in discover_source_files(): + module = import_path(source_file) + title = module_title(source_file) + reference_page = reference_page_path(source_file) + + reference_page.parent.mkdir(parents=True, exist_ok=True) + reference_page.write_text(render_page(title, module), encoding="utf-8") + + path_parts = list(source_file.relative_to(SPEC_PACKAGE_DIRECTORY).with_suffix("").parts) + if source_file.name == "__init__.py": + path_parts = path_parts[:-1] + page_entries.append((path_parts, page_reference(reference_page))) + + navigation_tree = build_navigation_tree(page_entries) + + reference_entries: list = [] + root_page = navigation_tree.get("__page__") + root_children = {name: value for name, value in navigation_tree.items() if name != "__page__"} + if root_page is not None: + reference_entries.append({"Overview": root_page}) + for child_name in sorted(root_children): + reference_entries.append(serialize_navigation_node(root_children[child_name], child_name)) + + navigation_lines = [" - Home: index.md", " - Reference:"] + navigation_lines.extend(format_navigation_entries(reference_entries, indent=4)) + rewrite_navigation(navigation_lines) + + print(f"Generated {len(page_entries)} reference pages under docs/reference/") + print(f"Updated navigation in {MKDOCS_CONFIGURATION}") + + +if __name__ == "__main__": + main()