Skip to content

Commit 93a20cd

Browse files
feat(autoaliasattr): Implement documentation of TypeVar's (#3818)
* feat(autoaliasattr): Implement Documentation of TypeVar's * Feedback --------- Co-authored-by: Francisco Manríquez Novoa <[email protected]>
1 parent a70aeee commit 93a20cd

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed

manim/utils/docbuild/autoaliasattr_directive.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
__all__ = ["AliasAttrDocumenter"]
1717

1818

19-
ALIAS_DOCS_DICT, DATA_DICT = parse_module_attributes()
19+
ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT = parse_module_attributes()
2020
ALIAS_LIST = [
2121
alias_name
2222
for module_dict in ALIAS_DOCS_DICT.values()
@@ -100,10 +100,11 @@ class AliasAttrDocumenter(Directive):
100100

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

108109
content = nodes.container()
109110

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

165+
# also hyperlink the TypeVars from that module
166+
if module_typevars is not None:
167+
for T in module_typevars:
168+
alias_doc = alias_doc.replace(f"`{T}`", f":class:`{T}`")
169+
164170
# Add all the lines with 4 spaces behind, to consider all the
165171
# documentation as a paragraph INSIDE the `.. class::` block
166172
doc_lines = alias_doc.split("\n")
@@ -172,6 +178,37 @@ def run(self) -> list[nodes.Element]:
172178
self.state.nested_parse(unparsed, 0, alias_container)
173179
category_alias_container += alias_container
174180

181+
# then add the module TypeVars section
182+
if module_typevars is not None:
183+
module_typevars_section = nodes.section(ids=[f"{module_name}.typevars"])
184+
content += module_typevars_section
185+
186+
# Use a rubric (title-like), just like in `module.rst`
187+
module_typevars_section += nodes.rubric(text="TypeVar's")
188+
189+
# name: str
190+
# definition: TypeVarDict = dict[str, str]
191+
for name, definition in module_typevars.items():
192+
# Using the `.. class::` directive is CRUCIAL, since
193+
# function/method parameters are always annotated via
194+
# classes - therefore Sphinx expects a class
195+
unparsed = ViewList(
196+
[
197+
f".. class:: {name}",
198+
"",
199+
" .. parsed-literal::",
200+
"",
201+
f" {definition}",
202+
"",
203+
]
204+
)
205+
206+
# Parse the reST text into a fresh container
207+
# https://www.sphinx-doc.org/en/master/extdev/markupapi.html#parsing-directive-content-as-rest
208+
typevar_container = nodes.container()
209+
self.state.nested_parse(unparsed, 0, typevar_container)
210+
module_typevars_section += typevar_container
211+
175212
# Then, add the traditional "Module Attributes" section
176213
if module_attrs_list is not None:
177214
module_attrs_section = nodes.section(ids=[f"{module_name}.data"])

manim/utils/docbuild/module_parsing.py

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
classified by category in different `AliasCategoryDict` objects.
2727
"""
2828

29+
ModuleTypeVarDict: TypeAlias = dict[str, str]
30+
"""Dictionary containing every :class:`TypeVar` defined in a module."""
31+
32+
2933
AliasDocsDict: TypeAlias = dict[str, ModuleLevelAliasDict]
3034
"""Dictionary which, for every module in Manim, contains documentation
3135
about their module-level attributes which are explicitly defined as
@@ -39,8 +43,12 @@
3943
explicitly defined as :class:`TypeAlias`.
4044
"""
4145

46+
TypeVarDict: TypeAlias = dict[str, ModuleTypeVarDict]
47+
"""A dictionary mapping module names to dictionaries of :class:`TypeVar` objects."""
48+
4249
ALIAS_DOCS_DICT: AliasDocsDict = {}
4350
DATA_DICT: DataDict = {}
51+
TYPEVAR_DICT: TypeVarDict = {}
4452

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

@@ -50,27 +58,32 @@
5058
# ruff: noqa: E721
5159

5260

53-
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
61+
def parse_module_attributes() -> tuple[AliasDocsDict, DataDict, TypeVarDict]:
5462
"""Read all files, generate Abstract Syntax Trees from them, and
5563
extract useful information about the type aliases defined in the
5664
files: the category they belong to, their definition and their
5765
description, separating them from the "regular" module attributes.
5866
5967
Returns
6068
-------
61-
ALIAS_DOCS_DICT : `AliasDocsDict`
69+
ALIAS_DOCS_DICT : :class:`AliasDocsDict`
6270
A dictionary containing the information from all the type
63-
aliases in Manim. See `AliasDocsDict` for more information.
71+
aliases in Manim. See :class:`AliasDocsDict` for more information.
6472
65-
DATA_DICT : `DataDict`
73+
DATA_DICT : :class:`DataDict`
6674
A dictionary containing the names of all DOCUMENTED
6775
module-level attributes which are not a :class:`TypeAlias`.
76+
77+
TYPEVAR_DICT : :class:`TypeVarDict`
78+
A dictionary containing the definitions of :class:`TypeVar` objects,
79+
organized by modules.
6880
"""
6981
global ALIAS_DOCS_DICT
7082
global DATA_DICT
83+
global TYPEVAR_DICT
7184

72-
if ALIAS_DOCS_DICT or DATA_DICT:
73-
return ALIAS_DOCS_DICT, DATA_DICT
85+
if ALIAS_DOCS_DICT or DATA_DICT or TYPEVAR_DICT:
86+
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT
7487

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

101+
# For storing TypeVars
102+
module_typevars: ModuleTypeVarDict = {}
103+
88104
# For storing regular module attributes
89105
data_list: list[str] = []
90106
data_name: str | None = None
@@ -172,6 +188,19 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
172188
alias_info = category_dict[alias_name]
173189
continue
174190

191+
# Check if it is a typing.TypeVar
192+
elif (
193+
type(node) is ast.Assign
194+
and type(node.targets[0]) is ast.Name
195+
and type(node.value) is ast.Call
196+
and type(node.value.func) is ast.Name
197+
and node.value.func.id.endswith("TypeVar")
198+
):
199+
module_typevars[node.targets[0].id] = ast.unparse(
200+
node.value
201+
).replace("_", r"\_")
202+
continue
203+
175204
# If here, the node is not a TypeAlias definition
176205
alias_info = None
177206

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

188-
if type(target) is ast.Name:
217+
if type(target) is ast.Name and not (
218+
type(node) is ast.Assign and target.id not in module_typevars
219+
):
189220
data_name = target.id
190221
else:
191222
data_name = None
@@ -194,5 +225,7 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
194225
ALIAS_DOCS_DICT[module_name] = module_dict
195226
if len(data_list) > 0:
196227
DATA_DICT[module_name] = data_list
228+
if module_typevars:
229+
TYPEVAR_DICT[module_name] = module_typevars
197230

198-
return ALIAS_DOCS_DICT, DATA_DICT
231+
return ALIAS_DOCS_DICT, DATA_DICT, TYPEVAR_DICT

0 commit comments

Comments
 (0)