-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #64 from dwhswenson/compile-docs
Automatic build of docs for `compile` command
- Loading branch information
Showing
17 changed files
with
640 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# _gendocs | ||
|
||
Tools for generating documentation for the tools import `paths_cli.compiling`. | ||
Note that this entire directory is considered outside the API, so nothing in | ||
here should be strongly relied on. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .config_handler import load_config, DocCategoryInfo | ||
from .docs_generator import DocsGenerator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from collections import namedtuple | ||
from paths_cli.commands.compile import select_loader | ||
|
||
DocCategoryInfo = namedtuple('DocCategoryInfo', ['header', 'description', | ||
'type_required'], | ||
defaults=[None, True]) | ||
|
||
|
||
def load_config(config_file): | ||
"""Load a configuration file for gendocs. | ||
The configuration file should be YAML or JSON, and should map each | ||
category name to the headings necessary to fill a DocCategoryInfo | ||
instance. | ||
Parameters | ||
---------- | ||
config_file : str | ||
name of YAML or JSON file | ||
""" | ||
loader = select_loader(config_file) | ||
with open(config_file, mode='r', encoding='utf-8') as f: | ||
dct = loader(f) | ||
|
||
result = {category: DocCategoryInfo(**details) | ||
for category, details in dct.items()} | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import sys | ||
from paths_cli.compiling.core import Parameter | ||
from .json_type_handlers import json_type_to_string | ||
from .config_handler import DocCategoryInfo | ||
|
||
PARAMETER_RST = """* **{p.name}**{type_str} - {p.description}{required}\n""" | ||
|
||
|
||
class DocsGenerator: | ||
"""This generates the RST to describe options for compile input files. | ||
Parameters | ||
---------- | ||
config : Dict[str, DocCategoryInfo] | ||
mapping of category name to DocCategoryInfo for that category; | ||
usually generated by :method:`.load_config` | ||
""" | ||
|
||
parameter_template = PARAMETER_RST | ||
_ANCHOR_SEP = "--" | ||
|
||
def __init__(self, config): | ||
self.config = config | ||
|
||
def format_parameter(self, parameter, type_str=None): | ||
"""Format a single :class:`.paths_cli.compiling.Parameter` in RST | ||
""" | ||
required = " (required)" if parameter.required else "" | ||
return self.parameter_template.format( | ||
p=parameter, type_str=type_str, required=required | ||
) | ||
|
||
def _get_cat_info(self, category_plugin): | ||
cat_info = self.config.get(category_plugin.label, None) | ||
if cat_info is None: | ||
cat_info = DocCategoryInfo(category_plugin.label) | ||
return cat_info | ||
|
||
def generate_category_rst(self, category_plugin): | ||
"""Generate the RST for a given category plugin. | ||
Parameters | ||
---------- | ||
category_plugin : :class:`.CategoryPlugin` | ||
the plugin for which we should generate the RST page | ||
Returns | ||
------- | ||
str : | ||
RST string for this category | ||
""" | ||
cat_info = self._get_cat_info(category_plugin) | ||
type_required = cat_info.type_required | ||
rst = f".. _compiling--{category_plugin.label}:\n\n" | ||
rst += f"{cat_info.header}\n{'=' * len(str(cat_info.header))}\n\n" | ||
if cat_info.description: | ||
rst += cat_info.description + "\n\n" | ||
rst += ".. contents:: :local:\n\n" | ||
for obj in category_plugin.type_dispatch.values(): | ||
rst += self.generate_plugin_rst( | ||
obj, category_plugin.label, type_required | ||
) | ||
return rst | ||
|
||
def generate_plugin_rst(self, plugin, category_name, | ||
type_required=True): | ||
"""Generate the RST for a given object plugin. | ||
Parameters | ||
---------- | ||
plugin : class:`.InstanceCompilerPlugin` | ||
the object plugin for to generate the RST for | ||
category_name : str | ||
the name of the category for this object | ||
type_required : bool | ||
whether the ``type`` parameter is required in the dict input for | ||
compiling this type of object (usually category-dependent) | ||
Returns | ||
------- | ||
str : | ||
RST string for this object plugin | ||
""" | ||
rst_anchor = f".. _{category_name}{self._ANCHOR_SEP}{plugin.name}:" | ||
rst = f"{rst_anchor}\n\n{plugin.name}\n{'-' * len(plugin.name)}\n\n" | ||
if plugin.description: | ||
rst += plugin.description + "\n\n" | ||
if type_required: | ||
type_param = Parameter( | ||
"type", | ||
json_type="", | ||
loader=None, | ||
description=(f"type identifier; must exactly match the " | ||
f"string ``{plugin.name}``"), | ||
) | ||
rst += self.format_parameter( | ||
type_param, type_str="" | ||
) | ||
|
||
name_param = Parameter( | ||
"name", | ||
json_type="string", | ||
loader=None, | ||
default="", | ||
description="name this object in order to reuse it", | ||
) | ||
rst += self.format_parameter(name_param, type_str=" (*string*)") | ||
for param in plugin.parameters: | ||
type_str = f" ({json_type_to_string(param.json_type)})" | ||
rst += self.format_parameter(param, type_str) | ||
|
||
rst += "\n\n" | ||
return rst | ||
|
||
@staticmethod | ||
def _get_filename(cat_info): | ||
fname = str(cat_info.header).lower() | ||
fname = fname.translate(str.maketrans(' ', '_')) | ||
return f"{fname}.rst" | ||
|
||
def generate(self, category_plugins, stdout=False): | ||
"""Generate RST output for the given plugins. | ||
This is the main method used to generate the entire set of | ||
documentation. | ||
Parameters | ||
---------- | ||
category_plugin : List[:class:`.CategoryPlugin`] | ||
list of category plugins document | ||
stdout : bool | ||
if False (default) a separate output file is generated for each | ||
category plugin. If True, all text is output to stdout | ||
(particularly useful for debugging/dry runs). | ||
""" | ||
for plugin in category_plugins: | ||
rst = self.generate_category_rst(plugin) | ||
if stdout: | ||
sys.stdout.write(rst) | ||
sys.stdout.flush() | ||
else: | ||
cat_info = self._get_cat_info(plugin) | ||
filename = self._get_filename(cat_info) | ||
with open(filename, 'w') as f: | ||
f.write(rst) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
class JsonTypeHandler: | ||
"""Abstract class to obtain documentation type from JSON schema type. | ||
Parameters | ||
---------- | ||
is_my_type : Callable[Any] -> bool | ||
return True if this instance should handle the given input type | ||
handler : Callable[Any] -> str | ||
convert the input type to a string suitable for the RST docs | ||
""" | ||
def __init__(self, is_my_type, handler): | ||
self._is_my_type = is_my_type | ||
self.handler = handler | ||
|
||
def is_my_type(self, json_type): | ||
"""Determine whether this instance should handle this type. | ||
Parameters | ||
---------- | ||
json_type : Any | ||
input type from JSON schema | ||
Returns | ||
------- | ||
bool : | ||
whether to handle this type with this instance | ||
""" | ||
return self._is_my_type(json_type) | ||
|
||
def __call__(self, json_type): | ||
if self.is_my_type(json_type): | ||
return self.handler(json_type) | ||
return json_type | ||
|
||
|
||
handle_object = JsonTypeHandler( | ||
is_my_type=lambda json_type: json_type == "object", | ||
handler=lambda json_type: "dict", | ||
) | ||
|
||
|
||
def _is_listof(json_type): | ||
try: | ||
return json_type["type"] == "array" | ||
except: # any exception should return false (mostly Key/Type Error) | ||
return False | ||
|
||
|
||
handle_listof = JsonTypeHandler( | ||
is_my_type=_is_listof, | ||
handler=lambda json_type: "list of " | ||
+ json_type_to_string(json_type["items"]), | ||
) | ||
|
||
|
||
class RefTypeHandler(JsonTypeHandler): | ||
"""Handle JSON types of the form {"$ref": "#/definitions/..."} | ||
Parameters | ||
---------- | ||
type_name : str | ||
the name to use in the RST type | ||
def_string : str | ||
the string following "#/definitions/" in the JSON type definition | ||
link_to : str or None | ||
if not None, the RST type will be linked with a ``:ref:`` pointing | ||
to the anchor given by ``link_to`` | ||
""" | ||
def __init__(self, type_name, def_string, link_to): | ||
self.type_name = type_name | ||
self.def_string = def_string | ||
self.link_to = link_to | ||
self.json_check = {"$ref": "#/definitions/" + def_string} | ||
super().__init__(is_my_type=self._reftype, handler=self._refhandler) | ||
|
||
def _reftype(self, json_type): | ||
return json_type == self.json_check | ||
|
||
def _refhandler(self, json_type): | ||
rst = f"{self.type_name}" | ||
if self.link_to: | ||
rst = f":ref:`{rst} <{self.link_to}>`" | ||
return rst | ||
|
||
|
||
class CategoryHandler(RefTypeHandler): | ||
"""Handle JSON types for OPS category definitions. | ||
OPS category definitions show up with JSON references pointing to | ||
"#/definitions/{CATEGORY}_type". This provides a convenience class over | ||
the :class:RefTypeHandler to treat OPS categories. | ||
Parameters | ||
---------- | ||
category : str | ||
name of the category | ||
""" | ||
def __init__(self, category): | ||
self.category = category | ||
def_string = f"{category}_type" | ||
link_to = f"compiling--{category}" | ||
super().__init__( | ||
type_name=category, def_string=def_string, link_to=link_to | ||
) | ||
|
||
|
||
class EvalHandler(RefTypeHandler): | ||
"""Handle JSON types for OPS custom evaluation definitions. | ||
Some parameters for the OPS compiler use the OPS custom evaluation | ||
mechanism, which evaluates certain Python-like string input. These are | ||
treated as special definition types in the JSON schema, and this object | ||
provides a convenience class over :class:`.RefTypeHandler` to treat | ||
custom evaluation types. | ||
Parameters | ||
---------- | ||
type_name : str | ||
name of the custom evaluation type | ||
link_to : str or None | ||
if not None, the RST type will be linked with a ``:ref:`` pointing | ||
to the anchor given by ``link_to`` | ||
""" | ||
def __init__(self, type_name, link_to=None): | ||
super().__init__( | ||
type_name=type_name, def_string=type_name, link_to=link_to | ||
) | ||
|
||
|
||
JSON_TYPE_HANDLERS = [ | ||
handle_object, | ||
handle_listof, | ||
CategoryHandler("engine"), | ||
CategoryHandler("cv"), | ||
CategoryHandler("volume"), | ||
EvalHandler("EvalInt"), | ||
EvalHandler("EvalFloat"), | ||
] | ||
|
||
|
||
def json_type_to_string(json_type): | ||
"""Convert JSON schema type to string for RST docs. | ||
This is the primary public-facing method for dealing with JSON schema | ||
types in RST document generation. | ||
Parameters | ||
---------- | ||
json_type : Any | ||
the type from the JSON schema | ||
Returns | ||
------- | ||
str : | ||
the type string description to be used in the RST document | ||
""" | ||
for handler in JSON_TYPE_HANDLERS: | ||
handled = handler(json_type) | ||
if handled != json_type: | ||
return handled | ||
return json_type |
Oops, something went wrong.