1"""Code unit (module) handling for Coverage.""" 2 3import glob, os 4 5from coverage.backward import open_source, string_class, StringIO 6from coverage.misc import CoverageException 7 8 9def 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 37class 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 ) 118