Commits

Ned Batchelder committed 3218cdf

Split out the filename operations; Morf -> CodeUnit.

Comments (0)

Files changed (5)

coverage/cmdline.py

         directory = settings.get('directory=')
 
         omit = settings.get('omit=')
-        if omit is not None:
-            omit = [self.coverage.abs_file(p) for p in omit.split(',')]
-        else:
-            omit = []
+        if omit:
+            omit = omit.split(',')
         
         if settings.get('report'):
             self.coverage.report_engine(args, show_missing, ignore_errors, omit_prefixes=omit)

coverage/codeunit.py

+"""Code unit (module) handling for coverage.py"""
+
+import glob, os, types
+
+def code_unit_factory(morfs, file_wrangler, omit_prefixes=None):
+    """Construct a list of CodeUnits from polymorphic inputs.
+    
+    `morfs` is a module or a filename, or a list of same.
+    `file_wrangler` is a FileWrangler that can help resolve filenames.
+    `omit_prefixes` is a list of prefixes.  CodeUnits that match those prefixes
+    will be omitted from the list.
+    
+    Returns a list of CodeUnit objects.
+    
+    """
+
+    # Be sure we have a list.
+    if not isinstance(morfs, types.ListType):
+        morfs = [morfs]
+    
+    # On Windows, the shell doesn't expand wildcards.  Do it here.
+    globbed = []
+    for morf in morfs:
+        if isinstance(morf, basestring) and ('?' in morf or '*' in morf):
+            globbed.extend(glob.glob(morf))
+        else:
+            globbed.append(morf)
+    morfs = globbed
+
+    code_units = [CodeUnit(morf, file_wrangler) for morf in morfs]
+    
+    if omit_prefixes:
+        prefixes = [file_wrangler.abs_file(p) for p in omit_prefixes]
+        filtered = []
+        for cu in code_units:
+            for prefix in prefixes:
+                if cu.name.startswith(prefix):
+                    break
+            else:
+                filtered.append(cu)
+    
+        code_units = filtered
+
+    return code_units
+
+
+class CodeUnit:
+    """Code unit: a filename or module.
+    
+    `name` is a human-readable name for this code unit.
+    `filename` is the os path from which we can read the source.
+    
+    """
+
+    def __init__(self, morf, file_wrangler):
+        if hasattr(morf, '__file__'):
+            f = morf.__file__
+        else:
+            f = morf
+        self.filename = file_wrangler.canonical_filename(f)
+
+        if hasattr(morf, '__name__'):
+            n = morf.__name__
+        else:
+            n = os.path.splitext(morf)[0]
+            n = file_wrangler.relative_filename(n)
+        self.name = n
+
+    def __cmp__(self, other):
+        return cmp(self.name, other.name)

coverage/control.py

 """Core control stuff for coverage.py"""
 
-import glob, os, re, sys, types
+import os, re, sys
 
 from coverage.data import CoverageData
 from coverage.misc import nice_pair, CoverageException
-from coverage.morf import morf_factory, Morf
-
+from coverage.codeunit import code_unit_factory
+from coverage.files import FileWrangler
 
 class coverage:
     def __init__(self):
         self.nesting = 0
         self.cstack = []
         self.xstack = []
-        self.relative_dir = self.abs_file(os.curdir)+os.sep
+        self.file_wrangler = FileWrangler()
         
         self.collector = Collector(self.should_trace)
         
         # specify both -r and -a without doing double work.
         self.analysis_cache = {}
     
-        # Cache of results of calling the canonical_filename() method, to
-        # avoid duplicating work.
-        self.canonical_filename_cache = {}
-    
         # The default exclude pattern.
         self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
 
             return False
         # TODO: flag: ignore std lib?
         # TODO: ignore by module as well as file?
-        return self.canonical_filename(filename)
+        return self.file_wrangler.canonical_filename(filename)
 
     def use_cache(self, usecache, cache_file=None):
         self.data.usefile(usecache, cache_file)
         """Entry point for combining together parallel-mode coverage data."""
         self.data.combine_parallel_data()
 
-    def get_zip_data(self, filename):
-        """ Get data from `filename` if it is a zip file path, or return None
-            if it is not.
-        """
-        import zipimport
-        markers = ['.zip'+os.sep, '.egg'+os.sep]
-        for marker in markers:
-            if marker in filename:
-                parts = filename.split(marker)
-                try:
-                    zi = zipimport.zipimporter(parts[0]+marker[:-1])
-                except zipimport.ZipImportError:
-                    continue
-                try:
-                    data = zi.get_data(parts[1])
-                except IOError:
-                    continue
-                return data
-        return None
-
-    def abs_file(self, filename):
-        """ Helper function to turn a filename into an absolute normalized
-            filename.
-        """
-        return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
-
-    def relative_filename(self, filename):
-        """ Convert filename to relative filename from self.relative_dir.
-        """
-        return filename.replace(self.relative_dir, "")
-
-    def canonical_filename(self, filename):
-        """Return a canonical filename for `filename`.
-        
-        An absolute path with no redundant components and normalized case.
-        
-        """
-        if not self.canonical_filename_cache.has_key(filename):
-            f = filename
-            if os.path.isabs(f) and not os.path.exists(f):
-                if not self.get_zip_data(f):
-                    f = os.path.basename(f)
-            if not os.path.isabs(f):
-                for path in [os.curdir] + sys.path:
-                    g = os.path.join(path, f)
-                    if os.path.exists(g):
-                        f = g
-                        break
-            cf = self.abs_file(f)
-            self.canonical_filename_cache[filename] = cf
-        return self.canonical_filename_cache[filename]
-
     def group_collected_data(self):
         """Group the collected data by filename and reset the collector."""
         self.data.add_raw_data(self.collector.data_points())
             ext = '.py'
         if ext == '.py':
             if not os.path.exists(filename):
-                source = self.get_zip_data(filename)
+                source = self.file_wrangler.get_zip_data(filename)
                 if not source:
                     raise CoverageException(
                         "No source for code '%s'." % morf.filename
         return f, s, m, mf
 
     def analysis2(self, morf):
-        morf = Morf(morf)
-        return self.analysis_engine(morf)
+        code_units = code_unit_factory(morf, self.file_wrangler)
+        return self.analysis_engine(code_units[0])
 
     def analysis_engine(self, morf):
         filename, statements, excluded, line_map = self.analyze_morf(morf)
         self.report_engine(morfs, show_missing=show_missing, ignore_errors=ignore_errors, file=file)
 
     def report_engine(self, morfs, show_missing=True, ignore_errors=False, file=None, omit_prefixes=None):
-        morfs = morf_factory(morfs, omit_prefixes)
-        morfs.sort()
+        code_units = code_unit_factory(morfs, self.file_wrangler, omit_prefixes)
+        code_units.sort()
 
-        max_name = max(5, max(map(lambda m: len(m.name), morfs)))
+        max_name = max(5, max(map(lambda cu: len(cu.name), code_units)))
         fmt_name = "%%- %ds  " % max_name
         fmt_err = fmt_name + "%s: %s"
         header = fmt_name % "Name" + " Stmts   Exec  Cover"
         print >>file, "-" * len(header)
         total_statements = 0
         total_executed = 0
-        for morf in morfs:
+        for cu in code_units:
             try:
-                _, statements, _, missing, readable = self.analysis_engine(morf)
+                _, statements, _, missing, readable = self.analysis_engine(cu)
                 n = len(statements)
                 m = n - len(missing)
                 if n > 0:
                     pc = 100.0 * m / n
                 else:
                     pc = 100.0
-                args = (morf.name, n, m, pc)
+                args = (cu.name, n, m, pc)
                 if show_missing:
                     args = args + (readable,)
                 print >>file, fmt_coverage % args
             except:
                 if not ignore_errors:
                     typ, msg = sys.exc_info()[:2]
-                    print >>file, fmt_err % (morf.name, typ, msg)
-        if len(morfs) > 1:
+                    print >>file, fmt_err % (cu.name, typ, msg)
+        if len(code_units) > 1:
             print >>file, "-" * len(header)
             if total_statements > 0:
                 pc = 100.0 * total_executed / total_statements
     else_re = re.compile(r"\s*else\s*:\s*(#|$)")
 
     def annotate(self, morfs, directory=None, ignore_errors=False, omit_prefixes=None):
-        morfs = morf_factory(morfs, omit_prefixes)
-        for morf in morfs:
+        code_units = code_unit_factory(morfs, self.file_wrangler, omit_prefixes)
+        for cu in code_units:
             try:
-                filename, statements, excluded, missing, _ = self.analysis_engine(morf)
+                filename, statements, excluded, missing, _ = self.analysis_engine(cu)
                 self.annotate_file(filename, statements, excluded, missing, directory)
             except KeyboardInterrupt:
                 raise

coverage/files.py

+"""File wrangling."""
+
+import os, sys
+
+class FileWrangler:
+    """Understand how filenames work."""
+
+    def __init__(self):
+        self.relative_dir = self.abs_file(os.curdir) + os.sep
+
+        # Cache of results of calling the canonical_filename() method, to
+        # avoid duplicating work.
+        self.canonical_filename_cache = {}
+
+    def abs_file(self, filename):
+        """ Helper function to turn a filename into an absolute normalized
+            filename.
+        """
+        return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
+
+    def relative_filename(self, filename):
+        """ Convert filename to relative filename from self.relative_dir.
+        """
+        return filename.replace(self.relative_dir, "")
+
+    def canonical_filename(self, filename):
+        """Return a canonical filename for `filename`.
+        
+        An absolute path with no redundant components and normalized case.
+        
+        """
+        if not self.canonical_filename_cache.has_key(filename):
+            f = filename
+            if os.path.isabs(f) and not os.path.exists(f):
+                if not self.get_zip_data(f):
+                    f = os.path.basename(f)
+            if not os.path.isabs(f):
+                for path in [os.curdir] + sys.path:
+                    g = os.path.join(path, f)
+                    if os.path.exists(g):
+                        f = g
+                        break
+            cf = self.abs_file(f)
+            self.canonical_filename_cache[filename] = cf
+        return self.canonical_filename_cache[filename]
+
+    def get_zip_data(self, filename):
+        """ Get data from `filename` if it is a zip file path, or return None
+            if it is not.
+        """
+        import zipimport
+        markers = ['.zip'+os.sep, '.egg'+os.sep]
+        for marker in markers:
+            if marker in filename:
+                parts = filename.split(marker)
+                try:
+                    zi = zipimport.zipimporter(parts[0]+marker[:-1])
+                except zipimport.ZipImportError:
+                    continue
+                try:
+                    data = zi.get_data(parts[1])
+                except IOError:
+                    continue
+                return data
+        return None

coverage/morf.py

-"""Module or Filename handling for coverage.py"""
-
-# TODO: Distinguish between morf (input: module or filename), and Morf (class
-# that can represent either).
-
-def morf_factory(morfs, omit_prefixes=None):
-    # Be sure we have a list.
-    if not isinstance(morfs, types.ListType):
-        morfs = [morfs]
-    
-    # On Windows, the shell doesn't expand wildcards.  Do it here.
-    globbed = []
-    for morf in morfs:
-        if isinstance(morf, basestring) and ('?' in morf or '*' in morf):
-            globbed.extend(glob.glob(morf))
-        else:
-            globbed.append(morf)
-    morfs = globbed
-
-    morfs = map(Morf, morfs)
-    
-    if omit_prefixes:
-        filtered_morfs = []
-        for morf in morfs:
-            for prefix in omit_prefixes:
-                if morf.name.startswith(prefix):
-                    break
-            else:
-                filtered_morfs.append(morf)
-    
-        morfs = filtered_morfs
-
-    return morfs
-
-class Morf:
-    def __init__(self, morf):
-        if hasattr(morf, '__file__'):
-            f = morf.__file__
-        else:
-            f = morf
-        self.filename = self.canonical_filename(f)
-
-        if hasattr(morf, '__name__'):
-            self.name = morf.__name__
-        else:
-            self.name = self.relative_filename(os.path.splitext(morf)[0])
-
-    def __cmp__(self, other):
-        return cmp(self.name, other.name)