-
Notifications
You must be signed in to change notification settings - Fork 4
/
ile_doc.py
executable file
·128 lines (106 loc) · 4.75 KB
/
ile_doc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
import re
from typing import List, Type, get_args, get_origin, get_type_hints
import pdoc
from ile import ILE_COMPONENTS
ILE_SIMPLE_CONFIG = 'ile_configs/auto_generated_null_template.yaml'
ILE_API_FILENAME = 'ILE_API.md'
ILE_API_HEADER = """# ILE API
[How to Use the ILE](./README.md)
#### Table of Content
- [Lists](#Lists)
- [Classes](#Classes)
- [Options](#Options)
## Lists
- [Agent settings](https://nextcenturycorporation.github.io/MCS/schema.html#agent-settings)
- [Interactable shapes](https://nextcenturycorporation.github.io/MCS/schema.html#interactable-objects)
- [Materials](https://nextcenturycorporation.github.io/MCS/schema.html#material-list)
- [Tool shapes](https://nextcenturycorporation.github.io/MCS/schema.html#tool-objects)
## Classes
Some [configurable ILE options](#Options) use the following classes
(represented as dicts in the YAML):
"""
ILE_API_MIDDLE = """
## Options
You may set the following options in your ILE (YAML) config file:
"""
ILE_COMPONENT_NAME = 'ideal_learning_env.components.ILEComponent'
REGEX_TO_MATCH_SIMPLE_EXAMPLE = "Simple Example:\n```\n\s*?((.|\n)*?)\n\s*?```" # noqa: E501, W605
def retrieve_all_types(typing: Type) -> List[Type]:
"""Return a list containing the given type (and all its non-primitive
properties, if it's a class), or, if the type has nested types (because
it's a list, union, etc.), return all those types."""
types: List[Type] = []
# If not a list or union...
if not get_origin(typing):
if not isinstance(typing, (dict, float, int, str, tuple)):
for nested in get_type_hints(typing).values():
types.extend(retrieve_all_types(nested))
return [typing] + types
# Ignore all primitive types.
return []
# Else if a list or union...
for nested in list(get_args(typing)):
types.extend(retrieve_all_types(nested))
return types
def main():
"""Auto-generate the ILE API markdown doc and example YAML config file."""
# Gather all of the ILE classes that are used in config properties.
types = []
for component in ILE_COMPONENTS:
for _, typing in get_type_hints(component).items():
types.extend(retrieve_all_types(typing))
types = [
typing for typing in set(types)
if typing.__module__.startswith('ideal_learning_env.')
]
# Save all of the names of the ILE classes from the types.
names = [f'{typing.__module__}.{typing.__name__}' for typing in types]
# Gather all of the files containing ILE components or types.
files = list(set([cls.__module__ for cls in ILE_COMPONENTS] + [
typing.__module__ for typing in types
]))
# Read the docstrings from each ILE class with pdoc.
context = pdoc.Context()
modules = [pdoc.Module(element, context=context) for element in files]
pdoc.link_inheritance(context)
# Retrieve a variable object for each class variable (ILE config file
# property) in each ILE module that contains the variable's docstring.
variables = []
# Retrieve a class object for each ILE class used in config properties.
configs = []
for mod in modules:
for cls in mod.classes():
name = f'{cls.module.name}.{cls.name}'
ancestors = [ancestor.name for ancestor in cls.mro()]
# Ignore all helper classes defined in component files...
if ILE_COMPONENT_NAME in ancestors:
# Ignore class variables with UPPER_CASE names because they're
# probably static class variables, not config variables.
variables.extend([
var for var in cls.class_variables()
if var.name.islower()
])
# ...unless those classes are used as config properties.
if name in names:
configs.extend([cls])
# Sort the variables from all ILE components together, since users don't
# need to know which specific config variables belong to which components.
variables.sort(key=lambda x: x.name)
configs.sort(key=lambda x: x.name)
# Auto-generate and save the ILE API markdown doc.
with open(ILE_API_FILENAME, 'w') as ile_api:
ile_api.write(ILE_API_HEADER)
for cls in configs:
ile_api.write(f'#### {cls.name}\n\n{cls.docstring}\n\n')
ile_api.write(ILE_API_MIDDLE)
for var in variables:
ile_api.write(f'#### {var.name}\n\n{var.docstring}\n\n')
# Auto-generate and save the example YAML configuration files.
with open(ILE_SIMPLE_CONFIG, 'w') as ile_config:
for var in variables:
match = re.search(REGEX_TO_MATCH_SIMPLE_EXAMPLE, var.docstring)
if match:
ile_config.write(f'{match.group(1)}\n')
if __name__ == '__main__':
main()