sphinxext: Support cliff application
This commit adds a feature to generate a reference of global optoions of a cliff application to autoprogram-cliff directive. If a class path of a cliff application is specified as the argument of autoprogram-cliff directive, it will be interpreted as a cliff application and global options of the specified application are rendered. Change-Id: I20e46521a137ca721fae28f10c5cf75d26069e45
This commit is contained in:
parent
dd60a2a253
commit
9a29859cf2
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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']
|
||||
|
||||
.. rst:directive:: .. autoprogram-cliff:: namespace
|
||||
Directive
|
||||
---------
|
||||
|
||||
Automatically document an instance of :py:class:`cliff.command.Command`,
|
||||
.. rst:directive:: .. autoprogram-cliff:: <namespace> or <app class>
|
||||
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user