"""Support for documenting platform availability""" from __future__ import annotations from typing import TYPE_CHECKING from docutils import nodes from sphinx import addnodes from sphinx.locale import _ as sphinx_gettext 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", "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", }) 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 = sphinx_gettext("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, }