summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Hellmann <doug@doughellmann.com>2015-08-22 16:58:38 +0000
committerDoug Hellmann <doug@doughellmann.com>2015-08-22 16:58:38 +0000
commitbc13758bd6a12f3a9be0d4f6ecfe3d2785df543a (patch)
tree3ef21ae1d149fdd761252ee2d46fa964e5a0d54e
parent006415f41bb67d25a2c132ca939aa5126f550a32 (diff)
downloadoslo-config-bc13758bd6a12f3a9be0d4f6ecfe3d2785df543a.tar.gz
Add sphinx extension to embed pretty descriptions of options2.3.0
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
-rw-r--r--oslo_config/generator.py73
-rw-r--r--oslo_config/sphinxext.py159
2 files changed, 200 insertions, 32 deletions
diff --git a/oslo_config/generator.py b/oslo_config/generator.py
index 9e23e80..30a556b 100644
--- a/oslo_config/generator.py
+++ b/oslo_config/generator.py
@@ -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))
diff --git a/oslo_config/sphinxext.py b/oslo_config/sphinxext.py
new file mode 100644
index 0000000..f36069a
--- /dev/null
+++ b/oslo_config/sphinxext.py
@@ -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)