Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.12] GH-121970: Extract availability into a new extension (GH-125082) #125238

Merged
merged 1 commit into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

extensions = [
'audit_events',
'availability',
'c_annotations',
'glossary_search',
'lexers',
Expand Down
123 changes: 123 additions & 0 deletions Doc/tools/extensions/availability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""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
_PLATFORMS = frozenset({
"AIX",
"Android",
"BSD",
"DragonFlyBSD",
"Emscripten",
"FreeBSD",
"Linux",
"macOS",
"NetBSD",
"OpenBSD",
"POSIX",
"Solaris",
"Unix",
"VxWorks",
"WASI",
"Windows",
})
_LIBC = frozenset({
"BSD libc",
"glibc",
"musl",
})
_THREADING = frozenset({
# POSIX platforms with pthreads
"pthreads",
})
KNOWN_PLATFORMS = _PLATFORMS | _LIBC | _THREADING


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, 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)
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.",
"s" if len(platforms) != 1 else "",
" ".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,
}
76 changes: 0 additions & 76 deletions Doc/tools/extensions/pyspecific.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
"Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", "Unix", "VxWorks",
"WASI", "Windows", "macOS",
# libc
"BSD libc", "glibc", "musl",
# POSIX platforms with pthreads
"pthreads",
})

def run(self):
availability_ref = ':ref:`Availability <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 Emscripten, 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):
Expand Down Expand Up @@ -473,7 +398,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('deprecated-removed', DeprecatedRemoved)
app.add_builder(PydocTopicsBuilder)
app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature)
Expand Down
Loading