mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-04 14:50:23 +00:00
Merge topic 'signature-refs'
cc21d0e478Utilities/Sphinx: Make signatures linkable37e015d4a6Utilities/Sphinx: Refactor Sphinx reference recording Acked-by: Kitware Robot <kwrobot@kitware.com> Merge-request: !8305
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user