|  | 
| 24 | 24 | from sphinx.domains.changeset import VersionChange, versionlabels, versionlabel_classes | 
| 25 | 25 | from sphinx.domains.python import PyFunction, PyMethod, PyModule | 
| 26 | 26 | from sphinx.locale import _ as sphinx_gettext | 
|  | 27 | +from sphinx.util import logging | 
| 27 | 28 | from sphinx.util.docutils import SphinxDirective | 
| 28 | 29 | from sphinx.writers.text import TextWriter, TextTranslator | 
| 29 | 30 | from sphinx.util.display import status_iterator | 
| @@ -107,6 +108,80 @@ def run(self): | 
| 107 | 108 |         return [pnode] | 
| 108 | 109 | 
 | 
| 109 | 110 | 
 | 
|  | 111 | +# Support for documenting platform availability | 
|  | 112 | + | 
|  | 113 | +class Availability(SphinxDirective): | 
|  | 114 | + | 
|  | 115 | +    has_content = True | 
|  | 116 | +    required_arguments = 1 | 
|  | 117 | +    optional_arguments = 0 | 
|  | 118 | +    final_argument_whitespace = True | 
|  | 119 | + | 
|  | 120 | +    # known platform, libc, and threading implementations | 
|  | 121 | +    known_platforms = frozenset({ | 
|  | 122 | +        "AIX", "Android", "BSD", "DragonFlyBSD", "Emscripten", "FreeBSD", | 
|  | 123 | +        "GNU/kFreeBSD", "Linux", "NetBSD", "OpenBSD", "POSIX", "Solaris", | 
|  | 124 | +        "Unix", "VxWorks", "WASI", "Windows", "macOS", "iOS", | 
|  | 125 | +        # libc | 
|  | 126 | +        "BSD libc", "glibc", "musl", | 
|  | 127 | +        # POSIX platforms with pthreads | 
|  | 128 | +        "pthreads", | 
|  | 129 | +    }) | 
|  | 130 | + | 
|  | 131 | +    def run(self): | 
|  | 132 | +        availability_ref = ':ref:`Availability <availability>`: ' | 
|  | 133 | +        avail_nodes, avail_msgs = self.state.inline_text( | 
|  | 134 | +            availability_ref + self.arguments[0], | 
|  | 135 | +            self.lineno) | 
|  | 136 | +        pnode = nodes.paragraph(availability_ref + self.arguments[0], | 
|  | 137 | +                                '', *avail_nodes, *avail_msgs) | 
|  | 138 | +        self.set_source_info(pnode) | 
|  | 139 | +        cnode = nodes.container("", pnode, classes=["availability"]) | 
|  | 140 | +        self.set_source_info(cnode) | 
|  | 141 | +        if self.content: | 
|  | 142 | +            self.state.nested_parse(self.content, self.content_offset, cnode) | 
|  | 143 | +        self.parse_platforms() | 
|  | 144 | + | 
|  | 145 | +        return [cnode] | 
|  | 146 | + | 
|  | 147 | +    def parse_platforms(self): | 
|  | 148 | +        """Parse platform information from arguments | 
|  | 149 | +
 | 
|  | 150 | +        Arguments is a comma-separated string of platforms. A platform may | 
|  | 151 | +        be prefixed with "not " to indicate that a feature is not available. | 
|  | 152 | +
 | 
|  | 153 | +        Example:: | 
|  | 154 | +
 | 
|  | 155 | +           .. availability:: Windows, Linux >= 4.2, not WASI | 
|  | 156 | +
 | 
|  | 157 | +        Arguments like "Linux >= 3.17 with glibc >= 2.27" are currently not | 
|  | 158 | +        parsed into separate tokens. | 
|  | 159 | +        """ | 
|  | 160 | +        platforms = {} | 
|  | 161 | +        for arg in self.arguments[0].rstrip(".").split(","): | 
|  | 162 | +            arg = arg.strip() | 
|  | 163 | +            platform, _, version = arg.partition(" >= ") | 
|  | 164 | +            if platform.startswith("not "): | 
|  | 165 | +                version = False | 
|  | 166 | +                platform = platform[4:] | 
|  | 167 | +            elif not version: | 
|  | 168 | +                version = True | 
|  | 169 | +            platforms[platform] = version | 
|  | 170 | + | 
|  | 171 | +        unknown = set(platforms).difference(self.known_platforms) | 
|  | 172 | +        if unknown: | 
|  | 173 | +            cls = type(self) | 
|  | 174 | +            logger = logging.getLogger(cls.__qualname__) | 
|  | 175 | +            logger.warning( | 
|  | 176 | +                f"Unknown platform(s) or syntax '{' '.join(sorted(unknown))}' " | 
|  | 177 | +                f"in '.. availability:: {self.arguments[0]}', see " | 
|  | 178 | +                f"{__file__}:{cls.__qualname__}.known_platforms for a set " | 
|  | 179 | +                "known platforms." | 
|  | 180 | +            ) | 
|  | 181 | + | 
|  | 182 | +        return platforms | 
|  | 183 | + | 
|  | 184 | + | 
| 110 | 185 | # Support for documenting decorators | 
| 111 | 186 | 
 | 
| 112 | 187 | class PyDecoratorMixin(object): | 
| @@ -398,6 +473,7 @@ def setup(app): | 
| 398 | 473 |     app.add_role('issue', issue_role) | 
| 399 | 474 |     app.add_role('gh', gh_issue_role) | 
| 400 | 475 |     app.add_directive('impl-detail', ImplementationDetail) | 
|  | 476 | +    app.add_directive('availability', Availability) | 
| 401 | 477 |     app.add_directive('deprecated-removed', DeprecatedRemoved) | 
| 402 | 478 |     app.add_builder(PydocTopicsBuilder) | 
| 403 | 479 |     app.add_object_type('opcode', 'opcode', '%s (opcode)', parse_opcode_signature) | 
|  | 
0 commit comments