1 """Code unit (module) handling for Coverage."""
5 from coverage
.backward
import open_source
, string_class
, StringIO
6 from coverage
.misc
import CoverageException
9 def code_unit_factory(morfs
, file_locator
):
10 """Construct a list of CodeUnits from polymorphic inputs.
12 `morfs` is a module or a filename, or a list of same.
14 `file_locator` is a FileLocator that can help resolve filenames.
16 Returns a list of CodeUnit objects.
19 # Be sure we have a list.
20 if not isinstance(morfs
, (list, tuple)):
23 # On Windows, the shell doesn't expand wildcards. Do it here.
26 if isinstance(morf
, string_class
) and ('?' in morf
or '*' in morf
):
27 globbed
.extend(glob
.glob(morf
))
32 code_units
= [CodeUnit(morf
, file_locator
) for morf
in morfs
]
37 class CodeUnit(object):
38 """Code unit: a filename or module.
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.
47 def __init__(self
, morf
, file_locator
):
48 self
.file_locator
= file_locator
50 if hasattr(morf
, '__file__'):
54 # .pyc files should always refer to a .py instead.
55 if f
.endswith('.pyc') or f
.endswith('.pyo'):
57 elif f
.endswith('$py.class'): # Jython
59 self
.filename
= self
.file_locator
.canonical_filename(f
)
61 if hasattr(morf
, '__name__'):
62 n
= modname
= morf
.__name
__
65 n
= os
.path
.splitext(morf
)[0]
66 rel
= self
.file_locator
.relative_filename(n
)
68 self
.relative
= (rel
!= n
)
74 self
.modname
= modname
77 return "<CodeUnit name=%r filename=%r>" % (self
.name
, self
.filename
)
79 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
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
95 def flat_rootname(self
):
96 """A base for a flat filename to correspond to this code unit.
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.
102 For example, the file a/b/c.py might return 'a_b_c'
106 return self
.modname
.replace('.', '_')
108 root
= os
.path
.splitdrive(self
.name
)[1]
109 return root
.replace('\\', '_').replace('/', '_').replace('.', '_')
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
)
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
)
122 # Couldn't find source.
123 raise CoverageException(
124 "No source for code '%s'." % self
.filename
127 def should_be_python(self
):
128 """Does it seem like this file should contain Python?
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
135 # Get the file extension.
136 _
, ext
= os
.path
.splitext(self
.filename
)
138 # Anything named *.py* should be Python.
139 if ext
.startswith('.py'):
141 # A file with no extension should be Python.
144 # Everything else is probably not Python.