From f2a4d5253ccaeababd4163960b8548f794ade708 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 3 Jul 2017 03:30:23 +0000 Subject: [PATCH] doc: autogenerate neutron CLI reference This replaces the doc-migration work. We can avoid the maintenance cost by generating CLI reference for neutron CLI like this. This patch introduces a new sphinx directive to render CLI global options to achieve the goal. This can be migrated to cliff sphinxext later. Change-Id: I7089df3f7fc6475ebdd1e71221492baf12f18226 --- doc/source/cli/index.rst | 1 + doc/source/cli/neutron-reference.rst | 48 +++++++++++++++++ neutronclient/cliff_sphinxext.py | 78 ++++++++++++++++++++++++++++ neutronclient/shell.py | 4 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 doc/source/cli/neutron-reference.rst diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index 8ba5923b7..f53a83acb 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -60,3 +60,4 @@ neutron CLI :maxdepth: 2 neutron CLI guide + neutron CLI reference diff --git a/doc/source/cli/neutron-reference.rst b/doc/source/cli/neutron-reference.rst new file mode 100644 index 000000000..24359a5eb --- /dev/null +++ b/doc/source/cli/neutron-reference.rst @@ -0,0 +1,48 @@ +.. + 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. + + + Convention for heading levels in Neutron devref: + ======= Heading 0 (reserved for the title in a document) + ------- Heading 1 + ~~~~~~~ Heading 2 + +++++++ Heading 3 + ''''''' Heading 4 + (Avoid deeper levels because they do not render well.) + +===================== +neutron CLI reference +===================== + +.. warning:: + + neutron CLI is now deprecated and will be removed in the future. + Use openstack CLI instead. See `openstack CLI command list + `__ + and :doc:`its extensions for advanced networking services `. + The command mapping from neutron CLI to openstack CLI is available + `here `__. + +neutron usage +------------- + +.. cliff-app:: neutronclient.shell.NeutronShell + :application: neutron + :arguments: 2.0 + +neutron API v2.0 commands +------------------------- + +.. autoprogram-cliff:: neutron.cli.v2 + :application: neutron + diff --git a/neutronclient/cliff_sphinxext.py b/neutronclient/cliff_sphinxext.py index 16915fac0..ca4756c38 100644 --- a/neutronclient/cliff_sphinxext.py +++ b/neutronclient/cliff_sphinxext.py @@ -20,6 +20,7 @@ from docutils import nodes from docutils.parsers import rst from docutils.parsers.rst import directives from docutils import statemachine +from oslo_utils import importutils from cliff import commandmanager @@ -301,7 +302,84 @@ class AutoprogramCliffDirective(rst.Directive): return output +class CliffAppDirective(rst.Directive): + """Auto-document a `cliff.app.App`.""" + + has_content = False + required_arguments = 1 + option_spec = { + 'arguments': directives.unchanged, + 'ignored': directives.unchanged, + 'application': directives.unchanged, + } + + def _generate_nodes(self, title, app, app_name, ignored_opts): + """Generate the relevant Sphinx nodes. + + This is a little funky. Parts of this use raw docutils nodes while + other parts use reStructuredText and nested parsing. The reason for + this is simple: it avoids us having to reinvent the wheel. While raw + docutils nodes are helpful for the simpler elements of the output, + they don't provide an easy way to use Sphinx's own directives, such as + the 'option' directive. Refer to [1] for more information. + + [1] http://www.sphinx-doc.org/en/stable/extdev/markupapi.html + + :param title: Title of command + :param app: Subclass of :py:class`cliff.app.App` + :param app_name: The name of the cliff application. + This is used as the command name. + :param ignored_opts: A list of options to exclude from output, if any + :returns: A list of docutil nodes + """ + parser = app.parser + ignored_opts = ignored_opts or [] + + # Drop the automatically-added help action + for action in list(parser._actions): + for option_string in action.option_strings: + if option_string in ignored_opts: + del parser._actions[parser._actions.index(action)] + break + + parser.prog = app_name + + source_name = '<{}>'.format(app.__class__.__name__) + result = statemachine.ViewList() + for line in _format_parser(parser): + result.append(line, source_name) + + section = nodes.section() + self.state.nested_parse(result, 0, section) + return section.children + + def run(self): + self.env = self.state.document.settings.env + + cliff_app_class = importutils.import_class(self.arguments[0]) + app_arguments = self.options.get('arguments', '').split() + cliff_app = cliff_app_class(*app_arguments) + + application_name = (self.options.get('application') + or self.env.config.autoprogram_cliff_application) + + global_ignored = self.env.config.autoprogram_cliff_ignored + local_ignored = self.options.get('ignored', '') + local_ignored = [x.strip() for x in local_ignored.split(',') + if x.strip()] + ignored_opts = list(set(global_ignored + local_ignored)) + + output = [] + title = application_name + output.extend(self._generate_nodes( + title, cliff_app, application_name, ignored_opts)) + + return output + + def setup(app): app.add_directive('autoprogram-cliff', AutoprogramCliffDirective) app.add_config_value('autoprogram_cliff_application', '', True) app.add_config_value('autoprogram_cliff_ignored', ['--help'], True) + + app.add_directive('cliff-app', CliffAppDirective) diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 017d642c1..51b06d3d1 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -149,8 +149,10 @@ class NeutronShell(app.App): def __init__(self, apiversion): namespace = NAMESPACE_MAP[apiversion] + description = (__doc__.strip() + + " (neutron CLI version: %s)" % __version__) super(NeutronShell, self).__init__( - description=__doc__.strip(), + description=description, version=VERSION, command_manager=commandmanager.CommandManager(namespace), )