diff options
-rw-r--r-- | Help/dev/documentation.rst | 17 | ||||
-rw-r--r-- | Utilities/Sphinx/cmake.py | 83 | ||||
-rw-r--r-- | Utilities/Sphinx/static/cmake.css | 5 |
3 files changed, 93 insertions, 12 deletions
diff --git a/Help/dev/documentation.rst b/Help/dev/documentation.rst index 1dd23c6..b7a77c9 100644 --- a/Help/dev/documentation.rst +++ b/Help/dev/documentation.rst @@ -305,6 +305,23 @@ The ``signature`` directive generates a hyperlink target for each signature: headers, the targets do not work with Sphinx ``:ref:`` syntax, however they can be globally referenced using e.g. ``:command:`string(APPEND)```. +Although whitespace in the signature is not preserved, by default, line breaks +are suppressed inside of square- or angle-brackets. This behavior can be +controlled using the ``:break:`` option; note, however, that there is no way +to *force* a line break. The default value is 'smart'. Allowable values are: + + ``all`` + Allow line breaks at any whitespace. + + ``smart`` (default) + Allow line breaks at whitespace, except between matched square- or + angle-brackets. For example, if a signature contains the text + ``<input>... [OUTPUT_VARIABLE <out-var>]``, a line break would be allowed + after ``<input>...`` but not between ``OUTPUT_VARIABLE`` and ``<out-var>``. + + ``verbatim`` + Allow line breaks only where the source document contains a newline. + The directive treats its content as the documentation of the signature(s). Indent the signature documentation accordingly. diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index edfbc7b..38fd98a 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -5,7 +5,7 @@ import os import re from dataclasses import dataclass -from typing import Any, cast +from typing import Any, List, cast # Override much of pygments' CMakeLexer. # We need to parse CMake syntax definitions, not CMake code. @@ -343,14 +343,69 @@ class CMakeGenexObject(CMakeObject): 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 get_signatures(self): + 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) - return [ws_re.sub(' ', line.strip()) for line in lines] + + 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' @@ -368,7 +423,9 @@ class CMakeSignatureObject(CMakeObject): raise self.warning(error) for classes, value in tokens: - if classes: + if value == '\xa0': + node += nodes.inline(value, value, classes=['nbsp']) + elif classes: node += nodes.inline(value, value, classes=classes) else: node += nodes.Text(value) @@ -378,13 +435,10 @@ class CMakeSignatureObject(CMakeObject): return sig - def __init__(self, *args, **kwargs): - self.targetnames = {} - super().__init__(*args, **kwargs) - def add_target_and_index(self, name, sig, signode): - if name in self.targetnames: - sigargs = self.targetnames[name] + sig = sig.replace('\xa0', ' ') + if sig in self.targetnames: + sigargs = self.targetnames[sig] else: def extract_keywords(params): for p in params: @@ -393,7 +447,7 @@ class CMakeSignatureObject(CMakeObject): else: return - keywords = extract_keywords(name.split('(')[1].split()) + keywords = extract_keywords(sig.split('(')[1].split()) sigargs = ' '.join(keywords) targetname = sigargs.lower() targetid = nodes.make_id(targetname) @@ -405,7 +459,7 @@ class CMakeSignatureObject(CMakeObject): self.state.document.note_explicit_target(signode) # Register the signature as a command object. - command = name.split('(')[0].lower() + command = sig.split('(')[0].lower() refname = f'{command}({sigargs})' refid = f'command:{command}({targetname})' @@ -414,6 +468,8 @@ class CMakeSignatureObject(CMakeObject): 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() @@ -421,6 +477,9 @@ class CMakeSignatureObject(CMakeObject): 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 CMakeXRefRole(XRefRole): diff --git a/Utilities/Sphinx/static/cmake.css b/Utilities/Sphinx/static/cmake.css index dd0dd02..41a74f5 100644 --- a/Utilities/Sphinx/static/cmake.css +++ b/Utilities/Sphinx/static/cmake.css @@ -40,6 +40,11 @@ div.sphinxsidebarwrapper { font-weight: normal; } +/* Implement non-breaking spaces in signatures. */ +.nbsp { + white-space: nowrap; +} + /* Remove unwanted margin in case list item contains a div-wrapping directive like `.. versionadded` or `.. deprecated`. */ dd > :first-child > p { |