Utilities/Sphinx: Add a directive to document command signatures

Add a `signature` directive to offer a CMake version of Sphinx's
`function` directive, similar to that found in other domains (py, cpp,
etc.).  Like others, this takes one or more signatures as arguments and
creates dt/dd nodes from the signatures and the directive contents.
This commit is contained in:
Matthew Woehlke
2023-02-24 15:01:14 -05:00
parent c09b760484
commit 74e3c1d313
6 changed files with 204 additions and 17 deletions

View File

@@ -241,6 +241,69 @@ Document a "genex" object:
The directive requires a single argument, the generator expression name.
``signature`` directive
^^^^^^^^^^^^^^^^^^^^^^^
Document `CMake Command Signatures <Style: CMake Command Signatures_>`_
within a ``Help/command/<command-name>.rst`` document.
.. code-block:: rst
.. signature:: <command-name>(<signature>)
This indented block documents one or more signatures of a CMake command.
The ``signature`` directive requires one argument, the signature summary:
* One or more signatures must immediately follow the ``::``.
The first signature may optionally be placed on the same line.
A blank line following the ``signature`` directive will result in a
documentation generation error: ``1 argument(s) required, 0 supplied``.
* Signatures may be split across multiple lines, but the final ``)`` of each
signature must be the last character on its line.
* Blank lines between signatures are not allowed. (Content after a blank line
is treated as part of the description.)
* Whitespace in signatures is not preserved. To document a complex signature,
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:
* Default target names are automatically extracted from leading "keyword"
arguments in the signatures, where a keyword is any sequence of
non-space starting with a letter. For example, the signature
``string(REGEX REPLACE <match-regex> ...)`` generates the target
``REGEX REPLACE``, similar to ``.. _`REGEX REPLACE`:``.
* Custom target names may be specified using a ``:target:`` option.
For example:
.. code-block:: rst
.. signature::
cmake_path(GET <path-var> ROOT_NAME <out-var>)
cmake_path(GET <path-var> ROOT_PATH <out-var>)
:target:
GET ROOT_NAME
GET ROOT_PATH
Provide a custom target name for each signature, one per line.
The first target may optionally be placed on the same line as ``:target:``.
* If a target name is already in use earlier in the document, no hyperlink
target will be generated.
* 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.
The directive treats its content as the documentation of the signature(s).
Indent the signature documentation accordingly.
``variable`` directive
^^^^^^^^^^^^^^^^^^^^^^
@@ -374,11 +437,11 @@ paragraph.
Style: CMake Command Signatures
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Command signatures should be marked up as plain literal blocks, not as
cmake ``code-blocks``.
Signatures are separated from preceding content by a section header.
That is, use:
A ``Help/command/<command-name>.rst`` document defines one ``command``
object in the `CMake Domain`_, but some commands have multiple signatures.
Use the CMake Domain's `signature directive`_ to document each signature.
Separate signatures from preceding content by a section header.
For example:
.. code-block:: rst
@@ -387,17 +450,23 @@ That is, use:
Normal Libraries
^^^^^^^^^^^^^^^^
::
.. signature::
add_library(<lib> ...)
This signature is used for ...
This signature is used for ...
Signatures of commands should wrap optional parts with square brackets,
and should mark list of optional arguments with an ellipsis (``...``).
Elements of the signature which are specified by the user should be
specified with angle brackets, and may be referred to in prose using
``inline-literal`` syntax.
Use the following conventions in command signature documentation:
* Use an angle-bracket ``<placeholder>`` for arguments to be specified
by the caller. Refer to them in prose using
`inline literal <Style: Inline Literals_>`_ syntax.
* Wrap optional parts with square brackets.
* Mark repeatable parts with a trailing ellipsis (``...``).
The ``signature`` directive may be used multiple times for different
signatures of the same command.
Style: Boolean Constants
^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -20,7 +20,7 @@ cmRST::cmRST(std::ostream& os, std::string docroot)
: OS(os)
, DocRoot(std::move(docroot))
, CMakeDirective("^.. (cmake:)?("
"command|envvar|genex|variable"
"command|envvar|genex|signature|variable"
")::[ \t]+([^ \t\n]+)$")
, CMakeModuleDirective("^.. cmake-module::[ \t]+([^ \t\n]+)$")
, ParsedLiteralDirective("^.. parsed-literal::[ \t]*(.*)$")

View File

@@ -70,6 +70,14 @@ Bracket Comment Content
Generator expression $<OTHER_GENEX> description.
.. cmake:signature:: some_command(SOME_SIGNATURE)
Command some_command SOME_SIGNATURE description.
.. signature:: other_command(OTHER_SIGNATURE)
Command other_command OTHER_SIGNATURE description.
.. cmake:variable:: some_var
Variable some_var description.

View File

@@ -73,6 +73,14 @@ Inline literal ``__`` followed by inline link `Link Text <InternalDest_>`_.
Generator expression $<OTHER_GENEX> description.
.. cmake:signature:: some_command(SOME_SIGNATURE)
Command some_command SOME_SIGNATURE description.
.. signature:: other_command(OTHER_SIGNATURE)
Command other_command OTHER_SIGNATURE description.
.. cmake:variable:: some_var
Variable some_var description.

View File

@@ -16,6 +16,9 @@ from pygments.lexers import CMakeLexer
from pygments.token import Name, Operator, Punctuation, String, Text, Comment, Generic, Whitespace, Number
from pygments.lexer import bygroups
# RE to split multiple command signatures
sig_end_re = re.compile(r'(?<=[)])\n')
# Notes on regular expressions below:
# - [\.\+-] are needed for string constants like gtk+-2.0
# - Unix paths are recognized by '/'; support for Windows paths may be added if needed
@@ -57,14 +60,16 @@ CMakeLexer.tokens["root"] = [
# (r'[^<>\])\}\|$"# \t\n]+', Name.Exception), # fallback, for debugging only
]
from docutils.utils.code_analyzer import Lexer, LexerError
from docutils.parsers.rst import Directive, directives
from docutils.transforms import Transform
from docutils import io, nodes
from sphinx.directives import ObjectDescription
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 import addnodes
sphinx_before_1_4 = False
@@ -286,9 +291,9 @@ class CMakeObject(ObjectDescription):
def add_target_and_index(self, name, sig, signode):
if self.objtype == 'command':
targetname = name.lower()
targetname = name.lower()
else:
targetname = name
targetname = name
targetid = '%s:%s' % (self.objtype, targetname)
if targetid not in self.state.document.ids:
signode['names'].append(targetid)
@@ -302,6 +307,79 @@ class CMakeObject(ObjectDescription):
if make_index_entry:
self.indexnode['entries'].append(make_index_entry(name, targetid))
class CMakeSignatureObject(CMakeObject):
object_type = 'signature'
option_spec = {
'target': directives.unchanged,
}
def get_signatures(self):
content = nl_escape_re.sub('', self.arguments[0])
lines = sig_end_re.split(content)
return [ws_re.sub(' ', 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 classes:
node += nodes.inline(value, value, classes=classes)
else:
node += nodes.Text(value)
signode.clear()
signode += node
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:
targetname = self.targetnames[name].lower()
else:
def extract_keywords(params):
for p in params:
if p[0].isalpha():
yield p
else:
return
keywords = extract_keywords(name.split('(')[1].split())
targetname = ' '.join(keywords).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)
def run(self):
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
return super().run()
class CMakeXRefRole(XRefRole):
# See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'.
@@ -411,6 +489,7 @@ class CMakeDomain(Domain):
'command': CMakeObject,
'envvar': CMakeObject,
'genex': CMakeObject,
'signature': CMakeSignatureObject,
'variable': CMakeObject,
# Other `object_types` cannot be created except by the `CMakeTransform`
}

View File

@@ -17,6 +17,29 @@ div.sphinxsidebarwrapper {
background-color: #dfdfdf;
}
/* Apply <pre> style (from classic.css) to signature directive argument. */
.signature .sig {
padding: 5px;
background-color: #eeeeee;
color: #333333;
line-height: 120%;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
/* Add additional styling to signature directive argument. */
.signature .sig {
margin-bottom: 5px;
padding-left: calc(5px + 3em);
text-indent: -3em;
font-family: monospace;
}
.signature .sig .code.sig-name {
font-weight: normal;
}
/* Remove unwanted margin in case list item contains a div-wrapping
directive like `.. versionadded` or `.. deprecated`. */
dd > :first-child > p {