158 lines
5.1 KiB
Python
Executable File

# Released under the MIT License. See LICENSE for details.
#
"""Documentation generation functionality."""
from __future__ import annotations
import os
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
from efro.terminal import Clr
if TYPE_CHECKING:
pass
@dataclass
class AttributeInfo:
"""Info about an attribute of a class."""
name: str
attr_type: str | None = None
docs: str | None = None
def parse_docs_attrs(attrs: list[AttributeInfo], docs: str) -> str:
"""Given a docs str, parses attribute descriptions contained within."""
docs_lines = docs.splitlines()
attr_line = None
for i, line in enumerate(docs_lines):
if line.strip() in ['Attributes:', 'Attrs:']:
attr_line = i
break
if attr_line is not None:
# Docs is now everything *up to* this.
docs = '\n'.join(docs_lines[:attr_line])
# Go through remaining lines creating attrs and docs for each.
cur_attr: AttributeInfo | None = None
for i in range(attr_line + 1, len(docs_lines)):
line = docs_lines[i].strip()
# A line with a single alphanumeric word preceding a colon
# is a new attr.
splits = line.split(' ', maxsplit=1)
if splits[0].replace('_', '').isalnum() and splits[-1].endswith(
':'
):
if cur_attr is not None:
attrs.append(cur_attr)
cur_attr = AttributeInfo(name=splits[0])
if len(splits) == 2:
# Remove brackets and convert from
# (type): to type.
cur_attr.attr_type = splits[1][1:-2]
# Any other line gets tacked onto the current attr.
else:
if cur_attr is not None:
if cur_attr.docs is None:
cur_attr.docs = ''
cur_attr.docs += line + '\n'
# Finish out last.
if cur_attr is not None:
attrs.append(cur_attr)
for attr in attrs:
if attr.docs is not None:
attr.docs = attr.docs.strip()
return docs
def generate_pdoc(projroot: str) -> None:
"""Generate a set of pdoc documentation."""
from batools import apprun
del projroot # Unused.
# Assemble and launch an app and do our docs generation from there.
# Note: we set EFRO_SUPPRESS_SET_CANONICAL_MODULE_NAMES because pdoc
# spits out lots of "UserWarning: cannot determine where FOO was
# taken from" warnings if not. Haven't actually seen what difference
# it makes in the output though. Basically the canonical names stuff
# makes things like bascenev1._actor.Actor show up as
# bascenev1.Actor instead.
if bool(True):
# Gen docs from the engine.
apprun.python_command(
'import batools.docs; batools.docs._run_pdoc_in_engine()',
purpose='pdocs generation',
include_project_tools=True,
env=dict(os.environ, EFRO_SUPPRESS_SET_CANONICAL_MODULE_NAMES='1'),
)
else:
# Gen docs using dummy modules.
_run_pdoc_with_dummy_modules()
def _run_pdoc_with_dummy_modules() -> None:
"""Generate docs outside of the engine using our dummy modules.
Dummy modules stand in for native engine modules, and should be
just intact enough for us to spit out docs from. The upside is
that they have full typing information about arguments/etc. so our
docs will be more complete than if we talk to the live engine.
"""
raise RuntimeError('UNDER CONSTRUCTION')
def _run_pdoc_in_engine() -> None:
"""Generate docs from within the running engine.
The upside of this way is we have all built-in native modules
available. The downside is that we don't have typing information for
those modules aside from what's embedded in their docstrings (which
is not parsed by pdoc). So we get lots of 'unknown' arg types in
docs/etc.
The ideal solution might be to start writing .pyi files for our
native modules to provide their type information instead of or in
addition to our dummy-module approach. Just need to see how that
works with our pipeline.
"""
import time
import pdoc
from batools.version import get_current_version
starttime = time.monotonic()
# Tell pdoc to go through all the modules in ba_data/python.
modulenames = sorted(
n.removesuffix('.py')
for n in os.listdir('src/assets/ba_data/python')
if not n.startswith('.')
)
assert modulenames
templatesdir = Path('src/assets/pdoc/templates')
assert templatesdir.is_dir()
version, buildnum = get_current_version()
pdoc.render.env.globals['ba_version'] = version
pdoc.render.env.globals['ba_build'] = buildnum
pdoc.render.configure(
search=True,
show_source=True,
template_directory=Path('src/assets/pdoc/templates'),
)
pdoc.pdoc(*modulenames, output_directory=Path('build/docs_pdoc_html'))
duration = time.monotonic() - starttime
print(f'{Clr.GRN}Generated pdoc documentation in {duration:.1f}s.{Clr.RST}')