From 44486ef65e39328426ad3b6e5e4af7866bb2fb30 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Fri, 20 Jan 2017 14:52:26 +0200 Subject: [PATCH] [docs] Several improvements for extensions * plugin_reference is extended to build data for particular Plugin Base cls * plugin_reference is extended to build data for all Plugin Bases (instead of just building data for hardcoded classes). * cli_reference is extended to build data for particular command * new include_vars extension is introduced for including const data in docs Change-Id: I76d9f85e41c6ab8e622f6d3581478962144525ba --- doc/ext/cli_reference.py | 5 + doc/ext/include_vars.py | 58 +++++++ doc/ext/plugin_reference.py | 209 +++++++++++++----------- doc/ext/utils.py | 1 + doc/source/conf.py | 3 +- doc/source/plugins/index.rst | 13 +- doc/source/plugins/plugin_reference.rst | 9 +- 7 files changed, 189 insertions(+), 109 deletions(-) create mode 100644 doc/ext/include_vars.py diff --git a/doc/ext/cli_reference.py b/doc/ext/cli_reference.py index c63c0677..8735baab 100644 --- a/doc/ext/cli_reference.py +++ b/doc/ext/cli_reference.py @@ -188,11 +188,16 @@ def make_category_section(name, parser): class CLIReferenceDirective(rst.Directive): + optional_arguments = 1 + option_spec = {"group": str} def run(self): parser = Parser() categories = copy.copy(main.categories) categories["db"] = manage.DBCommands + if "group" in self.options: + categories = {k: v for k,v in categories.items() + if k == self.options["group"]} cliutils._add_command_parsers(categories, parser) content = [] diff --git a/doc/ext/include_vars.py b/doc/ext/include_vars.py new file mode 100644 index 00000000..2655dca3 --- /dev/null +++ b/doc/ext/include_vars.py @@ -0,0 +1,58 @@ +# Copyright 2017: Mirantis Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from docutils import nodes +import json + +from oslo_utils import importutils + + +def include_var(name, rawtext, text, lineno, inliner, options=None, + content=None): + """ + + + :param name: The local name of the interpreted role, the role name + actually used in the document. + :param rawtext: A string containing the enitre interpreted text input, + including the role and markup. Return it as a problematic node + linked to a system message if a problem is encountered. + :param text: The interpreted text content. + :param lineno: The line number where the interpreted text begins. + :param inliner: The docutils.parsers.rst.states.Inliner object that + called include_var. It contains the several attributes useful for + error reporting and document tree access. + :param options: A dictionary of directive options for customization + (from the "role" directive), to be interpreted by the role function. + Used for additional attributes for the generated elements and other + functionality. + :param content: A list of strings, the directive content for + customization (from the "role" directive). To be interpreted by the + role function. + + :return: + """ + obj = importutils.import_class(text) + if isinstance(obj, (tuple, list)): + obj = ", ".join(obj) + elif isinstance(obj, dict): + obj = json.dumps(dict, indent=4) + else: + obj = str(obj) + return [nodes.Text(obj)], [] + + +def setup(app): + app.add_role("include-var", include_var) diff --git a/doc/ext/plugin_reference.py b/doc/ext/plugin_reference.py index b7fa7f67..6575da8c 100644 --- a/doc/ext/plugin_reference.py +++ b/doc/ext/plugin_reference.py @@ -14,124 +14,137 @@ # under the License. from docutils.parsers import rst +import re -from oslo_utils import importutils - +from rally.common.plugin import discover +from rally.common.plugin import plugin from rally import plugins -from utils import category, subcategory, paragraph, parse_text - -DATA = [ - { - "group": "task", - "items": [ - { - "name": "scenario runner", - "base": "rally.task.runner:ScenarioRunner" - }, - { - "name": "SLA", - "base": "rally.task.sla:SLA" - }, - { - "name": "context", - "base": "rally.task.context:Context" - }, - { - "name": "scenario", - "base": "rally.task.scenario:Scenario" - } - ] - }, - { - "group": "processing", - "items": [ - { - "name": "output chart", - "base": "rally.task.processing.charts:OutputChart" - } - ] - }, - { - "group": "deployment", - "items": [ - { - "name": "engine", - "base": "rally.deployment.engine:Engine" - }, - { - "name": "server provider", - "base": - "rally.deployment.serverprovider.provider:ProviderFactory" - } - ] - } -] +from utils import category, subcategory, section, paragraph, parse_text -def _make_pretty_parameters(parameters): - if not parameters: - return "" - - result = "**Parameters**:\n\n" - for p in parameters: - result += "* %(name)s: %(doc)s\n" % p - return result +CATEGORIES = { + "Common": ["OS Client"], + "Deployment": ["Engine", "Provider Factory"], + "Task Component": ["Chart", "Context", "Exporter", "Hook", + "Resource Type", "SLA", "Scenario", "Scenario Runner", + "Trigger"], + "Verification Component": ["Verifier Context", "Verification Reporter", + "Verifier Manager"] +} +# NOTE(andreykurilin): several bases do not have docstings at all, so it is +# redundant to display them +IGNORED_BASES = ["Resource Type", "Task Exporter", "OS Client"] -def make_plugin_section(plugin, base_name): - subcategory_obj = subcategory("%s [%s]" % (plugin.get_name(), base_name)) - info = plugin.get_info() - if info["title"]: - subcategory_obj.append(paragraph(info["title"])) +class PluginsReferenceDirective(rst.Directive): + optional_arguments = 1 + option_spec = {"base_cls": str} - if info["description"]: - subcategory_obj.extend(parse_text(info["description"])) + @staticmethod + def _make_pretty_parameters(parameters): + if not parameters: + return "" - if info["namespace"]: - subcategory_obj.append(paragraph( + result = "**Parameters**:\n\n" + for p in parameters: + result += "* %(name)s: %(doc)s\n" % p + return result + + def _make_plugin_section(self, plugin_cls, base_name=None): + section_name = plugin_cls.get_name() + if base_name: + section_name += " [%s]" % base_name + section_obj = section(section_name) + + info = plugin_cls.get_info() + if info["title"]: + section_obj.append(paragraph(info["title"])) + + if info["description"]: + section_obj.extend(parse_text(info["description"])) + + if info["namespace"]: + section_obj.append(paragraph( "**Namespace**: %s" % info["namespace"])) - if info["parameters"]: - subcategory_obj.extend(parse_text( - _make_pretty_parameters(info["parameters"]))) - if info["returns"]: - subcategory_obj.extend(parse_text( + if info["parameters"]: + section_obj.extend(parse_text( + self._make_pretty_parameters(info["parameters"]))) + if info["returns"]: + section_obj.extend(parse_text( "**Returns**:\n%s" % info["returns"])) - filename = info["module"].replace(".", "/") - ref = "https://github.com/openstack/rally/blob/master/%s.py" % filename - subcategory_obj.extend(parse_text("**Module**:\n`%s`__\n\n__ %s" - % (info["module"], ref))) - return subcategory_obj + filename = info["module"].replace(".", "/") + ref = "https://github.com/openstack/rally/blob/master/%s.py" % filename + section_obj.extend(parse_text("**Module**:\n`%s`__\n\n__ %s" + % (info["module"], ref))) + return section_obj + def _make_plugin_base_section(self, base_cls, base_name=None): + if base_name: + title = ("%ss" % base_name if base_name[-1] != "y" + else "%sies" % base_name[:-1]) + subcategory_obj = subcategory(title) + else: + subcategory_obj = [] + for p in sorted(base_cls.get_all(), key=lambda o: o.get_name()): + subcategory_obj.append(self._make_plugin_section(p, base_name)) -def make_plugin_base_section(plugin_group): - elements = [] + return subcategory_obj - for item in plugin_group["items"]: - name = item["name"].title() if "SLA" != item["name"] else item["name"] - category_obj = category("%s %ss" % (plugin_group["group"].title(), - name)) - elements.append(category_obj) + @staticmethod + def _parse_class_name(cls): + name = "" + for word in re.split(r'([A-Z][a-z]*)', cls.__name__): + if word: + if len(word) > 1 and name: + name += " " + name += word + return name - module, cls = item["base"].split(":") - plugin_base = getattr(importutils.import_module(module), cls) + def _get_all_plugins_bases(self): + """Return grouped and sorted all plugins bases.""" + bases = [] + bases_names = [] + for p in discover.itersubclasses(plugin.Plugin): + base_ref = getattr(p, "base_ref", None) + if base_ref == p: + name = self._parse_class_name(p) + if name in bases_names: + raise Exception("Two base classes with same name '%s' are " + "detected." % name) + bases_names.append(name) + category_of_base = "Common" + for cname, cbases in CATEGORIES.items(): + if name in cbases: + category_of_base = cname - for p in plugin_base.get_all(): - category_obj.append(make_plugin_section(p, item["name"])) - - return elements - - -class PluginReferenceDirective(rst.Directive): + bases.append((category_of_base, name, p)) + return sorted(bases) def run(self): - content = [] - for i in range(len(DATA)): - content.extend(make_plugin_base_section(DATA[i])) + plugins.load() + bases = self._get_all_plugins_bases() + if "base_cls" in self.options: + for _category_name, base_name, base_cls in bases: + if base_name == self.options["base_cls"]: + return self._make_plugin_base_section(base_cls) + raise Exception("Failed to generate plugins reference for '%s'" + " plugin base." % self.options["base_cls"]) - return content + categories = {} + + for category_name, base_name, base_cls in bases: + # FIXME(andreykurilin): do not ignore anything + if base_name in IGNORED_BASES: + continue + if category_name not in categories: + categories[category_name] = category(category_name) + category_of_base = categories[category_name] + category_of_base.append(self._make_plugin_base_section(base_cls, + base_name)) + return [content for _name, content in sorted(categories.items())] def setup(app): plugins.load() - app.add_directive("generate_plugin_reference", PluginReferenceDirective) + app.add_directive("generate_plugin_reference", PluginsReferenceDirective) diff --git a/doc/ext/utils.py b/doc/ext/utils.py index dc0397b5..4abb2627 100644 --- a/doc/ext/utils.py +++ b/doc/ext/utils.py @@ -37,6 +37,7 @@ hint = lambda msg: nodes.hint("", *parse_text(msg)) warning = lambda msg: nodes.warning("", paragraph(msg)) category = lambda title: parse_text("%s\n%s" % (title, "-" * len(title)))[0] subcategory = lambda title: parse_text("%s\n%s" % (title, "~" * len(title)))[0] +section = lambda title: parse_text("%s\n%s" % (title, "\"" * len(title)))[0] def make_definition(term, ref, descriptions): diff --git a/doc/source/conf.py b/doc/source/conf.py index e6aeb49c..414e6363 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -60,7 +60,8 @@ extensions = [ "sphinx.ext.ifconfig", "sphinx.ext.viewcode", "ext.cli_reference", - "ext.plugin_reference" + "ext.plugin_reference", + "ext.include_vars" ] todo_include_todos = True diff --git a/doc/source/plugins/index.rst b/doc/source/plugins/index.rst index fe64f45e..dc41d765 100644 --- a/doc/source/plugins/index.rst +++ b/doc/source/plugins/index.rst @@ -18,16 +18,17 @@ Rally Plugins ============= - -Rally Plugin Reference ----------------------- - Rally has a plugin oriented architecture - in other words Rally team is trying to make all places of code pluggable. Such architecture leads to the big amount -of plugins. :ref:`Rally Plugins Reference page ` contains -a full list with detailed descriptions of all official Rally plugins. +of plugins. :ref:`plugin-reference` contains a full list of all official Rally +plugins with detailed descriptions. +.. toctree:: + :maxdepth: 1 + + plugin_reference + How plugins work ---------------- diff --git a/doc/source/plugins/plugin_reference.rst b/doc/source/plugins/plugin_reference.rst index 7b8df1b8..5b52ade1 100644 --- a/doc/source/plugins/plugin_reference.rst +++ b/doc/source/plugins/plugin_reference.rst @@ -1,12 +1,13 @@ :tocdepth: 1 -.. _plugin_reference: + +.. _plugin-reference: -Rally Plugins Reference -======================= +Plugins Reference +================= .. contents:: - :depth: 1 + :depth: 2 :local: .. generate_plugin_reference::