coverage 7.6.7__cp311-cp311-win_amd64.whl → 7.11.1__cp311-cp311-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coverage/__init__.py +2 -0
- coverage/__main__.py +2 -0
- coverage/annotate.py +1 -2
- coverage/bytecode.py +177 -3
- coverage/cmdline.py +329 -154
- coverage/collector.py +31 -42
- coverage/config.py +166 -62
- coverage/context.py +4 -5
- coverage/control.py +164 -85
- coverage/core.py +70 -33
- coverage/data.py +3 -4
- coverage/debug.py +112 -56
- coverage/disposition.py +1 -0
- coverage/env.py +65 -55
- coverage/exceptions.py +35 -7
- coverage/execfile.py +18 -13
- coverage/files.py +23 -18
- coverage/html.py +134 -88
- coverage/htmlfiles/style.css +42 -2
- coverage/htmlfiles/style.scss +65 -1
- coverage/inorout.py +61 -44
- coverage/jsonreport.py +17 -8
- coverage/lcovreport.py +16 -20
- coverage/misc.py +50 -46
- coverage/multiproc.py +12 -7
- coverage/numbits.py +3 -4
- coverage/parser.py +193 -269
- coverage/patch.py +166 -0
- coverage/phystokens.py +24 -25
- coverage/plugin.py +13 -13
- coverage/plugin_support.py +36 -35
- coverage/python.py +9 -13
- coverage/pytracer.py +40 -33
- coverage/regions.py +2 -1
- coverage/report.py +59 -43
- coverage/report_core.py +6 -9
- coverage/results.py +118 -66
- coverage/sqldata.py +260 -210
- coverage/sqlitedb.py +33 -25
- coverage/sysmon.py +195 -157
- coverage/templite.py +6 -6
- coverage/tomlconfig.py +12 -12
- coverage/tracer.cp311-win_amd64.pyd +0 -0
- coverage/tracer.pyi +2 -0
- coverage/types.py +25 -22
- coverage/version.py +3 -18
- coverage/xmlreport.py +16 -13
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/METADATA +40 -18
- coverage-7.11.1.dist-info/RECORD +59 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/WHEEL +1 -1
- coverage-7.6.7.dist-info/RECORD +0 -58
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/entry_points.txt +0 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info/licenses}/LICENSE.txt +0 -0
- {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/top_level.txt +0 -0
coverage/env.py
CHANGED
|
@@ -8,9 +8,8 @@ from __future__ import annotations
|
|
|
8
8
|
import os
|
|
9
9
|
import platform
|
|
10
10
|
import sys
|
|
11
|
-
|
|
12
|
-
from typing import Any
|
|
13
11
|
from collections.abc import Iterable
|
|
12
|
+
from typing import Any, Final
|
|
14
13
|
|
|
15
14
|
# debug_info() at the bottom wants to show all the globals, but not imports.
|
|
16
15
|
# Grab the global names here to know which names to not show. Nothing defined
|
|
@@ -22,11 +21,11 @@ _UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"]
|
|
|
22
21
|
# Operating systems.
|
|
23
22
|
WINDOWS = sys.platform == "win32"
|
|
24
23
|
LINUX = sys.platform.startswith("linux")
|
|
25
|
-
|
|
24
|
+
MACOS = sys.platform == "darwin"
|
|
26
25
|
|
|
27
26
|
# Python implementations.
|
|
28
|
-
CPYTHON = (platform.python_implementation() == "CPython")
|
|
29
|
-
PYPY = (platform.python_implementation() == "PyPy")
|
|
27
|
+
CPYTHON = (platform.python_implementation() == "CPython") # fmt: skip
|
|
28
|
+
PYPY = (platform.python_implementation() == "PyPy") # fmt: skip
|
|
30
29
|
|
|
31
30
|
# Python versions. We amend version_info with one more value, a zero if an
|
|
32
31
|
# official version, or 1 if built from source beyond an official version.
|
|
@@ -36,69 +35,80 @@ PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)
|
|
|
36
35
|
|
|
37
36
|
if PYPY:
|
|
38
37
|
# Minimum now is 7.3.16
|
|
39
|
-
PYPYVERSION = sys.pypy_version_info
|
|
38
|
+
PYPYVERSION = tuple(sys.pypy_version_info) # type: ignore[attr-defined]
|
|
40
39
|
else:
|
|
41
40
|
PYPYVERSION = (0,)
|
|
42
41
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
"""Flags indicating this Python's behavior."""
|
|
46
|
-
|
|
47
|
-
# Does Python conform to PEP626, Precise line numbers for debugging and other tools.
|
|
48
|
-
# https://www.python.org/dev/peps/pep-0626
|
|
49
|
-
pep626 = (PYVERSION > (3, 10, 0, "alpha", 4))
|
|
50
|
-
|
|
51
|
-
# Is "if __debug__" optimized away?
|
|
52
|
-
optimize_if_debug = not pep626
|
|
53
|
-
|
|
54
|
-
# Is "if not __debug__" optimized away? The exact details have changed
|
|
55
|
-
# across versions.
|
|
56
|
-
if pep626:
|
|
57
|
-
optimize_if_not_debug = 1
|
|
58
|
-
else:
|
|
59
|
-
optimize_if_not_debug = 2
|
|
60
|
-
|
|
61
|
-
# 3.7 changed how functions with only docstrings are numbered.
|
|
62
|
-
docstring_only_function = (not PYPY) and (PYVERSION <= (3, 10))
|
|
63
|
-
|
|
64
|
-
# Lines after break/continue/return/raise are no longer compiled into the
|
|
65
|
-
# bytecode. They used to be marked as missing, now they aren't executable.
|
|
66
|
-
omit_after_jump = pep626 or PYPY
|
|
42
|
+
# Do we have a GIL?
|
|
43
|
+
GIL = getattr(sys, "_is_gil_enabled", lambda: True)()
|
|
67
44
|
|
|
68
|
-
|
|
69
|
-
|
|
45
|
+
# Do we ship compiled coveragepy wheels for this version?
|
|
46
|
+
SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14)
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
|
|
48
|
+
# Should we default to sys.monitoring?
|
|
49
|
+
SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
|
|
73
50
|
|
|
74
|
-
# Modules used to have firstlineno equal to the line number of the first
|
|
75
|
-
# real line of code. Now they always start at 1.
|
|
76
|
-
module_firstline_1 = pep626
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# When leaving a with-block, do we visit the with-line again for the exit?
|
|
82
|
-
exit_through_with = (PYVERSION >= (3, 10, 0, "beta"))
|
|
52
|
+
# Python behavior.
|
|
53
|
+
class PYBEHAVIOR:
|
|
54
|
+
"""Flags indicating this Python's behavior."""
|
|
83
55
|
|
|
84
56
|
# When leaving a with-block, do we visit the with-line exactly,
|
|
85
|
-
# or the inner-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
|
|
57
|
+
# or the context managers in inner-out order?
|
|
58
|
+
#
|
|
59
|
+
# mwith.py:
|
|
60
|
+
# with (
|
|
61
|
+
# open("/tmp/one", "w") as f2,
|
|
62
|
+
# open("/tmp/two", "w") as f3,
|
|
63
|
+
# open("/tmp/three", "w") as f4,
|
|
64
|
+
# ):
|
|
65
|
+
# print("hello 6")
|
|
66
|
+
#
|
|
67
|
+
# % python3.11 -m trace -t mwith.py | grep mwith
|
|
68
|
+
# --- modulename: mwith, funcname: <module>
|
|
69
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
70
|
+
# mwith.py(1): with (
|
|
71
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
72
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
73
|
+
# mwith.py(1): with (
|
|
74
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
75
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
76
|
+
# mwith.py(1): with (
|
|
77
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
78
|
+
# mwith.py(6): print("hello 6")
|
|
79
|
+
# mwith.py(1): with (
|
|
80
|
+
#
|
|
81
|
+
# % python3.12 -m trace -t mwith.py | grep mwith
|
|
82
|
+
# --- modulename: mwith, funcname: <module>
|
|
83
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
84
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
85
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
86
|
+
# mwith.py(6): print("hello 6")
|
|
87
|
+
# mwith.py(4): open("/tmp/three", "w") as f4,
|
|
88
|
+
# mwith.py(3): open("/tmp/two", "w") as f3,
|
|
89
|
+
# mwith.py(2): open("/tmp/one", "w") as f2,
|
|
90
|
+
|
|
91
|
+
exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip
|
|
92
|
+
|
|
93
|
+
# f-strings are parsed as code, pep 701
|
|
94
|
+
fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip
|
|
93
95
|
|
|
94
96
|
# PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/
|
|
95
|
-
pep669 = bool(getattr(sys, "monitoring", None))
|
|
97
|
+
pep669: Final[bool] = bool(getattr(sys, "monitoring", None))
|
|
96
98
|
|
|
97
99
|
# Where does frame.f_lasti point when yielding from a generator?
|
|
98
100
|
# It used to point at the YIELD, in 3.13 it points at the RESUME,
|
|
99
101
|
# then it went back to the YIELD.
|
|
100
102
|
# https://github.com/python/cpython/issues/113728
|
|
101
|
-
lasti_is_yield = (PYVERSION[:2] != (3, 13))
|
|
103
|
+
lasti_is_yield = (PYVERSION[:2] != (3, 13)) # fmt: skip
|
|
104
|
+
|
|
105
|
+
# PEP649 and PEP749: Deferred annotations
|
|
106
|
+
deferred_annotations = (PYVERSION >= (3, 14)) # fmt: skip
|
|
107
|
+
|
|
108
|
+
# Does sys.monitoring support BRANCH_RIGHT and BRANCH_LEFT? The names
|
|
109
|
+
# were added in early 3.14 alphas, but didn't work entirely correctly until
|
|
110
|
+
# after 3.14.0a5.
|
|
111
|
+
branch_right_left = pep669 and (PYVERSION > (3, 14, 0, "alpha", 5, 0))
|
|
102
112
|
|
|
103
113
|
|
|
104
114
|
# Coverage.py specifics, about testing scenarios. See tests/testenv.py also.
|
|
@@ -115,11 +125,11 @@ TESTING = os.getenv("COVERAGE_TESTING") == "True"
|
|
|
115
125
|
def debug_info() -> Iterable[tuple[str, Any]]:
|
|
116
126
|
"""Return a list of (name, value) pairs for printing debug information."""
|
|
117
127
|
info = [
|
|
118
|
-
(name, value)
|
|
128
|
+
(name, value)
|
|
129
|
+
for name, value in globals().items()
|
|
119
130
|
if not name.startswith("_") and name not in _UNINTERESTING_GLOBALS
|
|
120
131
|
]
|
|
121
132
|
info += [
|
|
122
|
-
(name, value) for name, value in PYBEHAVIOR.__dict__.items()
|
|
123
|
-
if not name.startswith("_")
|
|
133
|
+
(name, value) for name, value in PYBEHAVIOR.__dict__.items() if not name.startswith("_")
|
|
124
134
|
]
|
|
125
135
|
return sorted(info)
|
coverage/exceptions.py
CHANGED
|
@@ -5,47 +5,73 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
"""The base-base of all Coverage exceptions."""
|
|
10
|
-
pass
|
|
8
|
+
from typing import Any
|
|
11
9
|
|
|
12
10
|
|
|
13
|
-
class CoverageException(
|
|
11
|
+
class CoverageException(Exception):
|
|
14
12
|
"""The base class of all exceptions raised by Coverage.py."""
|
|
15
|
-
pass
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
*args: Any,
|
|
17
|
+
slug: str | None = None,
|
|
18
|
+
skip_tests: bool = False,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Create an exception.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
slug: A short string identifying the exception, will be used for
|
|
24
|
+
linking to documentation.
|
|
25
|
+
skip_tests: If True, raising this exception will skip the test it
|
|
26
|
+
is raised in. This is used for shutting off large numbers of
|
|
27
|
+
tests that we know will not succeed because of a configuration
|
|
28
|
+
mismatch.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
super().__init__(*args)
|
|
32
|
+
self.slug = slug
|
|
33
|
+
self.skip_tests = skip_tests
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConfigError(CoverageException):
|
|
19
37
|
"""A problem with a config file, or a value in one."""
|
|
38
|
+
|
|
20
39
|
pass
|
|
21
40
|
|
|
22
41
|
|
|
23
42
|
class DataError(CoverageException):
|
|
24
43
|
"""An error in using a data file."""
|
|
44
|
+
|
|
25
45
|
pass
|
|
26
46
|
|
|
47
|
+
|
|
27
48
|
class NoDataError(CoverageException):
|
|
28
49
|
"""We didn't have data to work with."""
|
|
50
|
+
|
|
29
51
|
pass
|
|
30
52
|
|
|
31
53
|
|
|
32
54
|
class NoSource(CoverageException):
|
|
33
55
|
"""We couldn't find the source for a module."""
|
|
56
|
+
|
|
34
57
|
pass
|
|
35
58
|
|
|
36
59
|
|
|
37
60
|
class NoCode(NoSource):
|
|
38
61
|
"""We couldn't find any code at all."""
|
|
62
|
+
|
|
39
63
|
pass
|
|
40
64
|
|
|
41
65
|
|
|
42
66
|
class NotPython(CoverageException):
|
|
43
67
|
"""A source file turned out not to be parsable Python."""
|
|
68
|
+
|
|
44
69
|
pass
|
|
45
70
|
|
|
46
71
|
|
|
47
72
|
class PluginError(CoverageException):
|
|
48
73
|
"""A plugin misbehaved."""
|
|
74
|
+
|
|
49
75
|
pass
|
|
50
76
|
|
|
51
77
|
|
|
@@ -55,9 +81,11 @@ class _ExceptionDuringRun(CoverageException):
|
|
|
55
81
|
Construct it with three arguments, the values from `sys.exc_info`.
|
|
56
82
|
|
|
57
83
|
"""
|
|
84
|
+
|
|
58
85
|
pass
|
|
59
86
|
|
|
60
87
|
|
|
61
88
|
class CoverageWarning(Warning):
|
|
62
89
|
"""A warning from Coverage.py."""
|
|
90
|
+
|
|
63
91
|
pass
|
coverage/execfile.py
CHANGED
|
@@ -12,12 +12,11 @@ import marshal
|
|
|
12
12
|
import os
|
|
13
13
|
import struct
|
|
14
14
|
import sys
|
|
15
|
-
|
|
16
15
|
from importlib.machinery import ModuleSpec
|
|
17
16
|
from types import CodeType, ModuleType
|
|
18
17
|
from typing import Any
|
|
19
18
|
|
|
20
|
-
from coverage.exceptions import CoverageException,
|
|
19
|
+
from coverage.exceptions import CoverageException, NoCode, NoSource, _ExceptionDuringRun
|
|
21
20
|
from coverage.files import canonical_filename, python_reported_file
|
|
22
21
|
from coverage.misc import isolate_module
|
|
23
22
|
from coverage.python import get_python_source
|
|
@@ -27,11 +26,13 @@ os = isolate_module(os)
|
|
|
27
26
|
|
|
28
27
|
PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
|
|
29
28
|
|
|
29
|
+
|
|
30
30
|
class DummyLoader:
|
|
31
31
|
"""A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.
|
|
32
32
|
|
|
33
33
|
Currently only implements the .fullname attribute
|
|
34
34
|
"""
|
|
35
|
+
|
|
35
36
|
def __init__(self, fullname: str, *_args: Any) -> None:
|
|
36
37
|
self.fullname = fullname
|
|
37
38
|
|
|
@@ -57,8 +58,8 @@ def find_module(
|
|
|
57
58
|
spec = importlib.util.find_spec(mod_main)
|
|
58
59
|
if not spec:
|
|
59
60
|
raise NoSource(
|
|
60
|
-
f"No module named {mod_main}; "
|
|
61
|
-
f"{modulename!r} is a package and cannot be directly executed",
|
|
61
|
+
f"No module named {mod_main}; "
|
|
62
|
+
+ f"{modulename!r} is a package and cannot be directly executed",
|
|
62
63
|
)
|
|
63
64
|
pathname = spec.origin
|
|
64
65
|
packagename = spec.name
|
|
@@ -72,6 +73,7 @@ class PyRunner:
|
|
|
72
73
|
This is meant to emulate real Python execution as closely as possible.
|
|
73
74
|
|
|
74
75
|
"""
|
|
76
|
+
|
|
75
77
|
def __init__(self, args: list[str], as_module: bool = False) -> None:
|
|
76
78
|
self.args = args
|
|
77
79
|
self.as_module = as_module
|
|
@@ -89,7 +91,10 @@ class PyRunner:
|
|
|
89
91
|
This needs to happen before any importing, and without importing anything.
|
|
90
92
|
"""
|
|
91
93
|
path0: str | None
|
|
92
|
-
if
|
|
94
|
+
if getattr(sys.flags, "safe_path", False):
|
|
95
|
+
# See https://docs.python.org/3/using/cmdline.html#cmdoption-P
|
|
96
|
+
path0 = None
|
|
97
|
+
elif self.as_module:
|
|
93
98
|
path0 = os.getcwd()
|
|
94
99
|
elif os.path.isdir(self.arg0):
|
|
95
100
|
# Running a directory means running the __main__.py file in that
|
|
@@ -142,7 +147,7 @@ class PyRunner:
|
|
|
142
147
|
# Running a directory means running the __main__.py file in that
|
|
143
148
|
# directory.
|
|
144
149
|
for ext in [".py", ".pyc", ".pyo"]:
|
|
145
|
-
try_filename = os.path.join(self.arg0, "__main__"
|
|
150
|
+
try_filename = os.path.join(self.arg0, f"__main__{ext}")
|
|
146
151
|
# 3.8.10 changed how files are reported when running a
|
|
147
152
|
# directory.
|
|
148
153
|
try_filename = os.path.abspath(try_filename)
|
|
@@ -177,11 +182,11 @@ class PyRunner:
|
|
|
177
182
|
main_mod.__file__ = main_mod.__file__[:-1]
|
|
178
183
|
if self.package is not None:
|
|
179
184
|
main_mod.__package__ = self.package
|
|
180
|
-
main_mod.__loader__ = self.loader
|
|
185
|
+
main_mod.__loader__ = self.loader # type: ignore[assignment]
|
|
181
186
|
if self.spec is not None:
|
|
182
187
|
main_mod.__spec__ = self.spec
|
|
183
188
|
|
|
184
|
-
main_mod.__builtins__ = sys.modules["builtins"]
|
|
189
|
+
main_mod.__builtins__ = sys.modules["builtins"] # type: ignore[attr-defined]
|
|
185
190
|
|
|
186
191
|
sys.modules["__main__"] = main_mod
|
|
187
192
|
|
|
@@ -206,7 +211,7 @@ class PyRunner:
|
|
|
206
211
|
cwd = os.getcwd()
|
|
207
212
|
try:
|
|
208
213
|
exec(code, main_mod.__dict__)
|
|
209
|
-
except SystemExit:
|
|
214
|
+
except SystemExit: # pylint: disable=try-except-raise
|
|
210
215
|
# The user called sys.exit(). Just pass it along to the upper
|
|
211
216
|
# layers, where it will be handled.
|
|
212
217
|
raise
|
|
@@ -232,7 +237,7 @@ class PyRunner:
|
|
|
232
237
|
assert err.__traceback__ is not None
|
|
233
238
|
err.__traceback__ = err.__traceback__.tb_next
|
|
234
239
|
sys.excepthook(typ, err, tb.tb_next)
|
|
235
|
-
except SystemExit:
|
|
240
|
+
except SystemExit: # pylint: disable=try-except-raise
|
|
236
241
|
raise
|
|
237
242
|
except Exception as exc:
|
|
238
243
|
# Getting the output right in the case of excepthook
|
|
@@ -311,11 +316,11 @@ def make_code_from_pyc(filename: str) -> CodeType:
|
|
|
311
316
|
flags = struct.unpack("<L", fpyc.read(4))[0]
|
|
312
317
|
hash_based = flags & 0x01
|
|
313
318
|
if hash_based:
|
|
314
|
-
fpyc.read(8)
|
|
319
|
+
fpyc.read(8) # Skip the hash.
|
|
315
320
|
else:
|
|
316
321
|
# Skip the junk in the header that we don't need.
|
|
317
|
-
fpyc.read(4)
|
|
318
|
-
fpyc.read(4)
|
|
322
|
+
fpyc.read(4) # Skip the moddate.
|
|
323
|
+
fpyc.read(4) # Skip the size.
|
|
319
324
|
|
|
320
325
|
# The rest of the file is the code object we want.
|
|
321
326
|
code = marshal.load(fpyc)
|
coverage/files.py
CHANGED
|
@@ -12,21 +12,20 @@ import os.path
|
|
|
12
12
|
import posixpath
|
|
13
13
|
import re
|
|
14
14
|
import sys
|
|
15
|
-
|
|
16
|
-
from typing import Callable
|
|
17
15
|
from collections.abc import Iterable
|
|
16
|
+
from typing import Callable
|
|
18
17
|
|
|
19
18
|
from coverage import env
|
|
20
19
|
from coverage.exceptions import ConfigError
|
|
21
20
|
from coverage.misc import human_sorted, isolate_module, join_regex
|
|
22
21
|
|
|
23
|
-
|
|
24
22
|
os = isolate_module(os)
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
RELATIVE_DIR: str = ""
|
|
28
26
|
CANONICAL_FILENAME_CACHE: dict[str, str] = {}
|
|
29
27
|
|
|
28
|
+
|
|
30
29
|
def set_relative_directory() -> None:
|
|
31
30
|
"""Set the directory that `relative_filename` will be relative to."""
|
|
32
31
|
global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
|
|
@@ -59,7 +58,7 @@ def relative_filename(filename: str) -> str:
|
|
|
59
58
|
"""
|
|
60
59
|
fnorm = os.path.normcase(filename)
|
|
61
60
|
if fnorm.startswith(RELATIVE_DIR):
|
|
62
|
-
filename = filename[len(RELATIVE_DIR):]
|
|
61
|
+
filename = filename[len(RELATIVE_DIR) :]
|
|
63
62
|
return filename
|
|
64
63
|
|
|
65
64
|
|
|
@@ -74,7 +73,7 @@ def canonical_filename(filename: str) -> str:
|
|
|
74
73
|
if not os.path.isabs(filename):
|
|
75
74
|
for path in [os.curdir] + sys.path:
|
|
76
75
|
if path is None:
|
|
77
|
-
continue
|
|
76
|
+
continue # type: ignore[unreachable]
|
|
78
77
|
f = os.path.join(path, filename)
|
|
79
78
|
try:
|
|
80
79
|
exists = os.path.exists(f)
|
|
@@ -112,7 +111,6 @@ def flat_rootname(filename: str) -> str:
|
|
|
112
111
|
|
|
113
112
|
|
|
114
113
|
if env.WINDOWS:
|
|
115
|
-
|
|
116
114
|
_ACTUAL_PATH_CACHE: dict[str, str] = {}
|
|
117
115
|
_ACTUAL_PATH_LIST_CACHE: dict[str, list[str]] = {}
|
|
118
116
|
|
|
@@ -149,6 +147,7 @@ if env.WINDOWS:
|
|
|
149
147
|
return actpath
|
|
150
148
|
|
|
151
149
|
else:
|
|
150
|
+
|
|
152
151
|
def actual_path(path: str) -> str:
|
|
153
152
|
"""The actual path for non-Windows platforms."""
|
|
154
153
|
return path
|
|
@@ -166,7 +165,7 @@ def zip_location(filename: str) -> tuple[str, str] | None:
|
|
|
166
165
|
name is in the zipfile.
|
|
167
166
|
|
|
168
167
|
"""
|
|
169
|
-
for ext in [".zip", ".whl", ".egg", ".pex"]:
|
|
168
|
+
for ext in [".zip", ".whl", ".egg", ".pex", ".par"]:
|
|
170
169
|
zipbase, extension, inner = filename.partition(ext + sep(filename))
|
|
171
170
|
if extension:
|
|
172
171
|
zipfile = zipbase + ext
|
|
@@ -224,9 +223,9 @@ class TreeMatcher:
|
|
|
224
223
|
somewhere in a subtree rooted at one of the directories.
|
|
225
224
|
|
|
226
225
|
"""
|
|
226
|
+
|
|
227
227
|
def __init__(self, paths: Iterable[str], name: str = "unknown") -> None:
|
|
228
228
|
self.original_paths: list[str] = human_sorted(paths)
|
|
229
|
-
#self.paths = list(map(os.path.normcase, paths))
|
|
230
229
|
self.paths = [os.path.normcase(p) for p in paths]
|
|
231
230
|
self.name = name
|
|
232
231
|
|
|
@@ -253,7 +252,8 @@ class TreeMatcher:
|
|
|
253
252
|
|
|
254
253
|
class ModuleMatcher:
|
|
255
254
|
"""A matcher for modules in a tree."""
|
|
256
|
-
|
|
255
|
+
|
|
256
|
+
def __init__(self, module_names: Iterable[str], name: str = "unknown") -> None:
|
|
257
257
|
self.modules = list(module_names)
|
|
258
258
|
self.name = name
|
|
259
259
|
|
|
@@ -282,6 +282,7 @@ class ModuleMatcher:
|
|
|
282
282
|
|
|
283
283
|
class GlobMatcher:
|
|
284
284
|
"""A matcher for files by file name pattern."""
|
|
285
|
+
|
|
285
286
|
def __init__(self, pats: Iterable[str], name: str = "unknown") -> None:
|
|
286
287
|
self.pats = list(pats)
|
|
287
288
|
self.re = globs_to_regex(self.pats, case_insensitive=env.WINDOWS)
|
|
@@ -310,6 +311,7 @@ def sep(s: str) -> str:
|
|
|
310
311
|
|
|
311
312
|
# Tokenizer for _glob_to_regex.
|
|
312
313
|
# None as a sub means disallowed.
|
|
314
|
+
# fmt: off
|
|
313
315
|
G2RX_TOKENS = [(re.compile(rx), sub) for rx, sub in [
|
|
314
316
|
(r"\*\*\*+", None), # Can't have ***
|
|
315
317
|
(r"[^/]+\*\*+", None), # Can't have x**
|
|
@@ -326,17 +328,19 @@ G2RX_TOKENS = [(re.compile(rx), sub) for rx, sub in [
|
|
|
326
328
|
(r"[\[\]]", None), # Can't have single square brackets
|
|
327
329
|
(r".", r"\\\g<0>"), # Anything else is escaped to be safe
|
|
328
330
|
]]
|
|
331
|
+
# fmt: on
|
|
332
|
+
|
|
329
333
|
|
|
330
334
|
def _glob_to_regex(pattern: str) -> str:
|
|
331
335
|
"""Convert a file-path glob pattern into a regex."""
|
|
332
336
|
# Turn all backslashes into slashes to simplify the tokenizer.
|
|
333
337
|
pattern = pattern.replace("\\", "/")
|
|
334
338
|
if "/" not in pattern:
|
|
335
|
-
pattern = "**/"
|
|
339
|
+
pattern = f"**/{pattern}"
|
|
336
340
|
path_rx = []
|
|
337
341
|
pos = 0
|
|
338
342
|
while pos < len(pattern):
|
|
339
|
-
for rx, sub in G2RX_TOKENS:
|
|
343
|
+
for rx, sub in G2RX_TOKENS: # pragma: always breaks
|
|
340
344
|
if m := rx.match(pattern, pos=pos):
|
|
341
345
|
if sub is None:
|
|
342
346
|
raise ConfigError(f"File pattern can't include {m[0]!r}")
|
|
@@ -372,7 +376,7 @@ def globs_to_regex(
|
|
|
372
376
|
flags |= re.IGNORECASE
|
|
373
377
|
rx = join_regex(map(_glob_to_regex, patterns))
|
|
374
378
|
if not partial:
|
|
375
|
-
rx =
|
|
379
|
+
rx = rf"(?:{rx})\Z"
|
|
376
380
|
compiled = re.compile(rx, flags=flags)
|
|
377
381
|
return compiled
|
|
378
382
|
|
|
@@ -388,6 +392,7 @@ class PathAliases:
|
|
|
388
392
|
map a path through those aliases to produce a unified path.
|
|
389
393
|
|
|
390
394
|
"""
|
|
395
|
+
|
|
391
396
|
def __init__(
|
|
392
397
|
self,
|
|
393
398
|
debugfn: Callable[[str], None] | None = None,
|
|
@@ -444,7 +449,7 @@ class PathAliases:
|
|
|
444
449
|
result = result.rstrip(r"\/") + result_sep
|
|
445
450
|
self.aliases.append((original_pattern, regex, result))
|
|
446
451
|
|
|
447
|
-
def map(self, path: str, exists:Callable[[str], bool] = source_exists) -> str:
|
|
452
|
+
def map(self, path: str, exists: Callable[[str], bool] = source_exists) -> str:
|
|
448
453
|
"""Map `path` through the aliases.
|
|
449
454
|
|
|
450
455
|
`path` is checked against all of the patterns. The first pattern to
|
|
@@ -478,13 +483,13 @@ class PathAliases:
|
|
|
478
483
|
new = new[2:]
|
|
479
484
|
if not exists(new):
|
|
480
485
|
self.debugfn(
|
|
481
|
-
f"Rule {original_pattern!r} changed {path!r} to {new!r} "
|
|
482
|
-
"which doesn't exist, continuing",
|
|
486
|
+
f"Rule {original_pattern!r} changed {path!r} to {new!r} "
|
|
487
|
+
+ "which doesn't exist, continuing",
|
|
483
488
|
)
|
|
484
489
|
continue
|
|
485
490
|
self.debugfn(
|
|
486
|
-
f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, "
|
|
487
|
-
f"producing {new!r}",
|
|
491
|
+
f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, "
|
|
492
|
+
+ f"producing {new!r}",
|
|
488
493
|
)
|
|
489
494
|
return new
|
|
490
495
|
|
|
@@ -499,7 +504,7 @@ class PathAliases:
|
|
|
499
504
|
if len(parts) > 1:
|
|
500
505
|
dir1 = parts[0]
|
|
501
506
|
pattern = f"*/{dir1}"
|
|
502
|
-
regex_pat =
|
|
507
|
+
regex_pat = rf"^(.*[\\/])?{re.escape(dir1)}[\\/]"
|
|
503
508
|
result = f"{dir1}{os.sep}"
|
|
504
509
|
# Only add a new pattern if we don't already have this pattern.
|
|
505
510
|
if not any(p == pattern for p, _, _ in self.aliases):
|