diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index a98898060d..2ccaf9a7fe 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -1,67 +1,15 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. +# BEGIN imports + import os import re from dataclasses import dataclass from typing import Any, List, Tuple, Type, cast -# 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 -# - 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.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 -# - (\\.) allows for \-escapes (used in manual/cmake-language.7) -# - $<..$<..$>..> nested occurrence in cmake-buildsystem -# - 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. - -CMakeLexer.tokens["root"] = [ - (r'\b(\w+)([ \t]*)(\()', bygroups(Name.Function, Text, Name.Function), '#push'), # fctn( - (r'\(', Name.Function, '#push'), - (r'\)', Name.Function, '#pop'), - (r'\[', Punctuation, '#push'), - (r'\]', Punctuation, '#pop'), - (r'[|;,.=*\-]', Punctuation), - (r'\\\\', Punctuation), # used in commands/source_group - (r'[:]', Operator), - (r'[<>]=', Punctuation), # used in FindPkgConfig.cmake - (r'\$<', Operator, '#push'), # $<...> - (r'<[^<|]+?>(\w*\.\.\.)?', Name.Variable), # - (r'(\$\w*\{)([^\}\$]*)?(?:(\$\w*\{)([^\}]+?)(\}))?([^\}]*?)(\})', # ${..} $ENV{..}, possibly nested - bygroups(Operator, Name.Tag, Operator, Name.Tag, Operator, Name.Tag, Operator)), - (r'([A-Z]+\{)(.+?)(\})', bygroups(Operator, Name.Tag, Operator)), # DATA{ ...} - (r'[a-z]+(@|(://))((\\.)|[\w.+-:/\\])+', Name.Attribute), # URL, git@, ... - (r'/\w[\w\.\+-/\\]*', Name.Attribute), # absolute path - (r'/', Name.Attribute), - (r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute), # relative path - (r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin), # initial A-Z, contains a-z - (r'@?[A-Z][A-Z0-9_]*', Name.Constant), - (r'[a-z_]((\\;)|(\\ )|[\w.+-])*', Name.Builtin), - (r'[0-9][0-9\.]*', Number), - (r'(?s)"(\\"|[^"])*"', String), # "string" - (r'\.\.\.', Name.Variable), - (r'<', Operator, '#push'), # <..|..> is different from - (r'>', Operator, '#pop'), - (r'\n', Whitespace), - (r'[ \t]+', Whitespace), - (r'#.*\n', Comment), - # (r'[^<>\])\}\|$"# \t\n]+', Name.Exception), # fallback, for debugging only -] +import sphinx from docutils.utils.code_analyzer import Lexer, LexerError from docutils.parsers.rst import Directive, directives @@ -77,13 +25,99 @@ from sphinx.util.nodes import make_refnode from sphinx.util import logging, ws_re from sphinx import addnodes -import sphinx +# END imports + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# BEGIN pygments tweaks + +# 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 +# - 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.lexers import CMakeLexer +from pygments.token import (Comment, Name, Number, Operator, Punctuation, + String, Text, Whitespace) +from pygments.lexer import bygroups + +# 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 +# - (\\.) allows for \-escapes (used in manual/cmake-language.7) +# - $<..$<..$>..> nested occurrence in cmake-buildsystem +# - 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. + +CMakeLexer.tokens["root"] = [ + # fctn( + (r'\b(\w+)([ \t]*)(\()', + bygroups(Name.Function, Text, Name.Function), '#push'), + (r'\(', Name.Function, '#push'), + (r'\)', Name.Function, '#pop'), + (r'\[', Punctuation, '#push'), + (r'\]', Punctuation, '#pop'), + (r'[|;,.=*\-]', Punctuation), + # used in commands/source_group + (r'\\\\', Punctuation), + (r'[:]', Operator), + # used in FindPkgConfig.cmake + (r'[<>]=', Punctuation), + # $<...> + (r'\$<', Operator, '#push'), + # + (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), + (r'/', Name.Attribute), + # relative path + (r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute), + # initial A-Z, contains a-z + (r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin), + (r'@?[A-Z][A-Z0-9_]*', Name.Constant), + (r'[a-z_]((\\;)|(\\ )|[\w.+-])*', Name.Builtin), + (r'[0-9][0-9\.]*', Number), + # "string" + (r'(?s)"(\\"|[^"])*"', String), + (r'\.\.\.', Name.Variable), + # <..|..> is different from + (r'<', Operator, '#push'), + (r'>', Operator, '#pop'), + (r'\n', Whitespace), + (r'[ \t]+', Whitespace), + (r'#.*\n', Comment), + # fallback, for debugging only + # (r'[^<>\])\}\|$"# \t\n]+', Name.Exception), +] + +# END pygments tweaks + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Require at least Sphinx 2.x. assert sphinx.version_info >= (2,) logger = logging.getLogger(__name__) +# RE to split multiple command signatures. +sig_end_re = re.compile(r'(?<=[)])\n') + @dataclass class ObjectEntry: @@ -162,13 +196,15 @@ class CMakeModule(Directive): self.state_machine.insert_input(lines, path) return [] + class _cmake_index_entry: def __init__(self, desc): self.desc = desc - def __call__(self, title, targetid, main = 'main'): + def __call__(self, title, targetid, main='main'): return ('pair', f'{self.desc} ; {title}', targetid, main, None) + _cmake_index_objs = { 'command': _cmake_index_entry('command'), 'cpack_gen': _cmake_index_entry('cpack generator'), @@ -189,6 +225,7 @@ _cmake_index_objs = { 'variable': _cmake_index_entry('variable'), } + class CMakeTransform(Transform): # Run this transform early since we insert nodes we want @@ -215,7 +252,8 @@ class CMakeTransform(Transform): title = False else: for line in f: - if len(line) > 0 and (line[0].isalnum() or line[0] == '<' or line[0] == '$'): + if len(line) > 0 and (line[0].isalnum() or + line[0] == '<' or line[0] == '$'): title = line.rstrip() break f.close() @@ -256,6 +294,7 @@ class CMakeTransform(Transform): domain = cast(CMakeDomain, env.get_domain('cmake')) domain.note_object(objtype, targetname, targetid, targetid) + class CMakeObject(ObjectDescription): def __init__(self, *args, **kwargs): self.targetname = None @@ -453,6 +492,7 @@ class CMakeSignatureObject(CMakeObject): return super().run() + class CMakeReferenceRole: # See sphinx.util.nodes.explicit_title_re; \x00 escapes '<'. _re = re.compile(r'^(.+?)(\s*)(?$', re.DOTALL) @@ -478,6 +518,7 @@ class CMakeReferenceRole: return super().__call__(name, rawtext, text, *args, **kwargs) return Class + class CMakeCRefRole(CMakeReferenceRole[ReferenceRole]): nodeclass: Type[Element] = nodes.reference innernodeclass: Type[TextElement] = nodes.literal @@ -493,6 +534,7 @@ class CMakeCRefRole(CMakeReferenceRole[ReferenceRole]): return [refnode], [] + class CMakeXRefRole(CMakeReferenceRole[XRefRole]): _re_sub = re.compile(r'^([^()\s]+)\s*\(([^()]*)\)$', re.DOTALL) @@ -530,6 +572,7 @@ class CMakeXRefRole(CMakeReferenceRole[XRefRole]): # def result_nodes(self, document, env, node, is_ref): # pass + class CMakeXRefTransform(Transform): # Run this transform early since we insert nodes we want @@ -570,6 +613,7 @@ class CMakeXRefTransform(Transform): indexnode['entries'] = [make_index_entry(objname, targetid, '')] ref.replace_self([indexnode, targetnode, ref]) + class CMakeDomain(Domain): """CMake domain.""" name = 'cmake' @@ -603,7 +647,7 @@ class CMakeDomain(Domain): } roles = { 'cref': CMakeCRefRole(), - 'command': CMakeXRefRole(fix_parens = True, lowercase = True), + 'command': CMakeXRefRole(fix_parens=True, lowercase=True), 'cpack_gen': CMakeXRefRole(), 'envvar': CMakeXRefRole(), 'generator': CMakeXRefRole(), @@ -668,6 +712,7 @@ class CMakeDomain(Domain): 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) app.add_transform(CMakeTransform)