1
0
Эх сурвалжийг харах

Utilities/Sphinx: Make signatures linkable

Add signatures to the collection of observed objects (which can be
referenced elsewhere). Don't automatically strip parameters from a
:command: reference, as these may now link signatures. (Do, however,
munge them into 'text <ref>' form if they aren't already, as not doing
so adds an extra '()' for some reason.) Correspondingly, change xref
resolution to try to match 'command' when a ref like 'command(args)' is
not matched, so that existing links to commands that have not been
converted to use the new signature directive don't immediately break.
Matthew Woehlke 2 жил өмнө
parent
commit
cc21d0e478

+ 3 - 3
Help/dev/documentation.rst

@@ -270,8 +270,7 @@ The ``signature`` directive requires one argument, the signature summary:
   abbreviate it in the ``signature`` directive argument and specify the full
   signature in a ``code-block`` in the description.
 
-The ``signature`` directive generates a document-local hyperlink target
-for each signature:
+The ``signature`` directive generates a hyperlink target for each signature:
 
 * Default target names are automatically extracted from leading "keyword"
   arguments in the signatures, where a keyword is any sequence of
@@ -299,7 +298,8 @@ for each signature:
 
 * The targets may be referenced from within the same document using
   ```REF`_`` or ```TEXT <REF_>`_`` syntax.  Like reStructuredText section
-  headers, the targets do not work with Sphinx ``:ref:`` syntax.
+  headers, the targets do not work with Sphinx ``:ref:`` syntax, however
+  they can be globally referenced using e.g. ``:command:`string(APPEND)```.
 
 The directive treats its content as the documentation of the signature(s).
 Indent the signature documentation accordingly.

+ 29 - 10
Utilities/Sphinx/cmake.py

@@ -360,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:
@@ -370,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:
@@ -379,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:
@@ -393,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:
@@ -461,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)
@@ -537,6 +547,15 @@ class CMakeDomain(Domain):
                      typ, target, node, contnode):
         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