• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

python-coincidence / coverage_pyver_pragma / 4700973029

pending completion
4700973029

push

github

Dominic Davis-Foster
Bump version v0.3.1 -> v0.3.2

1 of 1 new or added line in 1 file covered. (100.0%)

136 of 143 relevant lines covered (95.1%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.27
/coverage_pyver_pragma/grammar.py
1
#!/usr/bin/env python3
2
#
3
#  grammar.py
4
r"""
1✔
5
.. versionadded:: 0.2.0
6

7
As with ``coverage.py``, lines are marked with comments in the form::
8

9
        # pragma: no cover
10

11
With ``coverage_pyver_pragma``, the comment may be followed with an expression enclosed in parentheses::
12

13
        # pragma: no cover (<=py38 and !Windows)
14

15
Each expression consists of one or more tags
16
(:py:data:`VERSION_TAG`, :py:data:`PLATFORM_TAG` or :py:data:`IMPLEMENTATION_TAG`).
17
The tags can be joined with the keywords ``AND``, ``OR`` and ``NOT``, with the exclamation mark ``!`` implying ``NOT``.
18
Parentheses can be used to group sub expressions.
19
A series of tags without keywords between them are evaluated with ``AND``.
20

21
.. py:data:: VERSION_TAG
22

23
A ``VERSION_TAG`` comprises an optional comparator (one of ``<=``, ``<``, ``>=``, ``>``),
24
a version specifier in the form ``pyXX``, and an optional ``+`` to indicate ``>=``.
25

26

27
:bold-title:`Example:`
28

29
.. parsed-literal::
30

31
        <=py36
32
        >=py37
33
        <py38
34
        >py27
35
        py34+  # equivalent to >=py34
36

37

38
.. py:data::  PLATFORM_TAG
39

40
A ``PLATFORM_TAG`` comprises a single word which will be compared (ignoring case)
41
with the output of :func:`platform.system`.
42

43

44
:bold-title:`Example:`
45

46
.. parsed-literal::
47

48
        Windows
49
        Linux
50
        Darwin  # macOS
51
        Java
52

53
If the current platform cannot be determined all strings are treated as :py:obj:`True`.
54

55
.. raw:: latex
56

57
        \clearpage
58

59
.. py:data:: IMPLEMENTATION_TAG
60

61
An ``IMPLEMENTATION_TAG`` comprises a single word which will be compared (ignoring case)
62
with the output of :func:`platform.python_implementation`.
63

64

65
:bold-title:`Example:`
66

67
.. parsed-literal::
68

69
        CPython
70
        PyPy
71
        IronPython
72
        Jython
73

74

75
Examples
76
-----------
77

78
Ignore if the Python version is less than or equal to 3.7::
79

80
        # pragma: no cover (<=py37)
81

82
Ignore if running on Python 3.9::
83

84
        # pragma: no cover (py39)
85

86
Ignore if the Python version is greater than 3.6 and it's not running on PyPy::
87

88
        # pragma: no cover (>py36 and !PyPy)
89

90
Ignore if the Python version is less than 3.8 and it's running on Windows::
91

92
        # pragma: no cover (Windows and <py38)
93

94
Ignore when not running on macOS (Darwin)::
95

96
        # pragma: no cover (!Darwin)
97

98
Ignore when not running on CPython::
99

100
        # pragma: no cover (!CPython)
101

102
.. raw:: latex
103

104
        \clearpage
105

106
API Reference
107
----------------
108

109
.. automodulesumm:: coverage_pyver_pragma.grammar
110

111
.. autovariable:: GRAMMAR
112
        :no-value:
113

114
"""  # noqa: D400
115
#
116
#  Copyright © 2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
117
#
118
#  Permission is hereby granted, free of charge, to any person obtaining a copy
119
#  of this software and associated documentation files (the "Software"), to deal
120
#  in the Software without restriction, including without limitation the rights
121
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
122
#  copies of the Software, and to permit persons to whom the Software is
123
#  furnished to do so, subject to the following conditions:
124
#
125
#  The above copyright notice and this permission notice shall be included in all
126
#  copies or substantial portions of the Software.
127
#
128
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
129
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
130
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
131
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
132
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
133
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
134
#  OR OTHER DEALINGS IN THE SOFTWARE.
135
#
136

137
# stdlib
138
import os
1✔
139
import platform
1✔
140
import sys
1✔
141

142
# 3rd party
143
import packaging.specifiers
1✔
144
from domdf_python_tools.doctools import prettify_docstrings
1✔
145
from domdf_python_tools.stringlist import DelimitedList
1✔
146
from pyparsing import (
1✔
147
                CaselessKeyword,
148
                CaselessLiteral,
149
                Combine,
150
                Group,
151
                Literal,
152
                OneOrMore,
153
                Optional,
154
                ParserElement,
155
                ParseResults,
156
                Word,
157
                infixNotation,
158
                nums,
159
                oneOf,
160
                opAssoc
161
                )
162

163
__all__ = (
1✔
164
                "ImplementationTag",
165
                "LogicalAND",
166
                "LogicalNOT",
167
                "LogicalOR",
168
                "LogicalOp",
169
                "PlatformTag",
170
                "VersionTag",
171
                "VERSION_TAG",
172
                "PLATFORM_TAG",
173
                "IMPLEMENTATION_TAG",
174
                "GRAMMAR",
175
                )
176

177
# This ensures coverage.py records the correct coverage for these modules
178
# when they are under test
179

180
# pylint: disable=loop-global-usage,dotted-import-in-loop
181
for module in [m for m in sys.modules if m.startswith("domdf_python_tools")]:  # pragma: no cover (macOS)
1✔
182
        if module in sys.modules:
1✔
183
                del sys.modules[module]
1✔
184
# pylint: enable=loop-global-usage,dotted-import-in-loop
185

186
PYTHON_VERSION = os.environ.get("COV_PYTHON_VERSION", '.'.join(platform.python_version_tuple()[:2]))
1✔
187
PLATFORM = os.environ.get("COV_PLATFORM", platform.system()).casefold()
1✔
188
PYTHON_IMPLEMENTATION = os.environ.get("COV_PYTHON_IMPLEMENTATION", platform.python_implementation()).casefold()
1✔
189

190

191
@prettify_docstrings
1✔
192
class VersionTag(packaging.specifiers.SpecifierSet):
1✔
193
        """
194
        Represents a ``VERSION_TAG`` in the expression grammar.
195

196
        A ``VERSION_TAG`` comprises an optional comparator (one of ``<=``, ``<``, ``>=``, ``>``),
197
        a version specifier in the form ``pyXX``, and an optional ``+`` to indicate ``>=``.
198

199
        :bold-title:`Examples:`
200

201
        .. parsed-literal::
202

203
                <=py36
204
                >=py37
205
                <py38
206
                >py27
207
                py34+
208

209
        :param tokens:
210
        """
211

212
        def __init__(self, tokens: ParseResults):
1✔
213
                token_dict = dict(tokens["version"])
1✔
214

215
                version = token_dict["version"][2:]
1✔
216

217
                if "plus" in token_dict and "comparator" in token_dict:
1✔
218
                        raise SyntaxError("Cannot combine a comparator with the plus sign.")
1✔
219

220
                elif "plus" in token_dict:
1✔
221
                        super().__init__(f">={version[0]}.{version[1:]}")
1✔
222

223
                elif "comparator" in token_dict:
1✔
224
                        comparator = token_dict["comparator"]
1✔
225
                        super().__init__(f"{comparator}{version[0]}.{version[1:]}")
1✔
226

227
                else:
228
                        super().__init__(f"=={version[0]}.{version[1:]}")
1✔
229

230
        def __repr__(self) -> str:  # pragma: no cover
1✔
231
                return f"<{self.__class__.__name__}({str(self)!r})>"
×
232

233
        def __bool__(self) -> bool:
1✔
234
                return PYTHON_VERSION in self
1✔
235

236

237
@prettify_docstrings
1✔
238
class PlatformTag(str):
1✔
239
        """
240
        Represents a ``PLATFORM_TAG`` in the expression grammar.
241

242
        A ``PLATFORM_TAG`` comprises a single word which will be compared (ignoring case)
243
        with the output of :func:`platform.system`.
244

245
        :bold-title:`Examples:`
246

247
        .. parsed-literal::
248

249
                Windows
250
                Linux
251
                Darwin  # macOS
252
                Java
253

254
        .. latex:clearpage::
255

256
        If the current platform cannot be determined all strings are treated as :py:obj:`True`.
257

258
        :param tokens:
259
        """
260

261
        __slots__ = ()
1✔
262

263
        def __new__(cls, tokens: ParseResults) -> "PlatformTag":  # noqa: D102
1✔
264
                return super().__new__(cls, str(tokens["platform"]))
1✔
265

266
        def __repr__(self) -> str:  # pragma: no cover
1✔
267
                return f"<{self.__class__.__name__}({str(self)!r})>"
×
268

269
        def __bool__(self) -> bool:
1✔
270
                if not PLATFORM:  # pragma: no cover
1✔
271
                        return True
×
272

273
                return PLATFORM == self.casefold()
1✔
274

275

276
@prettify_docstrings
1✔
277
class ImplementationTag(str):
1✔
278
        """
279
        Represents an ``IMPLEMENTATION_TAG`` in the expression grammar.
280

281
        An ``IMPLEMENTATION_TAG`` comprises a single word which will be compared (ignoring case)
282
        with the output of :func:`platform.python_implementation`.
283

284
        :bold-title:`Examples:`
285

286
        .. parsed-literal::
287

288
                CPython
289
                PyPy
290
                IronPython
291
                Jython
292

293
        :param tokens:
294

295
        .. latex:vspace:: -10px
296
        """
297

298
        __slots__ = ()
1✔
299

300
        def __new__(cls, tokens: ParseResults) -> "ImplementationTag":  # noqa: D102
1✔
301
                return super().__new__(cls, str(tokens["implementation"]))
1✔
302

303
        def __repr__(self) -> str:  # pragma: no cover
1✔
304
                return f"<{self.__class__.__name__}({str(self)!r})>"
×
305

306
        def __bool__(self) -> bool:
1✔
307
                return PYTHON_IMPLEMENTATION == self.casefold()
1✔
308

309

310
@prettify_docstrings
1✔
311
class LogicalOp:
1✔
312
        """
313
        Represents a logical operator (``AND``, ``OR``, and ``NOT / !``).
314

315
        :param tokens:
316
        """
317

318
        def __init__(self, tokens: ParseResults):
1✔
319
                self.tokens = DelimitedList(tokens[0])
1✔
320

321
        def __format__(self, format_spec: str) -> str:
1✔
322
                return self.tokens.__format__(format_spec)
×
323

324
        def __getitem__(self, item):
1✔
325
                return self.tokens[item]
1✔
326

327
        def __str__(self) -> str:
1✔
328
                return f"[{self:, }]"
×
329

330
        def __repr__(self) -> str:  # pragma: no cover
1✔
331
                return f"<{self.__class__.__name__}({self})>"
×
332

333

334
@prettify_docstrings
1✔
335
class LogicalAND(LogicalOp):
1✔
336
        """
337
        Represents the ``AND`` logical operator.
338

339
        :param tokens:
340
        """
341

342
        def __bool__(self) -> bool:
1✔
343
                return bool(self[0]) and bool(self[2])
1✔
344

345

346
@prettify_docstrings
1✔
347
class LogicalOR(LogicalOp):
1✔
348
        """
349
        Represents the ``OR`` logical operator.
350

351
        :param tokens:
352
        """
353

354
        def __bool__(self) -> bool:
1✔
355
                return bool(self[0]) or bool(self[2])
1✔
356

357

358
@prettify_docstrings
1✔
359
class LogicalNOT(LogicalOp):
1✔
360
        """
361
        Represents the ``NOT / !`` logical operator.
362

363
        :param tokens:
364
        """
365

366
        def __bool__(self) -> bool:
1✔
367
                return not bool(self[1])
1✔
368

369

370
# Logical operators
371
AND = CaselessKeyword("and")
1✔
372
OR = CaselessKeyword("or")
1✔
373
NOT = CaselessKeyword("not") | Literal('!')
1✔
374

375
# Grammar comprises (case insensitive):
376
# Python versions (<=pyXXX, pyXXX+)
377

378
PLUS = Literal('+').setResultsName("plus")
1✔
379

380
LESS_THAN_EQUAL = "<="
1✔
381
LESS_THAN = '<'
1✔
382
GREATER_THAN_EQUAL = ">="
1✔
383
GREATER_THAN = '>'
1✔
384

385
OPS = [LESS_THAN, LESS_THAN_EQUAL, GREATER_THAN, GREATER_THAN_EQUAL]  # pylint: disable=use-tuple-over-list
1✔
386
COMPARATOR = Optional(oneOf(' '.join(OPS))).setResultsName("comparator")
1✔
387

388
VERSION = Combine(CaselessLiteral("py") + Word(nums)).setResultsName("version")
1✔
389
VERSION_TAG = Group(COMPARATOR + VERSION + Optional(PLUS)).setResultsName("version")
1✔
390
VERSION_TAG.setParseAction(VersionTag)
1✔
391

392
# Platforms (Windows, !Linux)
393
# TODO: other platforms
394

395
WINDOWS = CaselessLiteral("windows")
1✔
396
LINUX = CaselessLiteral("linux")
1✔
397
DARWIN = CaselessLiteral("darwin")
1✔
398
JAVA = CaselessLiteral("java")
1✔
399

400
PLATFORM_TAG = (WINDOWS | LINUX | DARWIN | JAVA).setResultsName("platform")
1✔
401
PLATFORM_TAG.setParseAction(PlatformTag)
1✔
402

403
# Implementations (CPython, !PyPy)
404
# TODO: other python implementations
405

406
CPYTHON = CaselessLiteral("cpython")
1✔
407
PYPY = CaselessLiteral("pypy")
1✔
408
JYTHON = CaselessLiteral("jython")
1✔
409
IRONPYTHON = CaselessLiteral("ironpython")
1✔
410

411
IMPLEMENTATION_TAG = (CPYTHON | PYPY | JYTHON | IRONPYTHON).setResultsName("implementation")
1✔
412
IMPLEMENTATION_TAG.setParseAction(ImplementationTag)
1✔
413

414
ELEMENTS = VERSION_TAG | PLATFORM_TAG | IMPLEMENTATION_TAG
1✔
415

416
GRAMMAR: ParserElement = OneOrMore(
1✔
417
                infixNotation(
418
                                ELEMENTS,
419
                                [
420
                                                (NOT, 1, opAssoc.RIGHT, LogicalNOT),
421
                                                (AND, 2, opAssoc.LEFT, LogicalAND),
422
                                                (OR, 2, opAssoc.LEFT, LogicalOR),
423
                                                ]
424
                                )
425
                )
426
"""
1✔
427
The :mod:`coverage_pyver_pragma` expression grammar.
428

429
This can be used to parse an expression outside of the coverage context.
430
"""
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc