Add sphinx extension to embed pretty descriptions of options

Add a new `show-options` directive for use in sphinx documentation to
embed the help and metadata about options in the output of the rendered
docs.

Change-Id: I549c8db98bf548dd0a7e8869a57301fa4096f78c
This commit is contained in:
Doug Hellmann 2015-08-22 16:58:38 +00:00
parent 006415f41b
commit bc13758bd6
2 changed files with 200 additions and 32 deletions

View File

@ -59,6 +59,46 @@ def register_cli_opts(conf):
conf.register_cli_opts(_generator_opts)
def _format_defaults(opt):
"Return a list of formatted default values."
if isinstance(opt, cfg.MultiStrOpt):
if opt.sample_default is not None:
defaults = opt.sample_default
elif not opt.default:
defaults = ['']
else:
defaults = opt.default
else:
if opt.sample_default is not None:
default_str = str(opt.sample_default)
elif opt.default is None:
default_str = '<None>'
elif isinstance(opt, cfg.StrOpt):
default_str = opt.default
elif isinstance(opt, cfg.BoolOpt):
default_str = str(opt.default).lower()
elif (isinstance(opt, cfg.IntOpt) or
isinstance(opt, cfg.FloatOpt)):
default_str = str(opt.default)
elif isinstance(opt, cfg.ListOpt):
default_str = ','.join(opt.default)
elif isinstance(opt, cfg.DictOpt):
sorted_items = sorted(opt.default.items(),
key=operator.itemgetter(0))
default_str = ','.join(['%s:%s' % i for i in sorted_items])
else:
LOG.warning('Unknown option type: %s', repr(opt))
default_str = str(opt.default)
defaults = [default_str]
results = []
for default_str in defaults:
if default_str.strip() != default_str:
default_str = '"%s"' % default_str
results.append(default_str)
return results
class _OptFormatter(object):
"""Format configuration option descriptions to a file."""
@ -146,39 +186,8 @@ class _OptFormatter(object):
'# This option is deprecated for removal.\n'
'# Its value may be silently ignored in the future.\n')
if isinstance(opt, cfg.MultiStrOpt):
if opt.sample_default is not None:
defaults = opt.sample_default
elif not opt.default:
defaults = ['']
else:
defaults = opt.default
else:
if opt.sample_default is not None:
default_str = str(opt.sample_default)
elif opt.default is None:
default_str = '<None>'
elif isinstance(opt, cfg.StrOpt):
default_str = opt.default
elif isinstance(opt, cfg.BoolOpt):
default_str = str(opt.default).lower()
elif (isinstance(opt, cfg.IntOpt) or
isinstance(opt, cfg.FloatOpt)):
default_str = str(opt.default)
elif isinstance(opt, cfg.ListOpt):
default_str = ','.join(opt.default)
elif isinstance(opt, cfg.DictOpt):
sorted_items = sorted(opt.default.items(),
key=operator.itemgetter(0))
default_str = ','.join(['%s:%s' % i for i in sorted_items])
else:
LOG.warning('Unknown option type: %s', repr(opt))
default_str = str(opt.default)
defaults = [default_str]
defaults = _format_defaults(opt)
for default_str in defaults:
if default_str.strip() != default_str:
default_str = '"%s"' % default_str
if default_str:
default_str = ' ' + default_str
lines.append('#%s =%s\n' % (opt.dest, default_str))

159
oslo_config/sphinxext.py Normal file
View File

@ -0,0 +1,159 @@
# 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
from docutils.parsers import rst
from docutils.statemachine import ViewList
from sphinx.util.nodes import nested_parse_with_titles
from oslo_config import cfg
from oslo_config import generator
import six
def _list_table(add, headers, data, title='', columns=None):
"""Build a list-table directive.
:param add: Function to add one row to output.
:param headers: List of header values.
:param data: Iterable of row data, yielding lists or tuples with rows.
"""
add('.. list-table:: %s' % title)
add(' :header-rows: 1')
if columns:
add(' :widths: %s' % (','.join(str(c) for c in columns)))
add('')
add(' - * %s' % headers[0])
for h in headers[1:]:
add(' * %s' % h)
for row in data:
add(' - * %s' % row[0])
for r in row[1:]:
add(' * %s' % r)
add('')
def _indent(text, n=2):
padding = ' ' * n
return '\n'.join(padding + l for l in text.splitlines())
class ShowOptionsDirective(rst.Directive):
# option_spec = {}
has_content = True
_TYPE_DESCRIPTIONS = {
cfg.StrOpt: 'string',
cfg.BoolOpt: 'boolean',
cfg.IntOpt: 'integer',
cfg.FloatOpt: 'floating point',
cfg.ListOpt: 'list',
cfg.DictOpt: 'dict',
cfg.MultiStrOpt: 'multi-valued',
}
def run(self):
env = self.state.document.settings.env
app = env.app
namespace = ' '.join(self.content)
opts = generator._list_opts([namespace])
result = ViewList()
source_name = '<' + __name__ + '>'
def _add(text):
"Append some text to the output result view to be parsed."
result.append(text, source_name)
def _add_indented(text):
"""Append some text, indented by a couple of spaces.
Indent everything under the option name,
to format it as a definition list.
"""
_add(_indent(text))
by_section = {}
for ignore, opt_list in opts:
for group_name, opts in opt_list:
by_section.setdefault(group_name, []).extend(opts)
for group_name, opt_list in sorted(by_section.items()):
group_name = group_name or 'DEFAULT'
app.info('[oslo.config] %s %s' % (namespace, group_name))
_add(group_name)
_add('=' * len(group_name))
_add('')
for opt in opt_list:
opt_type = self._TYPE_DESCRIPTIONS.get(type(opt),
'unknown type')
_add('``%s``' % opt.dest)
_add('')
_add_indented(':Type: %s' % opt_type)
for default in generator._format_defaults(opt):
if default:
default = '``' + default + '``'
_add_indented(':Default: %s' % default)
if getattr(opt.type, 'min', None):
_add_indented(':Minimum Value: %s' % opt.type.min)
if getattr(opt.type, 'max', None):
_add_indented(':Maximum Value: %s' % opt.type.max)
if getattr(opt.type, 'choices', None):
choices_text = ', '.join([self._get_choice_text(choice)
for choice in opt.type.choices])
_add_indented(':Valid Values: %s' % choices_text)
_add('')
_add_indented(opt.help)
_add('')
if opt.deprecated_opts:
_list_table(
_add_indented,
['Group', 'Name'],
((d.group or 'DEFAULT',
d.name or opt.dest or 'UNSET')
for d in opt.deprecated_opts),
title='Deprecated Variations',
)
if opt.deprecated_for_removal:
_add_indented('.. warning:')
_add_indented(' This option is deprecated for removal.')
_add_indented(' Its value may be silently ignored ')
_add_indented(' in the future.')
_add('')
_add('')
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, result, node)
return node.children
def _get_choice_text(self, choice):
if choice is None:
return '<None>'
elif choice == '':
return "''"
return six.text_type(choice)
def setup(app):
app.add_directive('show-options', ShowOptionsDirective)