cinder/doc/ext/driver_opts.py
Sean McGinnis c87da91757 Add config table sphinx directive
In the past we had used an openstack-manuals tool to manually generate
config option tables that would then be included into driver config
documentation.

With the move of documentation in-tree and the deprecation and removal
of that tool, we have ended up with options that are no longer being
updated when drivers change, or maintainers are left manually updating
the existing tables.

This addes a sphinx extension to use a new config-table directive to
automatically pull in config options from the source so we no longer
need to perform any manual action to pick up changes.

Change-Id: I625fb96229001c326ed2400155e2d067279a400e
2018-04-26 13:47:43 -05:00

128 lines
4.3 KiB
Python

# Copyright (c) 2018 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# 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.
"""Sphinx extension to be able to extract driver config options from code."""
import importlib
from docutils import nodes
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils import statemachine as sm
from oslo_config import cfg
class ConfigTableDirective(rst.Directive):
"""Directive to extract config options into docs output."""
option_spec = {
'table-title': directives.unchanged,
'config-target': directives.unchanged,
}
has_content = True
def _doc_module(self, module):
"""Extract config options from module."""
options = []
try:
mod = importlib.import_module(module)
for prop in dir(mod):
thing = getattr(mod, prop)
if isinstance(thing, cfg.Opt):
# An individual config option
options.append(thing)
elif (isinstance(thing, list) and len(thing) > 0 and
isinstance(thing[0], cfg.Opt)):
# A list of config opts
options.extend(thing)
except Exception as e:
self.error('Unable to import {}: {}'.format(module, e))
return options
def _get_default(self, opt):
"""Tries to pick the best text to use as the default."""
if hasattr(opt, 'sample_default') and opt.sample_default:
return opt.sample_default
if type(opt.default) == list:
return ', '.join(str(item) for item in opt.default)
result = str(opt.default)
if not result:
result = '<>'
return result
def run(self):
"""Load and find config options to document."""
modules = [c.strip() for c in self.content if c.strip()]
if not modules:
raise self.error('No modules provided to document.')
env = self.state.document.settings.env
app = env.app
result = sm.ViewList()
source = '<{}>'.format(__name__)
target = self.options.get('config-target', '')
title = self.options.get(
'table-title',
'Description of {} configuration options'.format(target))
result.append('.. _{}:'.format(title.replace(' ', '-')), source)
result.append('', source)
result.append('.. list-table:: {}'.format(title), source)
result.append(' :header-rows: 1', source)
result.append(' :class: config-ref-table', source)
result.append('', source)
result.append(' * - Configuration option = Default value', source)
result.append(' - Description', source)
options = []
for module in modules:
retval = self._doc_module(module)
if retval:
options.extend(retval)
else:
app.info('[config-table] No options found in {}'.format(
module))
# Get options sorted alphabetically but with deprecated options last
list.sort(options, key=lambda opt: opt.name)
list.sort(options, key=lambda opt: opt.deprecated_for_removal)
for opt in options:
result.append(
' * - ``{}`` = ``{}``'.format(
opt.name, self._get_default(opt)),
source)
result.append(
' - ({}) {}{}'.format(
opt.type, opt.help,
' **DEPRECATED**' if opt.deprecated_for_removal else ''),
source)
node = nodes.section()
node.document = self.state.document
self.state.nested_parse(result, 0, node)
return node.children
def setup(app):
app.add_directive('config-table', ConfigTableDirective)