Skip to content

Commit dd67a69

Browse files
committed
roughly working automatic compile docs generation
1 parent 9d697ba commit dd67a69

11 files changed

Lines changed: 233 additions & 9 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# _docgen
2+
3+
Tools for generating documentation for the tools import `paths_cli.compiling`.
4+
Note that this entire directory is considered outside the API, so nothing in
5+
here should be strongly relied on.
6+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .config_handler import load_config, DocCategoryInfo
2+
from .docs_generator import DocsGenerator
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from collections import namedtuple
2+
from paths_cli.commands.compile import select_loader
3+
4+
DocCategoryInfo = namedtuple('DocCategoryInfo', ['header', 'description'],
5+
defaults=[None])
6+
7+
8+
def load_config(config_file):
9+
loader = select_loader(config_file)
10+
with open(config_file, mode='r') as f:
11+
dct = loader(f)
12+
13+
result = {category: DocCategoryInfo(**details)
14+
for category, details in dct.items()}
15+
return result
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import sys
2+
from paths_cli.compiling.core import Parameter
3+
from .json_type_handlers import json_type_to_string
4+
from .config_handler import DocCategoryInfo
5+
6+
PARAMETER_RST = """* **{p.name}**{type_str} - {p.description}{required}\n"""
7+
8+
9+
class DocsGenerator:
10+
"""This generates the RST to describe options for compile input files.
11+
"""
12+
13+
parameter_template = PARAMETER_RST
14+
_ANCHOR_SEP = "--"
15+
def __init__(self, config):
16+
self.config = config
17+
18+
def format_parameter(self, parameter, type_str=None):
19+
required = " (required)" if parameter.required else ""
20+
return self.parameter_template.format(
21+
p=parameter, type_str=type_str, required=required
22+
)
23+
24+
def _get_cat_info(self, category_plugin):
25+
cat_info = self.config.get(category_plugin.label, None)
26+
if cat_info is None:
27+
cat_info = DocCategoryInfo(category_plugin.label)
28+
return cat_info
29+
30+
def generate_category_rst(self, category_plugin, type_required=True):
31+
cat_info = self._get_cat_info(category_plugin)
32+
rst = f".. _compiling--{category_plugin.label}:\n\n"
33+
rst += f"{cat_info.header}\n{'=' * len(str(cat_info.header))}\n\n"
34+
if cat_info.description:
35+
rst += cat_info.description + "\n\n"
36+
rst += ".. contents:: :local:\n\n"
37+
for obj in category_plugin.type_dispatch.values():
38+
rst += self.generate_plugin_rst(
39+
obj, category_plugin.label, type_required
40+
)
41+
return rst
42+
43+
def generate_plugin_rst(self, plugin, strategy_name,
44+
type_required=True):
45+
rst_anchor = f".. _{strategy_name}{self._ANCHOR_SEP}{plugin.name}:"
46+
rst = f"{rst_anchor}\n\n{plugin.name}\n{'-' * len(plugin.name)}\n\n"
47+
if plugin.description:
48+
rst += plugin.description + "\n\n"
49+
if type_required:
50+
type_param = Parameter(
51+
"type",
52+
json_type="",
53+
loader=None,
54+
description=(f"type identifier; must exactly match the "
55+
f"string ``{plugin.name}``"),
56+
)
57+
rst += self.format_parameter(
58+
type_param, type_str=""
59+
)
60+
61+
name_param = Parameter(
62+
"name",
63+
json_type="string",
64+
loader=None,
65+
default="",
66+
description="name this object in order to reuse it",
67+
)
68+
rst += self.format_parameter(name_param, type_str=" (*string*)")
69+
for param in plugin.parameters:
70+
type_str = f" ({json_type_to_string(param.json_type)})"
71+
rst += self.format_parameter(param, type_str)
72+
73+
rst += "\n\n"
74+
return rst
75+
76+
def _get_filename(self, cat_info):
77+
fname = str(cat_info.header).lower()
78+
fname = fname.translate(str.maketrans(' ', '_'))
79+
return f"{fname}.rst"
80+
81+
def generate(self, category_plugins, stdout=False):
82+
for plugin in category_plugins:
83+
rst = self.generate_category_rst(plugin)
84+
if stdout:
85+
sys.stdout.write(rst)
86+
sys.stdout.flush()
87+
else:
88+
cat_info = self._get_cat_info(plugin)
89+
filename = self._get_filename(cat_info)
90+
with open(filename, 'w') as f:
91+
f.write(rst)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
class JsonTypeHandler:
2+
def __init__(self, is_my_type, handler):
3+
self._is_my_type = is_my_type
4+
self.handler = handler
5+
6+
def is_my_type(self, json_type):
7+
return self._is_my_type(json_type)
8+
9+
def __call__(self, json_type):
10+
if self.is_my_type(json_type):
11+
return self.handler(json_type)
12+
return json_type
13+
14+
15+
handle_object = JsonTypeHandler(
16+
is_my_type=lambda json_type: json_type == "object",
17+
handler=lambda json_type: "dict",
18+
)
19+
20+
21+
def _is_listof(json_type):
22+
try:
23+
return json_type["type"] == "array"
24+
except:
25+
return False
26+
27+
28+
handle_listof = JsonTypeHandler(
29+
is_my_type=_is_listof,
30+
handler=lambda json_type: "list of "
31+
+ json_type_to_string(json_type["items"]),
32+
)
33+
34+
35+
class RefTypeHandler(JsonTypeHandler):
36+
def __init__(self, type_name, def_string, link_to):
37+
self.type_name = type_name
38+
self.def_string = def_string
39+
self.link_to = link_to
40+
self.json_check = {"$ref": "#/definitions/" + def_string}
41+
super().__init__(is_my_type=self._reftype, handler=self._refhandler)
42+
43+
def _reftype(self, json_type):
44+
return json_type == self.json_check
45+
46+
def _refhandler(self, json_type):
47+
rst = f"{self.type_name}"
48+
if self.link_to:
49+
rst = f":ref:`{rst} <{self.link_to}>`"
50+
return rst
51+
52+
53+
class CategoryHandler(RefTypeHandler):
54+
def __init__(self, category):
55+
self.category = category
56+
def_string = f"{category}_type"
57+
link_to = f"compiling--{category}"
58+
super().__init__(
59+
type_name=category, def_string=def_string, link_to=link_to
60+
)
61+
62+
63+
class EvalHandler(RefTypeHandler):
64+
def __init__(self, type_name, link_to=None):
65+
super().__init__(
66+
type_name=type_name, def_string=type_name, link_to=link_to
67+
)
68+
69+
70+
JSON_TYPE_HANDLERS = [
71+
handle_object,
72+
handle_listof,
73+
CategoryHandler("engine"),
74+
CategoryHandler("cv"),
75+
CategoryHandler("volume"),
76+
EvalHandler("EvalInt"),
77+
EvalHandler("EvalFloat"),
78+
]
79+
80+
81+
def json_type_to_string(json_type):
82+
for handler in JSON_TYPE_HANDLERS:
83+
handled = handler(json_type)
84+
if handled != json_type:
85+
return handled
86+
return json_type

‎paths_cli/compiling/core.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,21 @@ class InstanceCompilerPlugin(OPSPlugin):
143143
Name used in the text input for this object.
144144
aliases : List[str]
145145
Other names that can be used.
146+
description : str
147+
String description used in the documentation
146148
requires_ops : Tuple[int, int]
147149
version of OpenPathSampling required for this functionality
148150
requires_cli : Tuple[int, int]
149151
version of the OPS CLI requires for this functionality
150152
"""
151153
SCHEMA = "http://openpathsampling.org/schemas/sim-setup/draft01.json"
152154
category = None
153-
def __init__(self, builder, parameters, name=None, aliases=None,
154-
requires_ops=(1, 0), requires_cli=(0, 3)):
155+
def __init__(self, builder, parameters, name=None, *, aliases=None,
156+
description=None, requires_ops=(1, 0),
157+
requires_cli=(0, 3)):
155158
super().__init__(requires_ops, requires_cli)
156159
self.aliases = aliases
160+
self.description = description
157161
self.attribute_table = {p.name: p.loader for p in parameters
158162
if p.required}
159163
self.optional_attributes = {p.name: p.loader for p in parameters

‎paths_cli/compiling/engines.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def _openmm_options(dct):
4343
remapper=_openmm_options),
4444
parameters=OPENMM_PARAMETERS,
4545
name='openmm',
46+
description="Use OpenMM for the dynamics.",
4647
)
4748

4849
ENGINE_COMPILER = CategoryPlugin(EngineCompilerPlugin, aliases=['engines'])

‎paths_cli/compiling/gendocs.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import click
2+
from paths_cli.compiling._gendocs import DocsGenerator, load_config
3+
from paths_cli.compiling.root_compiler import _COMPILERS
4+
from paths_cli.commands.compile import register_installed_plugins
5+
6+
@click.command()
7+
@click.argument("config_file")
8+
@click.option("--stdout", type=bool, is_flag=True, default=False)
9+
def main(config_file, stdout): # -no-cov-
10+
register_installed_plugins()
11+
config = load_config(config_file)
12+
generator = DocsGenerator(config)
13+
generator.generate(_COMPILERS.values(), stdout)
14+
15+
if __name__ == "__main__":
16+
main()

‎paths_cli/compiling/shooting.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ def _remapping_gaussian_stddev(dct):
3232
'uniform': build_uniform_selector,
3333
'gaussian': build_gaussian_selector,
3434
},
35-
label='shooting selectors'
35+
label='shooting-selectors'
3636
)

‎paths_cli/compiling/strategies.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
StrategyCompilerPlugin, CategoryPlugin
55
)
66
from paths_cli.compiling.root_compiler import compiler_for
7+
from paths_cli.compiling.json_type import json_type_ref
78

89
def _strategy_name(class_name):
910
return f"openpathsampling.strategies.{class_name}"
@@ -16,9 +17,10 @@ def _group_parameter(group_name):
1617
SP_SELECTOR_PARAMETER = Parameter('selector', shooting_selector_compiler,
1718
default=None)
1819

19-
ENGINE_PARAMETER = Parameter('engine', compiler_for('engine'),
20-
description="the engine for moves of this "
21-
"type")
20+
ENGINE_PARAMETER = Parameter(
21+
'engine', compiler_for('engine'), json_type=json_type_ref('engine'),
22+
description="the engine for moves of this type"
23+
)
2224

2325
SHOOTING_GROUP_PARAMETER = _group_parameter('shooting')
2426
REPEX_GROUP_PARAMETER = _group_parameter('repex')
@@ -57,7 +59,7 @@ def _group_parameter(group_name):
5759
REPEX_GROUP_PARAMETER,
5860
REPLACE_TRUE_PARAMETER
5961
],
60-
name='nearest-neighbor=repex',
62+
name='nearest-neighbor-repex',
6163
)
6264

6365
build_all_set_repex_strategy = StrategyCompilerPlugin(

0 commit comments

Comments
 (0)