1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
"""Code unit (module) handling for Coverage."""
import glob, os
from coverage.backward import string_class, StringIO
from coverage.misc import CoverageException
def code_unit_factory(
morfs, file_locator, omit_prefixes=None, include_prefixes=None
):
"""Construct a list of CodeUnits from polymorphic inputs.
`morfs` is a module or a filename, or a list of same.
`file_locator` is a FileLocator that can help resolve filenames.
`include_prefixes` is a list of prefixes. Only CodeUnits that match those
prefixes will be included in the list. `omit_prefixes` is a list of
prefixes to omit from the list.
Returns a list of CodeUnit objects.
"""
# Be sure we have a list.
if not isinstance(morfs, (list, tuple)):
morfs = [morfs]
# On Windows, the shell doesn't expand wildcards. Do it here.
globbed = []
for morf in morfs:
if isinstance(morf, string_class) and ('?' in morf or '*' in morf):
globbed.extend(glob.glob(morf))
else:
globbed.append(morf)
morfs = globbed
code_units = [CodeUnit(morf, file_locator) for morf in morfs]
if include_prefixes:
assert not isinstance(include_prefixes, string_class) # common mistake
prefixes = [file_locator.abs_file(p) for p in include_prefixes]
filtered = []
for cu in code_units:
for prefix in prefixes:
if cu.filename.startswith(prefix):
filtered.append(cu)
break
code_units = filtered
if omit_prefixes:
code_units = omit_filter(omit_prefixes, code_units)
return code_units
def omit_filter(omit_prefixes, code_units):
"""
The filtering method removing any unwanted code_units
Refactored out so you can easily monkeypatch if needs be
"""
prefixes = [file_locator.abs_file(p) for p in omit_prefixes]
filtered = []
for cu in code_units:
for prefix in prefixes:
if cu.filename.startswith(prefix):
break
else:
filtered.append(cu)
return filtered
class CodeUnit(object):
"""Code unit: a filename or module.
Instance attributes:
`name` is a human-readable name for this code unit.
`filename` is the os path from which we can read the source.
`relative` is a boolean.
"""
def __init__(self, morf, file_locator):
self.file_locator = file_locator
if hasattr(morf, '__file__'):
f = morf.__file__
else:
f = morf
# .pyc files should always refer to a .py instead.
if f.endswith('.pyc'):
f = f[:-1]
self.filename = self.file_locator.canonical_filename(f)
if hasattr(morf, '__name__'):
n = modname = morf.__name__
self.relative = True
else:
n = os.path.splitext(morf)[0]
rel = self.file_locator.relative_filename(n)
if os.path.isabs(n):
self.relative = (rel != n)
else:
self.relative = True
n = rel
modname = None
self.name = n
self.modname = modname
def __repr__(self):
return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename)
# Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
# of them defined.
def __lt__(self, other):
return self.name < other.name
def __le__(self, other):
return self.name <= other.name
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return self.name != other.name
def __gt__(self, other):
return self.name > other.name
def __ge__(self, other):
return self.name >= other.name
def flat_rootname(self):
"""A base for a flat filename to correspond to this code unit.
Useful for writing files about the code where you want all the files in
the same directory, but need to differentiate same-named files from
different directories.
For example, the file a/b/c.py might return 'a_b_c'
"""
if self.modname:
return self.modname.replace('.', '_')
else:
root = os.path.splitdrive(self.name)[1]
return root.replace('\\', '_').replace('/', '_').replace('.', '_')
def source_file(self):
"""Return an open file for reading the source of the code unit."""
if os.path.exists(self.filename):
# A regular text file: open it.
return open(self.filename)
# Maybe it's in a zip file?
source = self.file_locator.get_zip_data(self.filename)
if source is not None:
return StringIO(source)
# Couldn't find source.
raise CoverageException(
"No source for code %r." % self.filename
)
|