Skip to content

Commit

Permalink
feat(autoaliasattr): Implement documentation of TypeVar's (#3818)
Browse files Browse the repository at this point in the history
* feat(autoaliasattr): Implement Documentation of TypeVar's

* Feedback

---------

Co-authored-by: Francisco Manríquez Novoa <[email protected]>
  • Loading branch information
JasonGrace2282 and chopan050 authored Jun 26, 2024
1 parent a70aeee commit 93a20cd
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 12 deletions.
45 changes: 41 additions & 4 deletions manim/utils/docbuild/autoaliasattr_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
__all__ = ["AliasAttrDocumenter"]


ALIAS_DOCS_DICT, DATA_DICT = parse_module_attributes()
ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT = parse_module_attributes()
ALIAS_LIST = [
alias_name
for module_dict in ALIAS_DOCS_DICT.values()
Expand Down Expand Up @@ -100,10 +100,11 @@ class AliasAttrDocumenter(Directive):

def run(self) -> list[nodes.Element]:
module_name = self.arguments[0]
# Slice module_name[6:] to remove the "manim." prefix which is
# not present in the keys of the DICTs
module_alias_dict = ALIAS_DOCS_DICT.get(module_name[6:], None)
module_attrs_list = DATA_DICT.get(module_name[6:], None)
module_name = module_name.removeprefix("manim.")
module_alias_dict = ALIAS_DOCS_DICT.get(module_name, None)
module_attrs_list = DATA_DICT.get(module_name, None)
module_typevars = TYPEVAR_DICT.get(module_name, None)

content = nodes.container()

Expand Down Expand Up @@ -161,6 +162,11 @@ def run(self) -> list[nodes.Element]:
for A in ALIAS_LIST:
alias_doc = alias_doc.replace(f"`{A}`", f":class:`~.{A}`")

# also hyperlink the TypeVars from that module
if module_typevars is not None:
for T in module_typevars:
alias_doc = alias_doc.replace(f"`{T}`", f":class:`{T}`")

# Add all the lines with 4 spaces behind, to consider all the
# documentation as a paragraph INSIDE the `.. class::` block
doc_lines = alias_doc.split("\n")
Expand All @@ -172,6 +178,37 @@ def run(self) -> list[nodes.Element]:
self.state.nested_parse(unparsed, 0, alias_container)
category_alias_container += alias_container

# then add the module TypeVars section
if module_typevars is not None:
module_typevars_section = nodes.section(ids=[f"{module_name}.typevars"])
content += module_typevars_section

# Use a rubric (title-like), just like in `module.rst`
module_typevars_section += nodes.rubric(text="TypeVar's")

# name: str
# definition: TypeVarDict = dict[str, str]
for name, definition in module_typevars.items():
# Using the `.. class::` directive is CRUCIAL, since
# function/method parameters are always annotated via
# classes - therefore Sphinx expects a class
unparsed = ViewList(
[
f".. class:: {name}",
"",
" .. parsed-literal::",
"",
f" {definition}",
"",
]
)

# Parse the reST text into a fresh container
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
typevar_container = nodes.container()
self.state.nested_parse(unparsed, 0, typevar_container)
module_typevars_section += typevar_container

# Then, add the traditional "Module Attributes" section
if module_attrs_list is not None:
module_attrs_section = nodes.section(ids=[f"{module_name}.data"])
Expand Down
49 changes: 41 additions & 8 deletions manim/utils/docbuild/module_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
classified by category in different `AliasCategoryDict` objects.
"""

ModuleTypeVarDict: TypeAlias = dict[str, str]
"""Dictionary containing every :class:`TypeVar` defined in a module."""


AliasDocsDict: TypeAlias = dict[str, ModuleLevelAliasDict]
"""Dictionary which, for every module in Manim, contains documentation
about their module-level attributes which are explicitly defined as
Expand All @@ -39,8 +43,12 @@
explicitly defined as :class:`TypeAlias`.
"""

TypeVarDict: TypeAlias = dict[str, ModuleTypeVarDict]
"""A dictionary mapping module names to dictionaries of :class:`TypeVar` objects."""

ALIAS_DOCS_DICT: AliasDocsDict = {}
DATA_DICT: DataDict = {}
TYPEVAR_DICT: TypeVarDict = {}

MANIM_ROOT = Path(__file__).resolve().parent.parent.parent

Expand All @@ -50,27 +58,32 @@
# ruff: noqa: E721


def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict, TypeVarDict]:
"""Read all files, generate Abstract Syntax Trees from them, and
extract useful information about the type aliases defined in the
files: the category they belong to, their definition and their
description, separating them from the "regular" module attributes.
Returns
-------
ALIAS_DOCS_DICT : `AliasDocsDict`
ALIAS_DOCS_DICT : :class:`AliasDocsDict`
A dictionary containing the information from all the type
aliases in Manim. See `AliasDocsDict` for more information.
aliases in Manim. See :class:`AliasDocsDict` for more information.
DATA_DICT : `DataDict`
DATA_DICT : :class:`DataDict`
A dictionary containing the names of all DOCUMENTED
module-level attributes which are not a :class:`TypeAlias`.
TYPEVAR_DICT : :class:`TypeVarDict`
A dictionary containing the definitions of :class:`TypeVar` objects,
organized by modules.
"""
global ALIAS_DOCS_DICT
global DATA_DICT
global TYPEVAR_DICT

if ALIAS_DOCS_DICT or DATA_DICT:
return ALIAS_DOCS_DICT, DATA_DICT
if ALIAS_DOCS_DICT or DATA_DICT or TYPEVAR_DICT:
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT

for module_path in MANIM_ROOT.rglob("*.py"):
module_name = module_path.resolve().relative_to(MANIM_ROOT)
Expand All @@ -85,6 +98,9 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
category_dict: AliasCategoryDict | None = None
alias_info: AliasInfo | None = None

# For storing TypeVars
module_typevars: ModuleTypeVarDict = {}

# For storing regular module attributes
data_list: list[str] = []
data_name: str | None = None
Expand Down Expand Up @@ -172,6 +188,19 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
alias_info = category_dict[alias_name]
continue

# Check if it is a typing.TypeVar
elif (
type(node) is ast.Assign
and type(node.targets[0]) is ast.Name
and type(node.value) is ast.Call
and type(node.value.func) is ast.Name
and node.value.func.id.endswith("TypeVar")
):
module_typevars[node.targets[0].id] = ast.unparse(
node.value
).replace("_", r"\_")
continue

# If here, the node is not a TypeAlias definition
alias_info = None

Expand All @@ -185,7 +214,9 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
else:
target = None

if type(target) is ast.Name:
if type(target) is ast.Name and not (
type(node) is ast.Assign and target.id not in module_typevars
):
data_name = target.id
else:
data_name = None
Expand All @@ -194,5 +225,7 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
ALIAS_DOCS_DICT[module_name] = module_dict
if len(data_list) > 0:
DATA_DICT[module_name] = data_list
if module_typevars:
TYPEVAR_DICT[module_name] = module_typevars

return ALIAS_DOCS_DICT, DATA_DICT
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT

0 comments on commit 93a20cd

Please sign in to comment.