]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
doc/_ext: rewrite directive using ObjectDescription
authorKefu Chai <kchai@redhat.com>
Thu, 6 May 2021 07:36:27 +0000 (15:36 +0800)
committerKefu Chai <kchai@redhat.com>
Thu, 6 May 2021 14:21:43 +0000 (22:21 +0800)
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 <kchai@redhat.com>
doc/_ext/ceph_confval.py

index 2d02cd8f73b73d1d5c9daec634cabfeeeccaffa7..71038a47a7f2300cd5197e9664f2b8851488e5e5 100644 (file)
@@ -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/<name>.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}:<confval>'
+        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',