| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | """Code unit (module) handling for Coverage.""" |
| 2 | |
| 3 | import glob, os |
| 4 | |
| 5 | from coverage.backward import open_source, string_class, StringIO |
| 6 | from coverage.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'): |
| 56 | f = f[:-1] |
| 57 | self.filename = self.file_locator.canonical_filename(f) |
| 58 | |
| 59 | if hasattr(morf, '__name__'): |
| 60 | n = modname = morf.__name__ |
| 61 | self.relative = True |
| 62 | else: |
| 63 | n = os.path.splitext(morf)[0] |
| 64 | rel = self.file_locator.relative_filename(n) |
| 65 | if os.path.isabs(n): |
| 66 | self.relative = (rel != n) |
| 67 | else: |
| 68 | self.relative = True |
| 69 | n = rel |
| 70 | modname = None |
| 71 | self.name = n |
| 72 | self.modname = modname |
| 73 | |
| 74 | def __repr__(self): |
| 75 | return "<CodeUnit name=%r filename=%r>" % (self.name, self.filename) |
| 76 | |
| 77 | # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all |
| 78 | # of them defined. |
| 79 | |
| 80 | def __lt__(self, other): return self.name < other.name |
| 81 | def __le__(self, other): return self.name <= other.name |
| 82 | def __eq__(self, other): return self.name == other.name |
| 83 | def __ne__(self, other): return self.name != other.name |
| 84 | def __gt__(self, other): return self.name > other.name |
| 85 | def __ge__(self, other): return self.name >= other.name |
| 86 | |
| 87 | def flat_rootname(self): |
| 88 | """A base for a flat filename to correspond to this code unit. |
| 89 | |
| 90 | Useful for writing files about the code where you want all the files in |
| 91 | the same directory, but need to differentiate same-named files from |
| 92 | different directories. |
| 93 | |
| 94 | For example, the file a/b/c.py might return 'a_b_c' |
| 95 | |
| 96 | """ |
| 97 | if self.modname: |
| 98 | return self.modname.replace('.', '_') |
| 99 | else: |
| 100 | root = os.path.splitdrive(self.name)[1] |
| 101 | return root.replace('\\', '_').replace('/', '_').replace('.', '_') |
| 102 | |
| 103 | def source_file(self): |
| 104 | """Return an open file for reading the source of the code unit.""" |
| 105 | if os.path.exists(self.filename): |
| 106 | # A regular text file: open it. |
| 107 | return open_source(self.filename) |
| 108 | |
| 109 | # Maybe it's in a zip file? |
| 110 | source = self.file_locator.get_zip_data(self.filename) |
| 111 | if source is not None: |
| 112 | return StringIO(source) |
| 113 | |
| 114 | # Couldn't find source. |
| 115 | raise CoverageException( |
| 116 | "No source for code %r." % self.filename |
| 117 | ) |