1 """Code unit (module) handling for Coverage.""" |
|
2 |
|
3 import glob, os, sys |
|
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 if isinstance(self.filename, unicode): |
|
61 self.filename = self.filename.encode(sys.getfilesystemencoding()) |
|
62 |
|
63 if hasattr(morf, '__name__'): |
|
64 n = modname = morf.__name__ |
|
65 self.relative = True |
|
66 else: |
|
67 n = os.path.splitext(morf)[0] |
|
68 rel = self.file_locator.relative_filename(n) |
|
69 if isinstance(rel, unicode): |
|
70 rel = rel.encode(sys.getfilesystemencoding()) |
|
71 if os.path.isabs(n): |
|
72 self.relative = (rel != n) |
|
73 else: |
|
74 self.relative = True |
|
75 n = rel |
|
76 modname = None |
|
77 self.name = n |
|
78 self.modname = modname |
|
79 |
|
80 def __repr__(self): |
|
81 return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename) |
|
82 |
|
83 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all |
|
84 # of them defined. |
|
85 |
|
86 def __lt__(self, other): |
|
87 return self.name < other.name |
|
88 def __le__(self, other): |
|
89 return self.name <= other.name |
|
90 def __eq__(self, other): |
|
91 return self.name == other.name |
|
92 def __ne__(self, other): |
|
93 return self.name != other.name |
|
94 def __gt__(self, other): |
|
95 return self.name > other.name |
|
96 def __ge__(self, other): |
|
97 return self.name >= other.name |
|
98 |
|
99 def flat_rootname(self): |
|
100 """A base for a flat filename to correspond to this code unit. |
|
101 |
|
102 Useful for writing files about the code where you want all the files in |
|
103 the same directory, but need to differentiate same-named files from |
|
104 different directories. |
|
105 |
|
106 For example, the file a/b/c.py might return 'a_b_c' |
|
107 |
|
108 """ |
|
109 if self.modname: |
|
110 return self.modname.replace('.', '_') |
|
111 else: |
|
112 root = os.path.splitdrive(self.name)[1] |
|
113 return root.replace('\\', '_').replace('/', '_').replace('.', '_') |
|
114 |
|
115 def source_file(self): |
|
116 """Return an open file for reading the source of the code unit.""" |
|
117 if os.path.exists(self.filename): |
|
118 # A regular text file: open it. |
|
119 return open_source(self.filename) |
|
120 |
|
121 # Maybe it's in a zip file? |
|
122 source = self.file_locator.get_zip_data(self.filename) |
|
123 if source is not None: |
|
124 return StringIO(source) |
|
125 |
|
126 # Couldn't find source. |
|
127 raise CoverageException( |
|
128 "No source for code '%s'." % self.filename |
|
129 ) |
|
130 |
|
131 def should_be_python(self): |
|
132 """Does it seem like this file should contain Python? |
|
133 |
|
134 This is used to decide if a file reported as part of the exection of |
|
135 a program was really likely to have contained Python in the first |
|
136 place. |
|
137 |
|
138 """ |
|
139 # Get the file extension. |
|
140 _, ext = os.path.splitext(self.filename) |
|
141 |
|
142 # Anything named *.py* should be Python. |
|
143 if ext.startswith('.py'): |
|
144 return True |
|
145 # A file with no extension should be Python. |
|
146 if not ext: |
|
147 return True |
|
148 # Everything else is probably not Python. |
|
149 return False |
|
150 |
|
151 # |
|
152 # eflag: FileType = Python2 |
|