|
1 """Code unit (module) handling for Coverage.""" |
|
2 |
|
3 import glob, os |
|
4 |
|
5 def code_unit_factory(morfs, file_locator, omit_prefixes=None): |
|
6 """Construct a list of CodeUnits from polymorphic inputs. |
|
7 |
|
8 `morfs` is a module or a filename, or a list of same. |
|
9 `file_locator` is a FileLocator that can help resolve filenames. |
|
10 `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes |
|
11 will be omitted from the list. |
|
12 |
|
13 Returns a list of CodeUnit objects. |
|
14 |
|
15 """ |
|
16 |
|
17 # Be sure we have a list. |
|
18 if not isinstance(morfs, (list, tuple)): |
|
19 morfs = [morfs] |
|
20 |
|
21 # On Windows, the shell doesn't expand wildcards. Do it here. |
|
22 globbed = [] |
|
23 for morf in morfs: |
|
24 if isinstance(morf, basestring) and ('?' in morf or '*' in morf): |
|
25 globbed.extend(glob.glob(morf)) |
|
26 else: |
|
27 globbed.append(morf) |
|
28 morfs = globbed |
|
29 |
|
30 code_units = [CodeUnit(morf, file_locator) for morf in morfs] |
|
31 |
|
32 if omit_prefixes: |
|
33 prefixes = [file_locator.abs_file(p) for p in omit_prefixes] |
|
34 filtered = [] |
|
35 for cu in code_units: |
|
36 for prefix in prefixes: |
|
37 if cu.name.startswith(prefix): |
|
38 break |
|
39 else: |
|
40 filtered.append(cu) |
|
41 |
|
42 code_units = filtered |
|
43 |
|
44 return code_units |
|
45 |
|
46 |
|
47 class CodeUnit: |
|
48 """Code unit: a filename or module. |
|
49 |
|
50 Instance attributes: |
|
51 |
|
52 `name` is a human-readable name for this code unit. |
|
53 `filename` is the os path from which we can read the source. |
|
54 `relative` is a boolean. |
|
55 |
|
56 """ |
|
57 |
|
58 def __init__(self, morf, file_locator): |
|
59 if hasattr(morf, '__file__'): |
|
60 f = morf.__file__ |
|
61 else: |
|
62 f = morf |
|
63 # .pyc files should always refer to a .py instead. |
|
64 if f.endswith('.pyc'): |
|
65 f = f[:-1] |
|
66 self.filename = file_locator.canonical_filename(f) |
|
67 |
|
68 if hasattr(morf, '__name__'): |
|
69 n = modname = morf.__name__ |
|
70 self.relative = True |
|
71 else: |
|
72 n = os.path.splitext(morf)[0] |
|
73 rel = file_locator.relative_filename(n) |
|
74 if os.path.isabs(n): |
|
75 self.relative = (rel != n) |
|
76 else: |
|
77 self.relative = True |
|
78 n = rel |
|
79 modname = None |
|
80 self.name = n |
|
81 self.modname = modname |
|
82 |
|
83 def __repr__(self): |
|
84 return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename) |
|
85 |
|
86 def __cmp__(self, other): |
|
87 return cmp(self.name, other.name) |
|
88 |
|
89 def flat_rootname(self): |
|
90 """A base for a flat filename to correspond to this code unit. |
|
91 |
|
92 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 |
|
94 different directories. |
|
95 |
|
96 For example, the file a/b/c.py might return 'a_b_c' |
|
97 |
|
98 """ |
|
99 if self.modname: |
|
100 return self.modname.replace('.', '_') |
|
101 else: |
|
102 root = os.path.splitdrive(os.path.splitext(self.name)[0])[1] |
|
103 return root.replace('\\', '_').replace('/', '_') |
|
104 |
|
105 def source_file(self): |
|
106 """Return an open file for reading the source of the code unit.""" |
|
107 return open(self.filename) |