1"""Support for documenting platform availability""" 2 3from __future__ import annotations 4 5from typing import TYPE_CHECKING 6 7from docutils import nodes 8from sphinx import addnodes 9from sphinx.util import logging 10from sphinx.util.docutils import SphinxDirective 11 12if TYPE_CHECKING: 13 from sphinx.application import Sphinx 14 from sphinx.util.typing import ExtensionMetadata 15 16logger = logging.getLogger("availability") 17 18# known platform, libc, and threading implementations 19_PLATFORMS = frozenset({ 20 "AIX", 21 "Android", 22 "BSD", 23 "DragonFlyBSD", 24 "Emscripten", 25 "FreeBSD", 26 "GNU/kFreeBSD", 27 "iOS", 28 "Linux", 29 "macOS", 30 "NetBSD", 31 "OpenBSD", 32 "POSIX", 33 "Solaris", 34 "Unix", 35 "VxWorks", 36 "WASI", 37 "Windows", 38}) 39_LIBC = frozenset({ 40 "BSD libc", 41 "glibc", 42 "musl", 43}) 44_THREADING = frozenset({ 45 # POSIX platforms with pthreads 46 "pthreads", 47}) 48KNOWN_PLATFORMS = _PLATFORMS | _LIBC | _THREADING 49 50 51class Availability(SphinxDirective): 52 has_content = True 53 required_arguments = 1 54 optional_arguments = 0 55 final_argument_whitespace = True 56 57 def run(self) -> list[nodes.container]: 58 title = "Availability" 59 refnode = addnodes.pending_xref( 60 title, 61 nodes.inline(title, title, classes=["xref", "std", "std-ref"]), 62 refdoc=self.env.docname, 63 refdomain="std", 64 refexplicit=True, 65 reftarget="availability", 66 reftype="ref", 67 refwarn=True, 68 ) 69 sep = nodes.Text(": ") 70 parsed, msgs = self.state.inline_text(self.arguments[0], self.lineno) 71 pnode = nodes.paragraph(title, "", refnode, sep, *parsed, *msgs) 72 self.set_source_info(pnode) 73 cnode = nodes.container("", pnode, classes=["availability"]) 74 self.set_source_info(cnode) 75 if self.content: 76 self.state.nested_parse(self.content, self.content_offset, cnode) 77 self.parse_platforms() 78 79 return [cnode] 80 81 def parse_platforms(self) -> dict[str, str | bool]: 82 """Parse platform information from arguments 83 84 Arguments is a comma-separated string of platforms. A platform may 85 be prefixed with "not " to indicate that a feature is not available. 86 87 Example:: 88 89 .. availability:: Windows, Linux >= 4.2, not WASI 90 91 Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not 92 parsed into separate tokens. 93 """ 94 platforms = {} 95 for arg in self.arguments[0].rstrip(".").split(","): 96 arg = arg.strip() 97 platform, _, version = arg.partition(" >= ") 98 if platform.startswith("not "): 99 version = False 100 platform = platform.removeprefix("not ") 101 elif not version: 102 version = True 103 platforms[platform] = version 104 105 if unknown := set(platforms).difference(KNOWN_PLATFORMS): 106 logger.warning( 107 "Unknown platform%s or syntax '%s' in '.. availability:: %s', " 108 "see %s:KNOWN_PLATFORMS for a set of known platforms.", 109 "s" if len(platforms) != 1 else "", 110 " ".join(sorted(unknown)), 111 self.arguments[0], 112 __file__, 113 ) 114 115 return platforms 116 117 118def setup(app: Sphinx) -> ExtensionMetadata: 119 app.add_directive("availability", Availability) 120 121 return { 122 "version": "1.0", 123 "parallel_read_safe": True, 124 "parallel_write_safe": True, 125 } 126