diff options
Diffstat (limited to 'Utilities/Sphinx')
-rw-r--r-- | Utilities/Sphinx/cmake.py | 96 |
1 files changed, 68 insertions, 28 deletions
diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index 9043709f9f..a07b6e9091 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -4,6 +4,9 @@ import os import re +from dataclasses import dataclass +from typing import Any, cast + # Override much of pygments' CMakeLexer. # We need to parse CMake syntax definitions, not CMake code. @@ -69,9 +72,11 @@ from sphinx.directives import ObjectDescription, nl_escape_re from sphinx.domains import Domain, ObjType from sphinx.roles import XRefRole from sphinx.util.nodes import make_refnode -from sphinx.util import ws_re +from sphinx.util import logging, ws_re from sphinx import addnodes +logger = logging.getLogger(__name__) + sphinx_before_1_4 = False sphinx_before_1_7_2 = False try: @@ -104,6 +109,14 @@ if sphinx_before_1_7_2: return new_items QtHelpBuilder.build_keywords = new_build_keywords +@dataclass +class ObjectEntry: + docname: str + objtype: str + node_id: str + name: str + + class CMakeModule(Directive): required_arguments = 1 optional_arguments = 0 @@ -205,14 +218,6 @@ _cmake_index_objs = { 'variable': _cmake_index_entry('variable'), } -def _cmake_object_inventory(env, document, line, objtype, targetid): - inv = env.domaindata['cmake']['objects'] - if targetid in inv: - document.reporter.warning( - 'CMake object "%s" also described in "%s".' % - (targetid, env.doc2path(inv[targetid][0])), line=line) - inv[targetid] = (env.docname, objtype) - class CMakeTransform(Transform): # Run this transform early since we insert nodes we want @@ -275,8 +280,10 @@ class CMakeTransform(Transform): indexnode = addnodes.index() indexnode['entries'] = [make_index_entry(title, targetid)] self.document.insert(0, indexnode) + # Add to cmake domain object inventory - _cmake_object_inventory(env, self.document, 1, objtype, targetid) + domain = cast(CMakeDomain, env.get_domain('cmake')) + domain.note_object(objtype, targetname, targetid, targetid) class CMakeObject(ObjectDescription): @@ -300,8 +307,10 @@ class CMakeObject(ObjectDescription): signode['ids'].append(targetid) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - _cmake_object_inventory(self.env, self.state.document, - self.lineno, self.objtype, targetid) + + domain = cast(CMakeDomain, self.env.get_domain('cmake')) + domain.note_object(self.objtype, targetname, targetid, targetid, + location=signode) make_index_entry = _cmake_index_objs.get(self.objtype) if make_index_entry: @@ -351,7 +360,7 @@ class CMakeSignatureObject(CMakeObject): def add_target_and_index(self, name, sig, signode): if name in self.targetnames: - targetname = self.targetnames[name].lower() + sigargs = self.targetnames[name] else: def extract_keywords(params): for p in params: @@ -361,7 +370,8 @@ class CMakeSignatureObject(CMakeObject): return keywords = extract_keywords(name.split('(')[1].split()) - targetname = ' '.join(keywords).lower() + sigargs = ' '.join(keywords) + targetname = sigargs.lower() targetid = nodes.make_id(targetname) if targetid not in self.state.document.ids: @@ -370,6 +380,15 @@ class CMakeSignatureObject(CMakeObject): signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) + # Register the signature as a command object. + command = name.split('(')[0].lower() + refname = f'{command}({sigargs})' + refid = f'command:{command}({targetname})' + + domain = cast(CMakeDomain, self.env.get_domain('cmake')) + domain.note_object('command', name=refname, target_id=refid, + node_id=targetid, location=signode) + def run(self): targets = self.options.get('target') if targets is not None: @@ -384,19 +403,15 @@ class CMakeXRefRole(XRefRole): # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'. _re = re.compile(r'^(.+?)(\s*)(?<!\x00)<(.*?)>$', re.DOTALL) - _re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL) + _re_ref = re.compile(r'^.*\s<\w+([(][\w\s]+[)])?>$', re.DOTALL) _re_genex = re.compile(r'^\$<([^<>:]+)(:[^<>]+)?>$', re.DOTALL) _re_guide = re.compile(r'^([^<>/]+)/([^<>]*)$', re.DOTALL) def __call__(self, typ, rawtext, text, *args, **keys): - # Translate CMake command cross-references of the form: - # `command_name(SUB_COMMAND)` - # to have an explicit target: - # `command_name(SUB_COMMAND) <command_name>` if typ == 'cmake:command': - m = CMakeXRefRole._re_sub.match(text) - if m: - text = '%s <%s>' % (text, m.group(1)) + m = CMakeXRefRole._re_ref.match(text) + if m is None: + text = f'{text} <{text}>' elif typ == 'cmake:genex': m = CMakeXRefRole._re_genex.match(text) if m: @@ -452,6 +467,10 @@ class CMakeXRefTransform(Transform): # Do not index cross-references to guide sections. continue + if objtype == 'command': + # Index signature references to their parent command. + objname = objname.split('(')[0].lower() + targetnum = env.new_serialno('index-%s:%s' % (objtype, objname)) targetid = 'index-%s-%s:%s' % (targetnum, objtype, objname) @@ -518,25 +537,46 @@ class CMakeDomain(Domain): def clear_doc(self, docname): to_clear = set() - for fullname, (fn, _) in self.data['objects'].items(): - if fn == docname: + for fullname, obj in self.data['objects'].items(): + if obj.docname == docname: to_clear.add(fullname) for fullname in to_clear: del self.data['objects'][fullname] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - targetid = '%s:%s' % (typ, target) + targetid = f'{typ}:{target}' obj = self.data['objects'].get(targetid) + + if obj is None and typ == 'command': + # If 'command(args)' wasn't found, try just 'command'. + # TODO: remove this fallback? warn? + # logger.warning(f'no match for {targetid}') + command = target.split('(')[0] + targetid = f'{typ}:{command}' + obj = self.data['objects'].get(targetid) + if obj is None: # TODO: warn somehow? return None - return make_refnode(builder, fromdocname, obj[0], targetid, + + return make_refnode(builder, fromdocname, obj.docname, obj.node_id, contnode, target) + def note_object(self, objtype: str, name: str, target_id: str, + node_id: str, location: Any = None): + if target_id in self.data['objects']: + other = self.data['objects'][target_id].docname + logger.warning( + f'CMake object {target_id!r} also described in {other!r}', + location=location) + + self.data['objects'][target_id] = ObjectEntry( + self.env.docname, objtype, node_id, name) + def get_objects(self): - for refname, (docname, type) in self.data['objects'].items(): - yield (refname, refname, type, docname, refname, 1) + for refname, obj in self.data['objects'].items(): + yield (refname, obj.name, obj.objtype, obj.docname, obj.node_id, 1) def setup(app): app.add_directive('cmake-module', CMakeModule) |