summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2017-11-14 10:01:35 +0000
committerGerrit Code Review <review@openstack.org>2017-11-14 10:01:35 +0000
commitc471e7a92a9129727c99b6a4a135ecea47c2a3ca (patch)
tree80eeba0e6e42eada89b17f28690f04304fd63046
parentec7aedb9214e4fdf8649c381743dfc02e28869b0 (diff)
parent9a29859cf21f711f77fcd75d0bcc1ee29d58db96 (diff)
downloadcliff-c471e7a92a9129727c99b6a4a135ecea47c2a3ca.tar.gz
Merge "sphinxext: Support cliff application"
-rw-r--r--cliff/sphinxext.py123
-rw-r--r--doc/source/user/demoapp.rst12
-rw-r--r--doc/source/user/sphinxext.rst51
3 files changed, 145 insertions, 41 deletions
diff --git a/cliff/sphinxext.py b/cliff/sphinxext.py
index 62c7e65..d8468b6 100644
--- a/cliff/sphinxext.py
+++ b/cliff/sphinxext.py
@@ -14,13 +14,16 @@
import argparse
import fnmatch
+import importlib
import re
+import sys
from docutils import nodes
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils import statemachine
+from cliff import app
from cliff import commandmanager
@@ -208,10 +211,42 @@ class AutoprogramCliffDirective(rst.Directive):
required_arguments = 1
option_spec = {
'command': directives.unchanged,
+ 'arguments': directives.unchanged,
'ignored': directives.unchanged,
'application': directives.unchanged,
}
+ def _get_ignored_opts(self):
+ 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()]
+ return list(set(global_ignored + local_ignored))
+
+ def _drop_ignored_options(self, parser, ignored_opts):
+ 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
+
+ def _load_app(self):
+ mod_str, _sep, class_str = self.arguments[0].rpartition('.')
+ if not mod_str:
+ return
+ try:
+ importlib.import_module(mod_str)
+ except ImportError:
+ return
+ try:
+ cliff_app_class = getattr(sys.modules[mod_str], class_str)
+ except AttributeError:
+ return
+ if not issubclass(cliff_app_class, app.App):
+ return
+ app_arguments = self.options.get('arguments', '').split()
+ return cliff_app_class(*app_arguments)
+
def _load_command(self, manager, command_name):
"""Load a command using an instance of a `CommandManager`."""
try:
@@ -222,8 +257,42 @@ class AutoprogramCliffDirective(rst.Directive):
'namespace'.format(
command_name, manager.namespace))
- def _generate_nodes(self, title, command_name, command_class,
- ignored_opts):
+ def _load_commands(self):
+ # TODO(sfinucan): We should probably add this wildcarding functionality
+ # to the CommandManager itself to allow things like "show me the
+ # commands like 'foo *'"
+ command_pattern = self.options.get('command')
+ manager = commandmanager.CommandManager(self.arguments[0])
+ if command_pattern:
+ commands = [x for x in manager.commands
+ if fnmatch.fnmatch(x, command_pattern)]
+ else:
+ commands = manager.commands.keys()
+ return dict((name, self._load_command(manager, name))
+ for name in commands)
+
+ def _generate_app_node(self, app, application_name):
+ ignored_opts = self._get_ignored_opts()
+
+ parser = app.parser
+
+ self._drop_ignored_options(parser, ignored_opts)
+
+ parser.prog = application_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]
+ return section.children
+
+ def _generate_nodes_per_command(self, title, command_name, command_class,
+ ignored_opts):
"""Generate the relevant Sphinx nodes.
This doesn't bother using raw docutils nodes as they simply don't offer
@@ -244,12 +313,7 @@ class AutoprogramCliffDirective(rst.Directive):
parser = command.get_parser(command_name)
ignored_opts = ignored_opts or []
- # Drop any ignored actions
- 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
+ self._drop_ignored_options(parser, ignored_opts)
section = nodes.section(
'',
@@ -267,42 +331,33 @@ class AutoprogramCliffDirective(rst.Directive):
return [section]
- def run(self):
- self.env = self.state.document.settings.env
-
- command_pattern = self.options.get('command')
- 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))
-
- # TODO(sfinucan): We should probably add this wildcarding functionality
- # to the CommandManager itself to allow things like "show me the
- # commands like 'foo *'"
- manager = commandmanager.CommandManager(self.arguments[0])
- if command_pattern:
- commands = [x for x in manager.commands
- if fnmatch.fnmatch(x, command_pattern)]
- else:
- commands = manager.commands.keys()
-
+ def _generate_command_nodes(self, commands, application_name):
+ ignored_opts = self._get_ignored_opts()
output = []
for command_name in sorted(commands):
- command_class = self._load_command(manager, command_name)
-
+ command_class = commands[command_name]
title = command_name
if application_name:
command_name = ' '.join([application_name, command_name])
- output.extend(self._generate_nodes(
+ output.extend(self._generate_nodes_per_command(
title, command_name, command_class, ignored_opts))
return output
+ def run(self):
+ self.env = self.state.document.settings.env
+
+ application_name = (self.options.get('application')
+ or self.env.config.autoprogram_cliff_application)
+
+ app = self._load_app()
+ if app:
+ return self._generate_app_node(app, application_name)
+
+ commands = self._load_commands()
+ return self._generate_command_nodes(commands, application_name)
+
def setup(app):
app.add_directive('autoprogram-cliff', AutoprogramCliffDirective)
diff --git a/doc/source/user/demoapp.rst b/doc/source/user/demoapp.rst
index 25e376a..ce506ef 100644
--- a/doc/source/user/demoapp.rst
+++ b/doc/source/user/demoapp.rst
@@ -288,11 +288,23 @@ is provided by :doc:`the cliff Sphinx extension <sphinxext>`.
.. code-block:: rest
+ .. autoprogram-cliff:: cliffdemo.main.DemoApp
+ :application: cliffdemo
+
.. autoprogram-cliff:: cliff.demo
:application: cliffdemo
Output
------
+Global Options
+~~~~~~~~~~~~~~
+
+.. autoprogram-cliff:: cliffdemo.main.DemoApp
+ :application: cliffdemo
+
+Command Options
+~~~~~~~~~~~~~~~
+
.. autoprogram-cliff:: cliff.demo
:application: cliffdemo
diff --git a/doc/source/user/sphinxext.rst b/doc/source/user/sphinxext.rst
index 5c44426..ae1cabe 100644
--- a/doc/source/user/sphinxext.rst
+++ b/doc/source/user/sphinxext.rst
@@ -7,27 +7,55 @@ Usage
cliff supports integration with Sphinx by way of a `Sphinx directives`__.
+Preparation
+-----------
+
Before using the :rst:dir:`autoprogram-cliff` directive you must add
`'cliff.sphinxext'` extension module to a list of `extensions` in `conf.py`:
.. code-block:: python
- extensions = ['cliff.sphinxext']
+ extensions = ['cliff.sphinxext']
+
+Directive
+---------
-.. rst:directive:: .. autoprogram-cliff:: namespace
+.. rst:directive:: .. autoprogram-cliff:: <namespace> or <app class>
- Automatically document an instance of :py:class:`cliff.command.Command`,
+ Automatically document an instance of :py:class:`cliff.command.Command`
+ or :py:class:`cliff.app.App`
including a description, usage summary, and overview of all options.
+ .. important::
+
+ There are two modes in this directive: **command** mode and **app**
+ mode. The directive takes one required argument and the mode is
+ determined based on the argument specified.
+
+ The **command** mode documents various information of a specified instance of
+ :py:class:`cliff.command.Command`. The **command** mode takes the namespace
+ that the command(s) can be found in as the argument. This is generally
+ defined in the `entry_points` section of either `setup.cfg` or
+ `setup.py`. You can specify which command(s) should be displayed using
+ `:command:` option.
+
.. code-block:: rst
.. autoprogram-cliff:: openstack.compute.v2
:command: server add fixed ip
- One argument is required, corresponding to the namespace that the command(s)
- can be found in. This is generally defined in the `entry_points` section of
- either `setup.cfg` or `setup.py`. Refer to the example_ below for more
- information.
+ The **app** mode documents various information of a specified instance of
+ :py:class:`cliff.app.App`. The **app** mode takes the python path of the
+ corresponding class as the argument. In the **app** mode, `:application:`
+ option is usually specified so that the command name is shown in the
+ rendered output.
+
+ .. code-block:: rst
+
+ .. autoprogram-cliff:: cliffdemo.main.DemoApp
+ :application: cliffdemo
+
+ Refer to the example_ below for more information.
In addition, the following directive options can be supplied:
@@ -38,6 +66,15 @@ Before using the :rst:dir:`autoprogram-cliff` directive you must add
wildcarding is supported. Refer to the example_ below for more
information.
+ This option is effective only in the **command** mode.
+
+ `:arguments`
+ The arguments to be passed when the cliff application is instantiated.
+ Some cliff applications requires arguments when instantiated.
+ This option can be used to specify such arguments.
+
+ This option is effective only in the **app** mode.
+
`:application:`
The top-level application name, which will be prefixed before all
commands. This option overrides the global option