Merge "sphinxext: Support cliff application"
This commit is contained in:
@@ -14,13 +14,16 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
import importlib
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers import rst
|
from docutils.parsers import rst
|
||||||
from docutils.parsers.rst import directives
|
from docutils.parsers.rst import directives
|
||||||
from docutils import statemachine
|
from docutils import statemachine
|
||||||
|
|
||||||
|
from cliff import app
|
||||||
from cliff import commandmanager
|
from cliff import commandmanager
|
||||||
|
|
||||||
|
|
||||||
@@ -208,10 +211,42 @@ class AutoprogramCliffDirective(rst.Directive):
|
|||||||
required_arguments = 1
|
required_arguments = 1
|
||||||
option_spec = {
|
option_spec = {
|
||||||
'command': directives.unchanged,
|
'command': directives.unchanged,
|
||||||
|
'arguments': directives.unchanged,
|
||||||
'ignored': directives.unchanged,
|
'ignored': directives.unchanged,
|
||||||
'application': 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):
|
def _load_command(self, manager, command_name):
|
||||||
"""Load a command using an instance of a `CommandManager`."""
|
"""Load a command using an instance of a `CommandManager`."""
|
||||||
try:
|
try:
|
||||||
@@ -222,8 +257,42 @@ class AutoprogramCliffDirective(rst.Directive):
|
|||||||
'namespace'.format(
|
'namespace'.format(
|
||||||
command_name, manager.namespace))
|
command_name, manager.namespace))
|
||||||
|
|
||||||
def _generate_nodes(self, title, command_name, command_class,
|
def _load_commands(self):
|
||||||
ignored_opts):
|
# 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.
|
"""Generate the relevant Sphinx nodes.
|
||||||
|
|
||||||
This doesn't bother using raw docutils nodes as they simply don't offer
|
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)
|
parser = command.get_parser(command_name)
|
||||||
ignored_opts = ignored_opts or []
|
ignored_opts = ignored_opts or []
|
||||||
|
|
||||||
# Drop any ignored actions
|
self._drop_ignored_options(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
|
|
||||||
|
|
||||||
section = nodes.section(
|
section = nodes.section(
|
||||||
'',
|
'',
|
||||||
@@ -267,42 +331,33 @@ class AutoprogramCliffDirective(rst.Directive):
|
|||||||
|
|
||||||
return [section]
|
return [section]
|
||||||
|
|
||||||
def run(self):
|
def _generate_command_nodes(self, commands, application_name):
|
||||||
self.env = self.state.document.settings.env
|
ignored_opts = self._get_ignored_opts()
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
for command_name in sorted(commands):
|
for command_name in sorted(commands):
|
||||||
command_class = self._load_command(manager, command_name)
|
command_class = commands[command_name]
|
||||||
|
|
||||||
title = command_name
|
title = command_name
|
||||||
if application_name:
|
if application_name:
|
||||||
command_name = ' '.join([application_name, command_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))
|
title, command_name, command_class, ignored_opts))
|
||||||
|
|
||||||
return output
|
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):
|
def setup(app):
|
||||||
app.add_directive('autoprogram-cliff', AutoprogramCliffDirective)
|
app.add_directive('autoprogram-cliff', AutoprogramCliffDirective)
|
||||||
|
@@ -288,11 +288,23 @@ is provided by :doc:`the cliff Sphinx extension <sphinxext>`.
|
|||||||
|
|
||||||
.. code-block:: rest
|
.. code-block:: rest
|
||||||
|
|
||||||
|
.. autoprogram-cliff:: cliffdemo.main.DemoApp
|
||||||
|
:application: cliffdemo
|
||||||
|
|
||||||
.. autoprogram-cliff:: cliff.demo
|
.. autoprogram-cliff:: cliff.demo
|
||||||
:application: cliffdemo
|
:application: cliffdemo
|
||||||
|
|
||||||
Output
|
Output
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Global Options
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoprogram-cliff:: cliffdemo.main.DemoApp
|
||||||
|
:application: cliffdemo
|
||||||
|
|
||||||
|
Command Options
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autoprogram-cliff:: cliff.demo
|
.. autoprogram-cliff:: cliff.demo
|
||||||
:application: cliffdemo
|
:application: cliffdemo
|
||||||
|
@@ -7,27 +7,55 @@ Usage
|
|||||||
|
|
||||||
cliff supports integration with Sphinx by way of a `Sphinx directives`__.
|
cliff supports integration with Sphinx by way of a `Sphinx directives`__.
|
||||||
|
|
||||||
|
Preparation
|
||||||
|
-----------
|
||||||
|
|
||||||
Before using the :rst:dir:`autoprogram-cliff` directive you must add
|
Before using the :rst:dir:`autoprogram-cliff` directive you must add
|
||||||
`'cliff.sphinxext'` extension module to a list of `extensions` in `conf.py`:
|
`'cliff.sphinxext'` extension module to a list of `extensions` in `conf.py`:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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.
|
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
|
.. code-block:: rst
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.compute.v2
|
.. autoprogram-cliff:: openstack.compute.v2
|
||||||
:command: server add fixed ip
|
:command: server add fixed ip
|
||||||
|
|
||||||
One argument is required, corresponding to the namespace that the command(s)
|
The **app** mode documents various information of a specified instance of
|
||||||
can be found in. This is generally defined in the `entry_points` section of
|
:py:class:`cliff.app.App`. The **app** mode takes the python path of the
|
||||||
either `setup.cfg` or `setup.py`. Refer to the example_ below for more
|
corresponding class as the argument. In the **app** mode, `:application:`
|
||||||
information.
|
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:
|
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
|
wildcarding is supported. Refer to the example_ below for more
|
||||||
information.
|
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:`
|
`:application:`
|
||||||
The top-level application name, which will be prefixed before all
|
The top-level application name, which will be prefixed before all
|
||||||
commands. This option overrides the global option
|
commands. This option overrides the global option
|
||||||
|
Reference in New Issue
Block a user