749 lines
27 KiB
Python
Raw Normal View History

2016-10-30 18:24:19 +01:00
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
2023-07-02 19:51:09 +02:00
# BEGIN imports
2014-08-03 19:52:23 +02:00
import os
import re
2023-07-02 19:51:09 +02:00
from dataclasses import dataclass
from typing import Any, List, Tuple, Type, cast
import sphinx
# The following imports may fail if we don't have Sphinx 2.x or later.
if sphinx.version_info >= (2,):
from docutils import io, nodes
from docutils.nodes import Element, Node, TextElement, system_message
from docutils.parsers.rst import Directive, directives
from docutils.transforms import Transform
from docutils.utils.code_analyzer import Lexer, LexerError
from sphinx import addnodes
from sphinx.directives import ObjectDescription, nl_escape_re
from sphinx.domains import Domain, ObjType
from sphinx.roles import XRefRole
from sphinx.util import logging, ws_re
from sphinx.util.docutils import ReferenceRole
from sphinx.util.nodes import make_refnode
else:
# Sphinx 2.x is required.
assert sphinx.version_info >= (2,)
# END imports
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# BEGIN pygments tweaks
2014-08-03 19:52:23 +02:00
2019-11-11 23:01:05 +01:00
# Override much of pygments' CMakeLexer.
# We need to parse CMake syntax definitions, not CMake code.
# For hard test cases that use much of the syntax below, see
2023-07-02 19:51:09 +02:00
# - module/FindPkgConfig.html
# (with "glib-2.0>=2.10 gtk+-2.0" and similar)
# - module/ExternalProject.html
# (with http:// https:// git@; also has command options -E --build)
# - manual/cmake-buildsystem.7.html
# (with nested $<..>; relative and absolute paths, "::")
from pygments.lexer import bygroups # noqa I100
2014-08-03 19:52:23 +02:00
from pygments.lexers import CMakeLexer
2023-07-02 19:51:09 +02:00
from pygments.token import (Comment, Name, Number, Operator, Punctuation,
String, Text, Whitespace)
2019-11-11 23:01:05 +01:00
# Notes on regular expressions below:
# - [\.\+-] are needed for string constants like gtk+-2.0
2023-07-02 19:51:09 +02:00
# - Unix paths are recognized by '/'; support for Windows paths may be added
# if needed
2019-11-11 23:01:05 +01:00
# - (\\.) allows for \-escapes (used in manual/cmake-language.7)
2021-09-14 00:13:48 +02:00
# - $<..$<..$>..> nested occurrence in cmake-buildsystem
2023-07-02 19:51:09 +02:00
# - Nested variable evaluations are only supported in a limited capacity.
# Only one level of nesting is supported and at most one nested variable can
# be present.
2019-11-11 23:01:05 +01:00
CMakeLexer.tokens["root"] = [
2023-07-02 19:51:09 +02:00
# fctn(
(r'\b(\w+)([ \t]*)(\()',
bygroups(Name.Function, Text, Name.Function), '#push'),
2019-11-11 23:01:05 +01:00
(r'\(', Name.Function, '#push'),
(r'\)', Name.Function, '#pop'),
(r'\[', Punctuation, '#push'),
(r'\]', Punctuation, '#pop'),
(r'[|;,.=*\-]', Punctuation),
2023-07-02 19:51:09 +02:00
# used in commands/source_group
(r'\\\\', Punctuation),
2019-11-11 23:01:05 +01:00
(r'[:]', Operator),
2023-07-02 19:51:09 +02:00
# used in FindPkgConfig.cmake
(r'[<>]=', Punctuation),
# $<...>
(r'\$<', Operator, '#push'),
# <expr>
(r'<[^<|]+?>(\w*\.\.\.)?', Name.Variable),
# ${..} $ENV{..}, possibly nested
(r'(\$\w*\{)([^\}\$]*)?(?:(\$\w*\{)([^\}]+?)(\}))?([^\}]*?)(\})',
bygroups(Operator, Name.Tag, Operator, Name.Tag, Operator, Name.Tag,
Operator)),
# DATA{ ...}
(r'([A-Z]+\{)(.+?)(\})', bygroups(Operator, Name.Tag, Operator)),
# URL, git@, ...
(r'[a-z]+(@|(://))((\\.)|[\w.+-:/\\])+', Name.Attribute),
# absolute path
(r'/\w[\w\.\+-/\\]*', Name.Attribute),
2019-11-11 23:01:05 +01:00
(r'/', Name.Attribute),
2023-07-02 19:51:09 +02:00
# relative path
(r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute),
# initial A-Z, contains a-z
(r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin),
2019-11-11 23:01:05 +01:00
(r'@?[A-Z][A-Z0-9_]*', Name.Constant),
(r'[a-z_]((\\;)|(\\ )|[\w.+-])*', Name.Builtin),
(r'[0-9][0-9\.]*', Number),
2023-07-02 19:51:09 +02:00
# "string"
(r'(?s)"(\\"|[^"])*"', String),
2019-11-11 23:01:05 +01:00
(r'\.\.\.', Name.Variable),
2023-07-02 19:51:09 +02:00
# <..|..> is different from <expr>
(r'<', Operator, '#push'),
2019-11-11 23:01:05 +01:00
(r'>', Operator, '#pop'),
(r'\n', Whitespace),
(r'[ \t]+', Whitespace),
(r'#.*\n', Comment),
2023-07-02 19:51:09 +02:00
# fallback, for debugging only
# (r'[^<>\])\}\|$"# \t\n]+', Name.Exception),
2019-11-11 23:01:05 +01:00
]
2014-08-03 19:52:23 +02:00
2023-07-02 19:51:09 +02:00
# END pygments tweaks
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
logger = logging.getLogger(__name__)
# RE to split multiple command signatures.
sig_end_re = re.compile(r'(?<=[)])\n')
@dataclass
class ObjectEntry:
docname: str
objtype: str
node_id: str
name: str
2014-08-03 19:52:23 +02:00
class CMakeModule(Directive):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'encoding': directives.encoding}
def __init__(self, *args, **keys):
self.re_start = re.compile(r'^#\[(?P<eq>=*)\[\.rst:$')
Directive.__init__(self, *args, **keys)
def run(self):
settings = self.state.document.settings
if not settings.file_insertion_enabled:
2023-07-02 19:51:09 +02:00
raise self.warning(f'{self.name!r} directive disabled.')
2014-08-03 19:52:23 +02:00
env = self.state.document.settings.env
rel_path, path = env.relfn2path(self.arguments[0])
path = os.path.normpath(path)
encoding = self.options.get('encoding', settings.input_encoding)
e_handler = settings.input_encoding_error_handler
try:
settings.record_dependencies.add(path)
f = io.FileInput(source_path=path, encoding=encoding,
error_handler=e_handler)
2023-07-02 19:51:09 +02:00
except UnicodeEncodeError:
msg = (f'Problems with {self.name!r} directive path:\n'
f'Cannot encode input file path {path!r} (wrong locale?).')
2022-11-16 20:14:03 +01:00
raise self.severe(msg)
2014-08-03 19:52:23 +02:00
except IOError as error:
2023-07-02 19:51:09 +02:00
msg = f'Problems with {self.name!r} directive path:\n{error}.'
2022-11-16 20:14:03 +01:00
raise self.severe(msg)
2014-08-03 19:52:23 +02:00
raw_lines = f.read().splitlines()
f.close()
rst = None
lines = []
for line in raw_lines:
if rst is not None and rst != '#':
# Bracket mode: check for end bracket
pos = line.find(rst)
if pos >= 0:
if line[0] == '#':
line = ''
else:
line = line[0:pos]
rst = None
else:
# Line mode: check for .rst start (bracket or line)
m = self.re_start.match(line)
if m:
2023-07-02 19:51:09 +02:00
rst = f']{m.group("eq")}]'
2014-08-03 19:52:23 +02:00
line = ''
elif line == '#.rst:':
rst = '#'
line = ''
elif rst == '#':
if line == '#' or line[:2] == '# ':
line = line[2:]
else:
rst = None
line = ''
elif rst is None:
line = ''
lines.append(line)
if rst is not None and rst != '#':
2023-07-02 19:51:09 +02:00
raise self.warning(f'{self.name!r} found unclosed bracket '
f'"#[{rst[1:-1]}[.rst:" in {path!r}')
2014-08-03 19:52:23 +02:00
self.state_machine.insert_input(lines, path)
return []
2023-07-02 19:51:09 +02:00
2014-08-03 19:52:23 +02:00
class _cmake_index_entry:
def __init__(self, desc):
self.desc = desc
2023-07-02 19:51:09 +02:00
def __call__(self, title, targetid, main='main'):
return ('pair', f'{self.desc} ; {title}', targetid, main, None)
2014-08-03 19:52:23 +02:00
_cmake_index_objs = {
'command': _cmake_index_entry('command'),
2018-10-28 12:09:07 +01:00
'cpack_gen': _cmake_index_entry('cpack generator'),
2018-08-09 18:06:22 +02:00
'envvar': _cmake_index_entry('envvar'),
2014-08-03 19:52:23 +02:00
'generator': _cmake_index_entry('generator'),
2021-09-14 00:13:48 +02:00
'genex': _cmake_index_entry('genex'),
2020-08-30 11:54:41 +02:00
'guide': _cmake_index_entry('guide'),
2014-08-03 19:52:23 +02:00
'manual': _cmake_index_entry('manual'),
'module': _cmake_index_entry('module'),
'policy': _cmake_index_entry('policy'),
'prop_cache': _cmake_index_entry('cache property'),
'prop_dir': _cmake_index_entry('directory property'),
'prop_gbl': _cmake_index_entry('global property'),
2015-04-27 22:25:09 +02:00
'prop_inst': _cmake_index_entry('installed file property'),
2014-08-03 19:52:23 +02:00
'prop_sf': _cmake_index_entry('source file property'),
'prop_test': _cmake_index_entry('test property'),
'prop_tgt': _cmake_index_entry('target property'),
'variable': _cmake_index_entry('variable'),
}
class CMakeTransform(Transform):
# Run this transform early since we insert nodes we want
# treated as if they were written in the documents.
default_priority = 210
def __init__(self, document, startnode):
Transform.__init__(self, document, startnode)
self.titles = {}
def parse_title(self, docname):
2021-09-14 00:13:48 +02:00
"""Parse a document title as the first line starting in [A-Za-z0-9<$]
2014-08-03 19:52:23 +02:00
or fall back to the document basename if no such line exists.
The cmake --help-*-list commands also depend on this convention.
Return the title or False if the document file does not exist.
"""
2023-07-02 19:51:09 +02:00
settings = self.document.settings
env = settings.env
2014-08-03 19:52:23 +02:00
title = self.titles.get(docname)
if title is None:
fname = os.path.join(env.srcdir, docname+'.rst')
try:
2023-07-02 19:51:09 +02:00
f = open(fname, 'r', encoding=settings.input_encoding)
2014-08-03 19:52:23 +02:00
except IOError:
title = False
else:
for line in f:
2023-07-02 19:51:09 +02:00
if len(line) > 0 and (line[0].isalnum() or
line[0] == '<' or line[0] == '$'):
2014-08-03 19:52:23 +02:00
title = line.rstrip()
break
f.close()
if title is None:
title = os.path.basename(docname)
self.titles[docname] = title
return title
def apply(self):
env = self.document.settings.env
# Treat some documents as cmake domain objects.
2020-08-30 11:54:41 +02:00
objtype, sep, tail = env.docname.partition('/')
2014-08-03 19:52:23 +02:00
make_index_entry = _cmake_index_objs.get(objtype)
if make_index_entry:
title = self.parse_title(env.docname)
# Insert the object link target.
2015-04-27 22:25:09 +02:00
if objtype == 'command':
targetname = title.lower()
2021-09-14 00:13:48 +02:00
elif objtype == 'guide' and not tail.endswith('/index'):
targetname = tail
2015-04-27 22:25:09 +02:00
else:
2021-09-14 00:13:48 +02:00
if objtype == 'genex':
m = CMakeXRefRole._re_genex.match(title)
if m:
title = m.group(1)
2015-04-27 22:25:09 +02:00
targetname = title
2023-07-02 19:51:09 +02:00
targetid = f'{objtype}:{targetname}'
2014-08-03 19:52:23 +02:00
targetnode = nodes.target('', '', ids=[targetid])
2015-04-27 22:25:09 +02:00
self.document.note_explicit_target(targetnode)
2014-08-03 19:52:23 +02:00
self.document.insert(0, targetnode)
# Insert the object index entry.
indexnode = addnodes.index()
indexnode['entries'] = [make_index_entry(title, targetid)]
self.document.insert(0, indexnode)
2023-07-02 19:51:09 +02:00
2014-08-03 19:52:23 +02:00
# Add to cmake domain object inventory
2023-07-02 19:51:09 +02:00
domain = cast(CMakeDomain, env.get_domain('cmake'))
domain.note_object(objtype, targetname, targetid, targetid)
2014-08-03 19:52:23 +02:00
class CMakeObject(ObjectDescription):
2023-07-02 19:51:09 +02:00
def __init__(self, *args, **kwargs):
self.targetname = None
super().__init__(*args, **kwargs)
2014-08-03 19:52:23 +02:00
def handle_signature(self, sig, signode):
# called from sphinx.directives.ObjectDescription.run()
signode += addnodes.desc_name(sig, sig)
return sig
def add_target_and_index(self, name, sig, signode):
2015-04-27 22:25:09 +02:00
if self.objtype == 'command':
2023-07-02 19:51:09 +02:00
targetname = name.lower()
elif self.targetname:
targetname = self.targetname
2015-04-27 22:25:09 +02:00
else:
2023-07-02 19:51:09 +02:00
targetname = name
targetid = f'{self.objtype}:{targetname}'
2014-08-03 19:52:23 +02:00
if targetid not in self.state.document.ids:
signode['names'].append(targetid)
signode['ids'].append(targetid)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
2023-07-02 19:51:09 +02:00
domain = cast(CMakeDomain, self.env.get_domain('cmake'))
domain.note_object(self.objtype, targetname, targetid, targetid,
location=signode)
2014-08-03 19:52:23 +02:00
make_index_entry = _cmake_index_objs.get(self.objtype)
if make_index_entry:
self.indexnode['entries'].append(make_index_entry(name, targetid))
2023-07-02 19:51:09 +02:00
class CMakeGenexObject(CMakeObject):
option_spec = {
'target': directives.unchanged,
}
def handle_signature(self, sig, signode):
name = super().handle_signature(sig, signode)
m = CMakeXRefRole._re_genex.match(sig)
if m:
name = m.group(1)
return name
def run(self):
target = self.options.get('target')
if target is not None:
self.targetname = target
return super().run()
class CMakeSignatureObject(CMakeObject):
object_type = 'signature'
BREAK_ALL = 'all'
BREAK_SMART = 'smart'
BREAK_VERBATIM = 'verbatim'
BREAK_CHOICES = {BREAK_ALL, BREAK_SMART, BREAK_VERBATIM}
def break_option(argument):
return directives.choice(argument, CMakeSignatureObject.BREAK_CHOICES)
option_spec = {
'target': directives.unchanged,
'break': break_option,
}
def _break_signature_all(sig: str) -> str:
return ws_re.sub(' ', sig)
def _break_signature_verbatim(sig: str) -> str:
lines = [ws_re.sub('\xa0', line.strip()) for line in sig.split('\n')]
return ' '.join(lines)
def _break_signature_smart(sig: str) -> str:
tokens = []
for line in sig.split('\n'):
token = ''
delim = ''
for c in line.strip():
if len(delim) == 0 and ws_re.match(c):
if len(token):
tokens.append(ws_re.sub('\xa0', token))
token = ''
else:
if c == '[':
delim += ']'
elif c == '<':
delim += '>'
elif len(delim) and c == delim[-1]:
delim = delim[:-1]
token += c
if len(token):
tokens.append(ws_re.sub('\xa0', token))
return ' '.join(tokens)
def __init__(self, *args, **kwargs):
self.targetnames = {}
self.break_style = CMakeSignatureObject.BREAK_SMART
super().__init__(*args, **kwargs)
def get_signatures(self) -> List[str]:
content = nl_escape_re.sub('', self.arguments[0])
lines = sig_end_re.split(content)
if self.break_style == CMakeSignatureObject.BREAK_VERBATIM:
fixup = CMakeSignatureObject._break_signature_verbatim
elif self.break_style == CMakeSignatureObject.BREAK_SMART:
fixup = CMakeSignatureObject._break_signature_smart
else:
fixup = CMakeSignatureObject._break_signature_all
return [fixup(line.strip()) for line in lines]
def handle_signature(self, sig, signode):
language = 'cmake'
classes = ['code', 'cmake', 'highlight']
node = addnodes.desc_name(sig, '', classes=classes)
try:
tokens = Lexer(sig, language, 'short')
except LexerError as error:
if self.state.document.settings.report_level > 2:
# Silently insert without syntax highlighting.
tokens = Lexer(sig, language, 'none')
else:
raise self.warning(error)
for classes, value in tokens:
if value == '\xa0':
node += nodes.inline(value, value, classes=['nbsp'])
elif classes:
node += nodes.inline(value, value, classes=classes)
else:
node += nodes.Text(value)
signode.clear()
signode += node
return sig
def add_target_and_index(self, name, sig, signode):
sig = sig.replace('\xa0', ' ')
if sig in self.targetnames:
sigargs = self.targetnames[sig]
else:
def extract_keywords(params):
for p in params:
if p[0].isalpha():
yield p
else:
return
keywords = extract_keywords(sig.split('(')[1].split())
sigargs = ' '.join(keywords)
targetname = sigargs.lower()
targetid = nodes.make_id(targetname)
if targetid not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetid)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
# Register the signature as a command object.
command = sig.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):
self.break_style = CMakeSignatureObject.BREAK_ALL
targets = self.options.get('target')
if targets is not None:
signatures = self.get_signatures()
targets = [t.strip() for t in targets.split('\n')]
for signature, target in zip(signatures, targets):
self.targetnames[signature] = target
self.break_style = (
self.options.get('break', CMakeSignatureObject.BREAK_SMART))
return super().run()
class CMakeReferenceRole:
2014-08-03 19:52:23 +02:00
# See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'.
_re = re.compile(r'^(.+?)(\s*)(?<!\x00)<(.*?)>$', re.DOTALL)
2023-07-02 19:51:09 +02:00
@staticmethod
def _escape_angle_brackets(text: str) -> str:
# CMake cross-reference targets frequently contain '<' so escape
# any explicit `<target>` with '<' not preceded by whitespace.
while True:
m = CMakeReferenceRole._re.match(text)
if m and len(m.group(2)) == 0:
text = f'{m.group(1)}\x00<{m.group(3)}>'
else:
break
return text
def __class_getitem__(cls, parent: Any):
class Class(parent):
def __call__(self, name: str, rawtext: str, text: str,
*args, **kwargs
) -> Tuple[List[Node], List[system_message]]:
text = CMakeReferenceRole._escape_angle_brackets(text)
return super().__call__(name, rawtext, text, *args, **kwargs)
return Class
class CMakeCRefRole(CMakeReferenceRole[ReferenceRole]):
nodeclass: Type[Element] = nodes.reference
innernodeclass: Type[TextElement] = nodes.literal
classes: List[str] = ['cmake', 'literal']
def run(self) -> Tuple[List[Node], List[system_message]]:
refnode = self.nodeclass(self.rawtext)
self.set_source_info(refnode)
refnode['refid'] = nodes.make_id(self.target)
refnode += self.innernodeclass(self.rawtext, self.title,
classes=self.classes)
return [refnode], []
class CMakeXRefRole(CMakeReferenceRole[XRefRole]):
2014-08-03 19:52:23 +02:00
_re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL)
2021-09-14 00:13:48 +02:00
_re_genex = re.compile(r'^\$<([^<>:]+)(:[^<>]+)?>$', re.DOTALL)
_re_guide = re.compile(r'^([^<>/]+)/([^<>]*)$', re.DOTALL)
2014-08-03 19:52:23 +02:00
2023-07-02 19:51:09 +02:00
def __call__(self, typ, rawtext, text, *args, **kwargs):
2014-08-03 19:52:23 +02:00
if typ == 'cmake:command':
2023-07-02 19:51:09 +02:00
# Translate a CMake command cross-reference of the form:
# `command_name(SUB_COMMAND)`
# to be its own explicit target:
# `command_name(SUB_COMMAND) <command_name(SUB_COMMAND)>`
# so the XRefRole `fix_parens` option does not add more `()`.
2014-08-03 19:52:23 +02:00
m = CMakeXRefRole._re_sub.match(text)
if m:
2023-07-02 19:51:09 +02:00
text = f'{text} <{text}>'
2021-09-14 00:13:48 +02:00
elif typ == 'cmake:genex':
m = CMakeXRefRole._re_genex.match(text)
if m:
2023-07-02 19:51:09 +02:00
text = f'{text} <{m.group(1)}>'
2021-09-14 00:13:48 +02:00
elif typ == 'cmake:guide':
m = CMakeXRefRole._re_guide.match(text)
if m:
2023-07-02 19:51:09 +02:00
text = f'{m.group(2)} <{text}>'
return super().__call__(typ, rawtext, text, *args, **kwargs)
2014-08-03 19:52:23 +02:00
2015-04-27 22:25:09 +02:00
# We cannot insert index nodes using the result_nodes method
# because CMakeXRefRole is processed before substitution_reference
# nodes are evaluated so target nodes (with 'ids' fields) would be
2018-08-09 18:06:22 +02:00
# duplicated in each evaluated substitution replacement. The
2015-04-27 22:25:09 +02:00
# docutils substitution transform does not allow this. Instead we
# use our own CMakeXRefTransform below to add index entries after
# substitutions are completed.
#
# def result_nodes(self, document, env, node, is_ref):
# pass
2023-07-02 19:51:09 +02:00
2015-04-27 22:25:09 +02:00
class CMakeXRefTransform(Transform):
# Run this transform early since we insert nodes we want
# treated as if they were written in the documents, but
# after the sphinx (210) and docutils (220) substitutions.
default_priority = 221
2023-07-02 19:51:09 +02:00
# This helper supports docutils < 0.18, which is missing 'findall',
# and docutils == 0.18.0, which is missing 'traverse'.
def _document_findall_as_list(self, condition):
if hasattr(self.document, 'findall'):
# Fully iterate into a list so the caller can grow 'self.document'
# while iterating.
return list(self.document.findall(condition))
# Fallback to 'traverse' on old docutils, which returns a list.
return self.document.traverse(condition)
2015-04-27 22:25:09 +02:00
def apply(self):
env = self.document.settings.env
# Find CMake cross-reference nodes and add index and target
# nodes for them.
2023-07-02 19:51:09 +02:00
for ref in self._document_findall_as_list(addnodes.pending_xref):
2015-04-27 22:25:09 +02:00
if not ref['refdomain'] == 'cmake':
continue
objtype = ref['reftype']
make_index_entry = _cmake_index_objs.get(objtype)
if not make_index_entry:
continue
objname = ref['reftarget']
2021-09-14 00:13:48 +02:00
if objtype == 'guide' and CMakeXRefRole._re_guide.match(objname):
# Do not index cross-references to guide sections.
continue
2023-07-02 19:51:09 +02:00
if objtype == 'command':
# Index signature references to their parent command.
objname = objname.split('(')[0].lower()
targetnum = env.new_serialno(f'index-{objtype}:{objname}')
2015-04-27 22:25:09 +02:00
2023-07-02 19:51:09 +02:00
targetid = f'index-{targetnum}-{objtype}:{objname}'
2015-04-27 22:25:09 +02:00
targetnode = nodes.target('', '', ids=[targetid])
self.document.note_explicit_target(targetnode)
indexnode = addnodes.index()
indexnode['entries'] = [make_index_entry(objname, targetid, '')]
ref.replace_self([indexnode, targetnode, ref])
2023-07-02 19:51:09 +02:00
2014-08-03 19:52:23 +02:00
class CMakeDomain(Domain):
"""CMake domain."""
name = 'cmake'
label = 'CMake'
object_types = {
'command': ObjType('command', 'command'),
2018-10-28 12:09:07 +01:00
'cpack_gen': ObjType('cpack_gen', 'cpack_gen'),
2018-08-09 18:06:22 +02:00
'envvar': ObjType('envvar', 'envvar'),
2014-08-03 19:52:23 +02:00
'generator': ObjType('generator', 'generator'),
2021-09-14 00:13:48 +02:00
'genex': ObjType('genex', 'genex'),
2020-08-30 11:54:41 +02:00
'guide': ObjType('guide', 'guide'),
2014-08-03 19:52:23 +02:00
'variable': ObjType('variable', 'variable'),
'module': ObjType('module', 'module'),
'policy': ObjType('policy', 'policy'),
'prop_cache': ObjType('prop_cache', 'prop_cache'),
'prop_dir': ObjType('prop_dir', 'prop_dir'),
'prop_gbl': ObjType('prop_gbl', 'prop_gbl'),
2015-04-27 22:25:09 +02:00
'prop_inst': ObjType('prop_inst', 'prop_inst'),
2014-08-03 19:52:23 +02:00
'prop_sf': ObjType('prop_sf', 'prop_sf'),
'prop_test': ObjType('prop_test', 'prop_test'),
'prop_tgt': ObjType('prop_tgt', 'prop_tgt'),
'manual': ObjType('manual', 'manual'),
}
directives = {
'command': CMakeObject,
2018-08-09 18:06:22 +02:00
'envvar': CMakeObject,
2023-07-02 19:51:09 +02:00
'genex': CMakeGenexObject,
'signature': CMakeSignatureObject,
2014-08-03 19:52:23 +02:00
'variable': CMakeObject,
2023-07-02 19:51:09 +02:00
# Other `object_types` cannot be created except by the `CMakeTransform`
2014-08-03 19:52:23 +02:00
}
roles = {
2023-07-02 19:51:09 +02:00
'cref': CMakeCRefRole(),
'command': CMakeXRefRole(fix_parens=True, lowercase=True),
2018-10-28 12:09:07 +01:00
'cpack_gen': CMakeXRefRole(),
2018-08-09 18:06:22 +02:00
'envvar': CMakeXRefRole(),
2014-08-03 19:52:23 +02:00
'generator': CMakeXRefRole(),
2021-09-14 00:13:48 +02:00
'genex': CMakeXRefRole(),
2020-08-30 11:54:41 +02:00
'guide': CMakeXRefRole(),
2014-08-03 19:52:23 +02:00
'variable': CMakeXRefRole(),
'module': CMakeXRefRole(),
'policy': CMakeXRefRole(),
'prop_cache': CMakeXRefRole(),
'prop_dir': CMakeXRefRole(),
'prop_gbl': CMakeXRefRole(),
2015-04-27 22:25:09 +02:00
'prop_inst': CMakeXRefRole(),
2014-08-03 19:52:23 +02:00
'prop_sf': CMakeXRefRole(),
'prop_test': CMakeXRefRole(),
'prop_tgt': CMakeXRefRole(),
'manual': CMakeXRefRole(),
}
initial_data = {
2023-07-02 19:51:09 +02:00
'objects': {}, # fullname -> ObjectEntry
2014-08-03 19:52:23 +02:00
}
def clear_doc(self, docname):
to_clear = set()
2023-07-02 19:51:09 +02:00
for fullname, obj in self.data['objects'].items():
if obj.docname == docname:
2014-08-03 19:52:23 +02:00
to_clear.add(fullname)
for fullname in to_clear:
del self.data['objects'][fullname]
2023-07-02 19:51:09 +02:00
def merge_domaindata(self, docnames, otherdata):
"""Merge domaindata from the workers/chunks when they return.
Called once per parallelization chunk.
Only used when sphinx is run in parallel mode.
:param docnames: a Set of the docnames that are part of the current
chunk to merge
:param otherdata: the partial data calculated by the current chunk
"""
for refname, obj in otherdata['objects'].items():
if obj.docname in docnames:
self.data['objects'][refname] = obj
2014-08-03 19:52:23 +02:00
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
2023-07-02 19:51:09 +02:00
targetid = f'{typ}:{target}'
2014-08-03 19:52:23 +02:00
obj = self.data['objects'].get(targetid)
2023-07-02 19:51:09 +02:00
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)
2014-08-03 19:52:23 +02:00
if obj is None:
# TODO: warn somehow?
return None
2023-07-02 19:51:09 +02:00
return make_refnode(builder, fromdocname, obj.docname, obj.node_id,
2014-08-03 19:52:23 +02:00
contnode, target)
2023-07-02 19:51:09 +02:00
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)
2014-08-03 19:52:23 +02:00
def get_objects(self):
2023-07-02 19:51:09 +02:00
for refname, obj in self.data['objects'].items():
2023-07-14 19:10:31 +02:00
yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
2023-07-02 19:51:09 +02:00
2014-08-03 19:52:23 +02:00
def setup(app):
app.add_directive('cmake-module', CMakeModule)
app.add_transform(CMakeTransform)
2015-04-27 22:25:09 +02:00
app.add_transform(CMakeXRefTransform)
2014-08-03 19:52:23 +02:00
app.add_domain(CMakeDomain)
2023-05-23 16:38:00 +02:00
return {"parallel_read_safe": True}