-
Notifications
You must be signed in to change notification settings - Fork 3
Automatic build of docs for compile command
#64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
90ed290
separate compile's register_installed_plugins
dwhswenson 9d697ba
Merge branch 'main' of github.com:openpathsampling/openpathsampling-c…
dwhswenson dd67a69
roughly working automatic compile docs generation
dwhswenson 936e0b3
add json_type
dwhswenson b0cea21
Add tests for config_handler, json_type_handlers
dwhswenson 83de5f9
tests for docs_generator
dwhswenson 2546f69
Merge branch 'main' of github.com:openpathsampling/openpathsampling-c…
dwhswenson e19b5a0
test for the gendocs script
dwhswenson d3567cc
docstrings
dwhswenson def35eb
a little cleanup; should be ready for review
dwhswenson d1f430a
Apply suggestions from code review
dwhswenson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.