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