From 157e6a47c2da89a0b2241c7cc3b19faedadc812a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 8 Oct 2024 02:29:52 +0100 Subject: [PATCH 1/4] Extract ``availability`` into a new extension --- Doc/conf.py | 1 + Doc/tools/extensions/availability.py | 103 +++++++++++++++++++++++++++ Doc/tools/extensions/pyspecific.py | 76 -------------------- 3 files changed, 104 insertions(+), 76 deletions(-) create mode 100644 Doc/tools/extensions/availability.py diff --git a/Doc/conf.py b/Doc/conf.py index 5f22340ac434c9..287e0da46eb11c 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -21,6 +21,7 @@ extensions = [ 'audit_events', + 'availability', 'c_annotations', 'glossary_search', 'lexers', diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py new file mode 100644 index 00000000000000..00da7170f83aae --- /dev/null +++ b/Doc/tools/extensions/availability.py @@ -0,0 +1,103 @@ +"""Support for documenting platform availability""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docutils import nodes +from sphinx import addnodes +from sphinx.util import logging +from sphinx.util.docutils import SphinxDirective + +if TYPE_CHECKING: + from sphinx.application import Sphinx + from sphinx.util.typing import ExtensionMetadata + +logger = logging.getLogger('availability') + +# known platform, libc, and threading implementations +KNOWN_PLATFORMS = frozenset({ + 'AIX', 'Android', 'BSD', 'DragonFlyBSD', 'Emscripten', 'FreeBSD', + 'GNU/kFreeBSD', 'iOS', 'Linux', 'macOS', 'NetBSD', 'OpenBSD', + 'POSIX', 'Solaris', 'Unix', 'VxWorks', 'WASI', 'Windows', + # libc + 'BSD libc', 'glibc', 'musl', + # POSIX platforms with pthreads + 'pthreads', +}) # fmt: skip + + +class Availability(SphinxDirective): + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + + def run(self) -> list[nodes.container]: + title = 'Availability' + refnode = addnodes.pending_xref( + title, + nodes.inline(title, title, classes=['xref', 'std', 'std-ref']), + refdoc=self.env.docname, + refdomain='std', + refexplicit=True, + reftarget='availability', + reftype='ref', + refwarn=True, + ) + sep = nodes.Text(': ') + parsed, messages = self.state.inline_text(self.arguments[0], self.lineno) + pnode = nodes.paragraph(title, '', refnode, sep, *parsed, *messages) + self.set_source_info(pnode) + cnode = nodes.container('', pnode, classes=['availability']) + self.set_source_info(cnode) + if self.content: + self.state.nested_parse(self.content, self.content_offset, cnode) + self.parse_platforms() + + return [cnode] + + def parse_platforms(self) -> dict[str, str | bool]: + """Parse platform information from arguments + + Arguments is a comma-separated string of platforms. A platform may + be prefixed with "not " to indicate that a feature is not available. + + Example:: + + .. availability:: Windows, Linux >= 4.2, not WASI + + Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not + parsed into separate tokens. + """ + platforms = {} + for arg in self.arguments[0].rstrip('.').split(','): + arg = arg.strip() + platform, _, version = arg.partition(' >= ') + if platform.startswith('not '): + version = False + platform = platform.removeprefix('not ') + elif not version: + version = True + platforms[platform] = version + + if unknown := set(platforms).difference(KNOWN_PLATFORMS): + logger.warning( + "Unknown platform(s) or syntax '%s' in '.. availability:: %s', " + 'see %s:KNOWN_PLATFORMS for a set of known platforms.', + ' '.join(sorted(unknown)), + self.arguments[0], + __file__, + ) + + return platforms + + +def setup(app: Sphinx) -> ExtensionMetadata: + app.add_directive('availability', Availability) + + return { + 'version': '1.0', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py index b6623a2b8e01f1..bcb8a421e32d09 100644 --- a/Doc/tools/extensions/pyspecific.py +++ b/Doc/tools/extensions/pyspecific.py @@ -24,7 +24,6 @@ from sphinx.domains.changeset import VersionChange, versionlabels, versionlabel_classes from sphinx.domains.python import PyFunction, PyMethod, PyModule from sphinx.locale import _ as sphinx_gettext -from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.writers.text import TextWriter, TextTranslator from sphinx.util.display import status_iterator @@ -108,80 +107,6 @@ def run(self): return [pnode] -# Support for documenting platform availability - -class Availability(SphinxDirective): - - has_content = True - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - - # known platform, libc, and threading implementations - known_platforms = frozenset({ - "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", - "GNU/kFreeBSD", "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", - "Unix", "VxWorks", "WASI", "Windows", "macOS", "iOS", - # libc - "BSD libc", "glibc", "musl", - # POSIX platforms with pthreads - "pthreads", - }) - - def run(self): - availability_ref = ':ref:`Availability `: ' - avail_nodes, avail_msgs = self.state.inline_text( - availability_ref + self.arguments[0], - self.lineno) - pnode = nodes.paragraph(availability_ref + self.arguments[0], - '', *avail_nodes, *avail_msgs) - self.set_source_info(pnode) - cnode = nodes.container("", pnode, classes=["availability"]) - self.set_source_info(cnode) - if self.content: - self.state.nested_parse(self.content, self.content_offset, cnode) - self.parse_platforms() - - return [cnode] - - def parse_platforms(self): - """Parse platform information from arguments - - Arguments is a comma-separated string of platforms. A platform may - be prefixed with "not " to indicate that a feature is not available. - - Example:: - - .. availability:: Windows, Linux >= 4.2, not WASI - - Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not - parsed into separate tokens. - """ - platforms = {} - for arg in self.arguments[0].rstrip(".").split(","): - arg = arg.strip() - platform, _, version = arg.partition(" >= ") - if platform.startswith("not "): - version = False - platform = platform[4:] - elif not version: - version = True - platforms[platform] = version - - unknown = set(platforms).difference(self.known_platforms) - if unknown: - cls = type(self) - logger = logging.getLogger(cls.__qualname__) - logger.warning( - f"Unknown platform(s) or syntax '{' '.join(sorted(unknown))}' " - f"in '.. availability:: {self.arguments[0]}', see " - f"{__file__}:{cls.__qualname__}.known_platforms for a set " - "known platforms." - ) - - return platforms - - # Support for documenting decorators class PyDecoratorMixin(object): @@ -492,7 +417,6 @@ def setup(app): app.add_role('issue', issue_role) app.add_role('gh', gh_issue_role) app.add_directive('impl-detail', ImplementationDetail) - app.add_directive('availability', Availability) app.add_directive('versionadded', PyVersionChange, override=True) app.add_directive('versionchanged', PyVersionChange, override=True) app.add_directive('versionremoved', PyVersionChange, override=True) From 98a0da4f948db0120cbefe3706829b7d3d620ecb Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 8 Oct 2024 03:18:15 +0100 Subject: [PATCH 2/4] lint --- Doc/tools/extensions/availability.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py index 00da7170f83aae..d0f32a3fdb0a36 100644 --- a/Doc/tools/extensions/availability.py +++ b/Doc/tools/extensions/availability.py @@ -46,8 +46,8 @@ def run(self) -> list[nodes.container]: refwarn=True, ) sep = nodes.Text(': ') - parsed, messages = self.state.inline_text(self.arguments[0], self.lineno) - pnode = nodes.paragraph(title, '', refnode, sep, *parsed, *messages) + parsed, msgs = self.state.inline_text(self.arguments[0], self.lineno) + pnode = nodes.paragraph(title, '', refnode, sep, *parsed, *msgs) self.set_source_info(pnode) cnode = nodes.container('', pnode, classes=['availability']) self.set_source_info(cnode) From 648f8cbdb0ac05442b38100b7992d4011689e1c6 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 9 Oct 2024 07:41:32 +0100 Subject: [PATCH 3/4] Conditional pluralisation --- Doc/tools/extensions/availability.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py index d0f32a3fdb0a36..f84133c873bf94 100644 --- a/Doc/tools/extensions/availability.py +++ b/Doc/tools/extensions/availability.py @@ -83,8 +83,9 @@ def parse_platforms(self) -> dict[str, str | bool]: if unknown := set(platforms).difference(KNOWN_PLATFORMS): logger.warning( - "Unknown platform(s) or syntax '%s' in '.. availability:: %s', " + "Unknown platform%s or syntax '%s' in '.. availability:: %s', " 'see %s:KNOWN_PLATFORMS for a set of known platforms.', + 's' if len(platforms) != 1 else '', ' '.join(sorted(unknown)), self.arguments[0], __file__, From c6a81f034279f17a8b79a474de9bf074e291630c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:09:56 +0100 Subject: [PATCH 4/4] Double quotes --- Doc/tools/extensions/availability.py | 77 ++++++++++++++++++---------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/Doc/tools/extensions/availability.py b/Doc/tools/extensions/availability.py index f84133c873bf94..47833fdcb87590 100644 --- a/Doc/tools/extensions/availability.py +++ b/Doc/tools/extensions/availability.py @@ -13,18 +13,39 @@ from sphinx.application import Sphinx from sphinx.util.typing import ExtensionMetadata -logger = logging.getLogger('availability') +logger = logging.getLogger("availability") # known platform, libc, and threading implementations -KNOWN_PLATFORMS = frozenset({ - 'AIX', 'Android', 'BSD', 'DragonFlyBSD', 'Emscripten', 'FreeBSD', - 'GNU/kFreeBSD', 'iOS', 'Linux', 'macOS', 'NetBSD', 'OpenBSD', - 'POSIX', 'Solaris', 'Unix', 'VxWorks', 'WASI', 'Windows', - # libc - 'BSD libc', 'glibc', 'musl', +_PLATFORMS = frozenset({ + "AIX", + "Android", + "BSD", + "DragonFlyBSD", + "Emscripten", + "FreeBSD", + "GNU/kFreeBSD", + "iOS", + "Linux", + "macOS", + "NetBSD", + "OpenBSD", + "POSIX", + "Solaris", + "Unix", + "VxWorks", + "WASI", + "Windows", +}) +_LIBC = frozenset({ + "BSD libc", + "glibc", + "musl", +}) +_THREADING = frozenset({ # POSIX platforms with pthreads - 'pthreads', -}) # fmt: skip + "pthreads", +}) +KNOWN_PLATFORMS = _PLATFORMS | _LIBC | _THREADING class Availability(SphinxDirective): @@ -34,22 +55,22 @@ class Availability(SphinxDirective): final_argument_whitespace = True def run(self) -> list[nodes.container]: - title = 'Availability' + title = "Availability" refnode = addnodes.pending_xref( title, - nodes.inline(title, title, classes=['xref', 'std', 'std-ref']), + nodes.inline(title, title, classes=["xref", "std", "std-ref"]), refdoc=self.env.docname, - refdomain='std', + refdomain="std", refexplicit=True, - reftarget='availability', - reftype='ref', + reftarget="availability", + reftype="ref", refwarn=True, ) - sep = nodes.Text(': ') + sep = nodes.Text(": ") parsed, msgs = self.state.inline_text(self.arguments[0], self.lineno) - pnode = nodes.paragraph(title, '', refnode, sep, *parsed, *msgs) + pnode = nodes.paragraph(title, "", refnode, sep, *parsed, *msgs) self.set_source_info(pnode) - cnode = nodes.container('', pnode, classes=['availability']) + cnode = nodes.container("", pnode, classes=["availability"]) self.set_source_info(cnode) if self.content: self.state.nested_parse(self.content, self.content_offset, cnode) @@ -71,12 +92,12 @@ def parse_platforms(self) -> dict[str, str | bool]: parsed into separate tokens. """ platforms = {} - for arg in self.arguments[0].rstrip('.').split(','): + for arg in self.arguments[0].rstrip(".").split(","): arg = arg.strip() - platform, _, version = arg.partition(' >= ') - if platform.startswith('not '): + platform, _, version = arg.partition(" >= ") + if platform.startswith("not "): version = False - platform = platform.removeprefix('not ') + platform = platform.removeprefix("not ") elif not version: version = True platforms[platform] = version @@ -84,9 +105,9 @@ def parse_platforms(self) -> dict[str, str | bool]: if unknown := set(platforms).difference(KNOWN_PLATFORMS): logger.warning( "Unknown platform%s or syntax '%s' in '.. availability:: %s', " - 'see %s:KNOWN_PLATFORMS for a set of known platforms.', - 's' if len(platforms) != 1 else '', - ' '.join(sorted(unknown)), + "see %s:KNOWN_PLATFORMS for a set of known platforms.", + "s" if len(platforms) != 1 else "", + " ".join(sorted(unknown)), self.arguments[0], __file__, ) @@ -95,10 +116,10 @@ def parse_platforms(self) -> dict[str, str | bool]: def setup(app: Sphinx) -> ExtensionMetadata: - app.add_directive('availability', Availability) + app.add_directive("availability", Availability) return { - 'version': '1.0', - 'parallel_read_safe': True, - 'parallel_write_safe': True, + "version": "1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, }