Переглянути джерело

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.
Matthew Woehlke 2 роки тому
батько
коміт
74e3c1d313

+ 82 - 13
Help/dev/documentation.rst

@@ -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 ...
+
+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 (``...``).
 
-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.
+The ``signature`` directive may be used multiple times for different
+signatures of the same command.
 
 Style: Boolean Constants
 ^^^^^^^^^^^^^^^^^^^^^^^^

+ 1 - 1
Source/cmRST.cxx

@@ -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]*(.*)$")

+ 8 - 0
Tests/CMakeLib/testRST.expect

@@ -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.

+ 8 - 0
Tests/CMakeLib/testRST.rst

@@ -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.

+ 82 - 3
Utilities/Sphinx/cmake.py

@@ -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`
     }

+ 23 - 0
Utilities/Sphinx/static/cmake.css

@@ -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 {