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