From cef51925a427130c7b1fcdd418bf4d80fb37e9fb Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Mon, 13 Mar 2023 13:10:13 -0400 Subject: Utilities/Sphinx: Require Sphinx 2.x or later Tweak our Sphinx extensions slightly to assert that Sphinx is at least 2.x. Remove hacks for older versions of Sphinx. This cleans up a bunch of messy code and, more importantly, paves the way for consolidating our import statements. --- Utilities/Sphinx/cmake.py | 42 ++++++------------------------------------ 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index 5990f8a..ad61fa6 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -77,39 +77,13 @@ from sphinx.util.nodes import make_refnode from sphinx.util import logging, ws_re from sphinx import addnodes +import sphinx + +# Require at least Sphinx 2.x. +assert sphinx.version_info >= (2,) + logger = logging.getLogger(__name__) -sphinx_before_1_4 = False -sphinx_before_1_7_2 = False -try: - from sphinx import version_info - if version_info < (1, 4): - sphinx_before_1_4 = True - if version_info < (1, 7, 2): - sphinx_before_1_7_2 = True -except ImportError: - # The `sphinx.version_info` tuple was added in Sphinx v1.2: - sphinx_before_1_4 = True - sphinx_before_1_7_2 = True - -if sphinx_before_1_7_2: - # Monkey patch for sphinx generating invalid content for qcollectiongenerator - # https://github.com/sphinx-doc/sphinx/issues/1435 - from sphinx.util.pycompat import htmlescape - from sphinx.builders.qthelp import QtHelpBuilder - old_build_keywords = QtHelpBuilder.build_keywords - def new_build_keywords(self, title, refs, subitems): - old_items = old_build_keywords(self, title, refs, subitems) - new_items = [] - for item in old_items: - before, rest = item.split("ref=\"", 1) - ref, after = rest.split("\"") - if ("<" in ref and ">" in ref): - new_items.append(before + "ref=\"" + htmlescape(ref) + "\"" + after) - else: - new_items.append(item) - return new_items - QtHelpBuilder.build_keywords = new_build_keywords @dataclass class ObjectEntry: @@ -194,11 +168,7 @@ class _cmake_index_entry: self.desc = desc def __call__(self, title, targetid, main = 'main'): - # See https://github.com/sphinx-doc/sphinx/issues/2673 - if sphinx_before_1_4: - return ('pair', u'%s ; %s' % (self.desc, title), targetid, main) - else: - return ('pair', u'%s ; %s' % (self.desc, title), targetid, main, None) + return ('pair', u'%s ; %s' % (self.desc, title), targetid, main, None) _cmake_index_objs = { 'command': _cmake_index_entry('command'), -- cgit v0.12 From 9fab1ad5042140057897a210baec58848a951d29 Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Mon, 13 Mar 2023 13:25:56 -0400 Subject: Utilities/Sphinx: Use f-strings in cmake.py Port all uses of old-style % string formatting to use f-strings. These are generally more readable (and tend to be shorter as well). Although this requires Python 3.6 or later, that should be available 'stock' on most or all platforms still under support, and besides, we were already using f-strings in some places. --- Utilities/Sphinx/cmake.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index ad61fa6..a988980 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -106,7 +106,7 @@ class CMakeModule(Directive): def run(self): settings = self.state.document.settings if not settings.file_insertion_enabled: - raise self.warning('"%s" directive disabled.' % self.name) + raise self.warning(f'{self.name!r} directive disabled.') env = self.state.document.settings.env rel_path, path = env.relfn2path(self.arguments[0]) @@ -117,13 +117,12 @@ class CMakeModule(Directive): settings.record_dependencies.add(path) f = io.FileInput(source_path=path, encoding=encoding, error_handler=e_handler) - except UnicodeEncodeError as error: - msg = ('Problems with "%s" directive path:\n' - 'Cannot encode input file path "%s" ' - '(wrong locale?).' % (self.name, path)) + except UnicodeEncodeError: + msg = (f'Problems with {self.name!r} directive path:\n' + f'Cannot encode input file path {path!r} (wrong locale?).') raise self.severe(msg) except IOError as error: - msg = 'Problems with "%s" directive path:\n%s.' % (self.name, error) + msg = f'Problems with {self.name!r} directive path:\n{error}.' raise self.severe(msg) raw_lines = f.read().splitlines() f.close() @@ -143,7 +142,7 @@ class CMakeModule(Directive): # Line mode: check for .rst start (bracket or line) m = self.re_start.match(line) if m: - rst = ']%s]' % m.group('eq') + rst = f']{m.group("eq")}]' line = '' elif line == '#.rst:': rst = '#' @@ -158,8 +157,8 @@ class CMakeModule(Directive): line = '' lines.append(line) if rst is not None and rst != '#': - raise self.warning('"%s" found unclosed bracket "#[%s[.rst:" in %s' % - (self.name, rst[1:-1], path)) + raise self.warning(f'{self.name!r} found unclosed bracket ' + f'"#[{rst[1:-1]}[.rst:" in {path!r}') self.state_machine.insert_input(lines, path) return [] @@ -168,7 +167,7 @@ class _cmake_index_entry: self.desc = desc def __call__(self, title, targetid, main = 'main'): - return ('pair', u'%s ; %s' % (self.desc, title), targetid, main, None) + return ('pair', f'{self.desc} ; {title}', targetid, main, None) _cmake_index_objs = { 'command': _cmake_index_entry('command'), @@ -244,7 +243,7 @@ class CMakeTransform(Transform): if m: title = m.group(1) targetname = title - targetid = '%s:%s' % (objtype, targetname) + targetid = f'{objtype}:{targetname}' targetnode = nodes.target('', '', ids=[targetid]) self.document.note_explicit_target(targetnode) self.document.insert(0, targetnode) @@ -274,7 +273,7 @@ class CMakeObject(ObjectDescription): targetname = self.targetname else: targetname = name - targetid = '%s:%s' % (self.objtype, targetname) + targetid = f'{self.objtype}:{targetname}' if targetid not in self.state.document.ids: signode['names'].append(targetid) signode['ids'].append(targetid) @@ -513,11 +512,11 @@ class CMakeXRefRole(CMakeReferenceRole[XRefRole]): elif typ == 'cmake:genex': m = CMakeXRefRole._re_genex.match(text) if m: - text = '%s <%s>' % (text, m.group(1)) + text = f'{text} <{m.group(1)}>' elif typ == 'cmake:guide': m = CMakeXRefRole._re_guide.match(text) if m: - text = '%s <%s>' % (m.group(2), text) + text = f'{m.group(2)} <{text}>' return super().__call__(typ, rawtext, text, *args, **kwargs) # We cannot insert index nodes using the result_nodes method @@ -561,9 +560,9 @@ class CMakeXRefTransform(Transform): # Index signature references to their parent command. objname = objname.split('(')[0].lower() - targetnum = env.new_serialno('index-%s:%s' % (objtype, objname)) + targetnum = env.new_serialno(f'index-{objtype}:{objname}') - targetid = 'index-%s-%s:%s' % (targetnum, objtype, objname) + targetid = f'index-{targetnum}-{objtype}:{objname}' targetnode = nodes.target('', '', ids=[targetid]) self.document.note_explicit_target(targetnode) -- cgit v0.12 From adbc8c982d19caa684ecfd4ac78b7f6b3fb6e82b Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Mon, 13 Mar 2023 13:32:36 -0400 Subject: Utilities/Sphinx: Fix flake8 gripes in cmake.py Edit Utilities/Sphinx/cmake.py to avoid lines longer than 79 characters and to obey other flake8-checked style rules. This will allow using flake8 to check for possible issues without having to wade through noise about improper styling. (Also, of course, consistent styling is beneficial in its own right.) --- Utilities/Sphinx/cmake.py | 129 +++++++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 42 deletions(-) diff --git a/Utilities/Sphinx/cmake.py b/Utilities/Sphinx/cmake.py index a988980..2ccaf9a 100644 --- a/Utilities/Sphinx/cmake.py +++ b/Utilities/Sphinx/cmake.py @@ -1,89 +1,123 @@ # 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 +import sphinx + +from docutils.utils.code_analyzer import Lexer, LexerError +from docutils.parsers.rst import Directive, directives +from docutils.transforms import Transform +from docutils.nodes import Element, Node, TextElement, system_message +from docutils import io, nodes + +from sphinx.directives import ObjectDescription, nl_escape_re +from sphinx.domains import Domain, ObjType +from sphinx.roles import XRefRole +from sphinx.util.docutils import ReferenceRole +from sphinx.util.nodes import make_refnode +from sphinx.util import logging, ws_re +from sphinx import addnodes + +# 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, "::") +# - 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.token import (Comment, Name, Number, Operator, Punctuation, + String, Text, Whitespace) 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 +# - 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. +# - 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( + # 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), - (r'\\\\', Punctuation), # used in commands/source_group + # used in commands/source_group + (r'\\\\', Punctuation), (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 + # 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), - (r'\w[\w\.\+-]*/[\w.+-/\\]*', Name.Attribute), # relative path - (r'[A-Z]((\\.)|[\w.+-])*[a-z]((\\.)|[\w.+-])*', Name.Builtin), # initial A-Z, contains a-z + # 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), - (r'(?s)"(\\"|[^"])*"', String), # "string" + # "string" + (r'(?s)"(\\"|[^"])*"', String), (r'\.\.\.', Name.Variable), - (r'<', Operator, '#push'), # <..|..> is different from + # <..|..> is different from + (r'<', Operator, '#push'), (r'>', Operator, '#pop'), (r'\n', Whitespace), (r'[ \t]+', Whitespace), (r'#.*\n', Comment), - # (r'[^<>\])\}\|$"# \t\n]+', Name.Exception), # fallback, for debugging only + # fallback, for debugging only + # (r'[^<>\])\}\|$"# \t\n]+', Name.Exception), ] -from docutils.utils.code_analyzer import Lexer, LexerError -from docutils.parsers.rst import Directive, directives -from docutils.transforms import Transform -from docutils.nodes import Element, Node, TextElement, system_message -from docutils import io, nodes - -from sphinx.directives import ObjectDescription, nl_escape_re -from sphinx.domains import Domain, ObjType -from sphinx.roles import XRefRole -from sphinx.util.docutils import ReferenceRole -from sphinx.util.nodes import make_refnode -from sphinx.util import logging, ws_re -from sphinx import addnodes +# END pygments tweaks -import sphinx +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # 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) -- cgit v0.12