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