diff options
author | Doug Hellmann <doug@doughellmann.com> | 2015-08-22 16:58:38 +0000 |
---|---|---|
committer | Doug Hellmann <doug@doughellmann.com> | 2015-08-22 16:58:38 +0000 |
commit | bc13758bd6a12f3a9be0d4f6ecfe3d2785df543a (patch) | |
tree | 3ef21ae1d149fdd761252ee2d46fa964e5a0d54e | |
parent | 006415f41bb67d25a2c132ca939aa5126f550a32 (diff) | |
download | oslo-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.py | 73 | ||||
-rw-r--r-- | oslo_config/sphinxext.py | 159 |
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) |