# Starlark docgen Using the `sphinx_stardoc` rule, API documentation can be generated from bzl source code. This rule requires both MyST-based markdown and the `sphinx_bzl` Sphinx extension are enabled. This allows source code to use Markdown and Sphinx syntax to create rich documentation with cross references, types, and more. ## Configuring Sphinx While the `sphinx_stardoc` rule doesn't require Sphinx itself, the source it generates requires some additional Sphinx plugins and config settings. When defining the `sphinx_build_binary` target, also depend on: * `@rules_python//sphinxdocs/src/sphinx_bzl:sphinx_bzl` * `myst_parser` (e.g. `@pypi//myst_parser`) * `typing_extensions` (e.g. `@pypi//myst_parser`) ``` sphinx_build_binary( name = "sphinx-build", deps = [ "@rules_python//sphinxdocs/src/sphinx_bzl", "@pypi//myst_parser", "@pypi//typing_extensions", ... ] ) ``` In `conf.py`, enable the `sphinx_bzl` extension, `myst_parser` extension, and the `colon_fence` MyST extension. ``` extensions = [ "myst_parser", "sphinx_bzl.bzl", ] myst_enable_extensions = [ "colon_fence", ] ``` ## Generating docs from bzl files To convert the bzl code to Sphinx doc sources, `sphinx_stardocs` is the primary rule to do so. It takes a list of `bzl_library` targets or files and generates docs for each. When a `bzl_library` target is passed, the `bzl_library.srcs` value can only have a single file. Example: ``` sphinx_stardocs( name = "my_docs", srcs = [ ":binary_bzl", ":library_bzl", ] ) bzl_library( name = "binary_bzl", srcs = ["binary.bzl"], deps = ... ) bzl_library( name = "library_bzl", srcs = ["library.bzl"], deps = ... ) ``` ## User-defined types While Starlark doesn't have user-defined types as a first-class concept, it's still possible to create such objects using `struct` and lambdas. For the purposes of documentation, they can be documented by creating a module-level `struct` with matching fields *and* also a field named `TYPEDEF`. When the `sphinx_stardoc` rule sees a struct with a `TYPEDEF` field, it generates doc using the {rst:directive}`bzl:typedef` directive and puts all the struct's fields within the typedef. The net result is the rendered docs look similar to how a class would be documented in other programming languages. For example, a the Starlark implemenation of a `Square` object with a `area()` method would look like: ``` def _Square_typedef(): """A square with fixed size. :::{field} width :type: int ::: """ def _Square_new(width): """Creates a Square. Args: width: {type}`int` width of square Returns: {type}`Square` """ self = struct( area = lambda *a, **k: _Square_area(self, *a, **k), width = width ) return self def _Square_area(self, ): """Tells the area of the square.""" return self.width * self.width Square = struct( TYPEDEF = _Square_typedef, new = _Square_new, area = _Square_area, ) ``` This will then genereate markdown that looks like: ``` ::::{bzl:typedef} Square A square with fixed size :::{bzl:field} width :type: int ::: :::{bzl:function} new() ...args etc from _Square_new... ::: :::{bzl:function} area() ...args etc from _Square_area... ::: :::: ``` Which renders as: :::{bzl:currentfile} //example:square.bzl ::: ::::{bzl:typedef} Square A square with fixed size :::{bzl:field} width :type: int ::: :::{bzl:function} new() ... ::: :::{bzl:function} area() ... ::: ::::