From e6254a9dcb7325b87bd25dea40fd627775c9724d Mon Sep 17 00:00:00 2001 From: Kefu Chai Date: Thu, 6 May 2021 15:36:27 +0800 Subject: [PATCH] doc/_ext: rewrite directive using ObjectDescription which allows us to use different scheme when defining an option, without this change, if two options in different mgr module share the same name we cannot differentiate them, after this change, their id would prefixed with the module name. Signed-off-by: Kefu Chai --- doc/_ext/ceph_confval.py | 143 +++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/doc/_ext/ceph_confval.py b/doc/_ext/ceph_confval.py index 2d02cd8f73b..71038a47a7f 100644 --- a/doc/_ext/ceph_confval.py +++ b/doc/_ext/ceph_confval.py @@ -6,14 +6,17 @@ from typing import Any, Dict, List, Union from docutils.nodes import Node from docutils.parsers.rst import directives -from docutils.parsers.rst import Directive +from docutils.statemachine import StringList + from sphinx import addnodes +from sphinx.directives import ObjectDescription from sphinx.domains.python import PyField from sphinx.environment import BuildEnvironment from sphinx.locale import _ from sphinx.util import logging, status_iterator, ws_re +from sphinx.util.docutils import switch_source_input, SphinxDirective from sphinx.util.docfields import Field - +from sphinx.util.nodes import make_id import jinja2 import jinja2.filters import yaml @@ -22,7 +25,6 @@ logger = logging.getLogger(__name__) TEMPLATE = ''' -.. confval_option:: {{ opt.name }} {% if desc %} {{ desc | wordwrap(70) | indent(3) }} {% endif %} @@ -171,7 +173,7 @@ def jinja_template() -> jinja2.Template: FieldValueT = Union[bool, float, int, str] -class CephModule(Directive): +class CephModule(SphinxDirective): """ Directive to name the mgr module for which options are documented. """ @@ -182,15 +184,14 @@ class CephModule(Directive): def run(self) -> List[Node]: module = self.arguments[0].strip() - env = self.state.document.settings.env if module == 'None': - env.ref_context.pop('ceph:module', None) + self.env.ref_context.pop('ceph:module', None) else: - env.ref_context['ceph:module'] = module + self.env.ref_context['ceph:module'] = module return [] -class CephOption(Directive): +class CephOption(ObjectDescription): """ emit option loaded from given command/options/.yaml.in file """ @@ -200,6 +201,19 @@ class CephOption(Directive): final_argument_whitespace = False option_spec = {'default': directives.unchanged} + + doc_field_types = [ + Field('default', + label=_('Default'), + has_arg=False, + names=('default',)), + Field('type', + label=_('Type'), + has_arg=False, + names=('type',), + bodyrolename='class'), + ] + template = jinja_template() opts: Dict[str, Dict[str, FieldValueT]] = {} mgr_opts: Dict[str, # module name @@ -210,13 +224,12 @@ class CephOption(Directive): def _load_yaml(self) -> Dict[str, Dict[str, FieldValueT]]: if CephOption.opts: return CephOption.opts - env = self.state.document.settings.env opts = [] - for fn in status_iterator(env.config.ceph_confval_imports, + for fn in status_iterator(self.config.ceph_confval_imports, 'loading options...', 'red', - len(env.config.ceph_confval_imports), - env.app.verbosity): - env.note_dependency(fn) + len(self.config.ceph_confval_imports), + self.env.app.verbosity): + self.env.note_dependency(fn) try: with open(fn, 'r') as f: yaml_in = io.StringIO() @@ -307,11 +320,10 @@ class CephOption(Directive): mgr_opts = CephOption.mgr_opts.get(module) if mgr_opts is not None: return mgr_opts - env = self.state.document.settings.env - python_path = env.config.ceph_confval_mgr_python_path + python_path = self.config.ceph_confval_mgr_python_path for path in python_path.split(':'): sys.path.insert(0, self._normalize_path(path)) - module_path = env.config.ceph_confval_mgr_module_path + module_path = self.env.config.ceph_confval_mgr_module_path module_path = self._normalize_path(module_path) sys.path.insert(0, module_path) os.environ['UNITTEST'] = 'true' @@ -322,18 +334,16 @@ class CephOption(Directive): for module in status_iterator(modules, 'loading module...', 'darkgreen', len(modules), - env.app.verbosity): + self.env.app.verbosity): fn = os.path.join(module_path, module, 'module.py') if os.path.exists(fn): - env.note_dependency(fn) + self.env.note_dependency(fn) opts += self._collect_options_from_module(module) CephOption.mgr_opts[module] = dict((opt['name'], opt) for opt in opts) return CephOption.mgr_opts[module] - def run(self) -> List[Any]: - name = self.arguments[0] - env = self.state.document.settings.env - cur_module = env.ref_context.get('ceph:module') + def _render_option(self, name) -> str: + cur_module = self.env.ref_context.get('ceph:module') if cur_module: opt = self._load_module(cur_module).get(name) else: @@ -344,18 +354,51 @@ class CephOption(Directive): opt_default = opt.get('default') default = self.options.get('default', opt_default) try: - rendered = self.template.render(opt=opt, - desc=desc, - default=default) + return self.template.render(opt=opt, + desc=desc, + default=default) except Exception as e: message = (f'Unable to render option "{name}": {e}. ', f'opt={opt}, desc={desc}, default={default}') raise self.error(message) - lineno = self.lineno - self.state_machine.input_offset - 1 - source = self.state_machine.input_lines.source(lineno) - self.state_machine.insert_input(rendered.split('\n'), source) - return [] + def handle_signature(self, + sig: str, + signode: addnodes.desc_signature) -> str: + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like XRefRole does + name = ws_re.sub(' ', sig) + return name + + def transform_content(self, contentnode: addnodes.desc_content) -> None: + name = self.arguments[0] + source, lineno = self.get_source_info() + source = f'{source}:{lineno}:' + fields = StringList(self._render_option(name).splitlines() + [''], + source=source, parent_offset=lineno) + with switch_source_input(self.state, fields): + self.state.nested_parse(fields, 0, contentnode) + + def add_target_and_index(self, + name: str, + sig: str, + signode: addnodes.desc_signature) -> None: + cur_module = self.env.ref_context.get('ceph:module') + if cur_module: + prefix = '-'.join(['mgr', cur_module, self.objtype]) + else: + prefix = self.objtype + node_id = make_id(self.env, self.state.document, prefix, name) + signode['ids'].append(node_id) + self.state.document.note_explicit_target(signode) + if cur_module: + entry = f'{cur_module} {name}; mgr module option' + else: + entry = f'{name}; configuration option' + self.indexnode['entries'].append(('pair', entry, node_id, '', None)) + std = self.env.get_domain('std') + std.note_object(self.objtype, name, node_id, location=signode) def setup(app) -> Dict[str, Any]: @@ -371,41 +414,6 @@ def setup(app) -> Dict[str, Any]: default=[], rebuild='', types=[str]) - app.add_directive('confval', CephOption) - app.add_directive('mgr_module', CephModule) - app.add_object_type( - 'confval_option', - 'confval', - objname='configuration value', - indextemplate='pair: %s; configuration value', - parse_node=_parse_option_desc, - doc_field_types=[ - PyField( - 'type', - label=_('Type'), - has_arg=False, - names=('type',), - bodyrolename='class' - ), - Field( - 'default', - label=_('Default'), - has_arg=False, - names=('default',), - ), - Field( - 'required', - label=_('Required'), - has_arg=False, - names=('required',), - ), - Field( - 'example', - label=_('Example'), - has_arg=False, - ) - ] - ) app.add_object_type( 'confsec', 'confsec', @@ -418,6 +426,13 @@ def setup(app) -> Dict[str, Any]: has_arg=False, )] ) + app.add_object_type( + 'confval', + 'confval', + objname='configuration option', + ) + app.add_directive_to_domain('std', 'mgr_module', CephModule) + app.add_directive_to_domain('std', 'confval', CephOption, override=True) return { 'version': 'builtin', -- 2.39.5