| """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, |
| } |