DebugClients/Python3/coverage/files.py

changeset 4489
d0d6e4ad31bd
parent 3495
fac17a82b431
child 5051
3586ebd9fac8
diff -r 456c58fc64b0 -r d0d6e4ad31bd DebugClients/Python3/coverage/files.py
--- a/DebugClients/Python3/coverage/files.py	Sun Oct 04 13:35:09 2015 +0200
+++ b/DebugClients/Python3/coverage/files.py	Sun Oct 04 22:37:56 2015 +0200
@@ -1,111 +1,123 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
 """File wrangling."""
 
-from .backward import to_string
-from .misc import CoverageException
-import fnmatch, os, os.path, re, sys
-import ntpath, posixpath
-
-class FileLocator(object):
-    """Understand how filenames work."""
-
-    def __init__(self):
-        # The absolute path to our current directory.
-        self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep)
-
-        # Cache of results of calling the canonical_filename() method, to
-        # avoid duplicating work.
-        self.canonical_filename_cache = {}
-
-    def relative_filename(self, filename):
-        """Return the relative form of `filename`.
-
-        The filename will be relative to the current directory when the
-        `FileLocator` was constructed.
-
-        """
-        fnorm = os.path.normcase(filename)
-        if fnorm.startswith(self.relative_dir):
-            filename = filename[len(self.relative_dir):]
-        return filename
-
-    def canonical_filename(self, filename):
-        """Return a canonical filename for `filename`.
-
-        An absolute path with no redundant components and normalized case.
+import fnmatch
+import ntpath
+import os
+import os.path
+import posixpath
+import re
+import sys
 
-        """
-        if filename not in self.canonical_filename_cache:
-            if not os.path.isabs(filename):
-                for path in [os.curdir] + sys.path:
-                    if path is None:
-                        continue
-                    f = os.path.join(path, filename)
-                    if os.path.exists(f):
-                        filename = f
-                        break
-            cf = abs_file(filename)
-            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.
+from coverage import env
+from coverage.backward import unicode_class
+from coverage.misc import CoverageException, join_regex
 
-        Returns the string data read from the zip file, or None if no zip file
-        could be found or `filename` isn't in it.  The data returned will be
-        an empty string if the file is empty.
 
-        """
-        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 to_string(data)
-        return None
+RELATIVE_DIR = None
+CANONICAL_FILENAME_CACHE = {}
 
 
-if sys.platform == 'win32':
+def set_relative_directory():
+    """Set the directory that `relative_filename` will be relative to."""
+    global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
+
+    # The absolute path to our current directory.
+    RELATIVE_DIR = os.path.normcase(abs_file(os.curdir) + os.sep)
+
+    # Cache of results of calling the canonical_filename() method, to
+    # avoid duplicating work.
+    CANONICAL_FILENAME_CACHE = {}
+
+def relative_directory():
+    """Return the directory that `relative_filename` is relative to."""
+    return RELATIVE_DIR
+
+def relative_filename(filename):
+    """Return the relative form of `filename`.
+
+    The file name will be relative to the current directory when the
+    `set_relative_directory` was called.
+
+    """
+    fnorm = os.path.normcase(filename)
+    if fnorm.startswith(RELATIVE_DIR):
+        filename = filename[len(RELATIVE_DIR):]
+    return filename
+
+def canonical_filename(filename):
+    """Return a canonical file name for `filename`.
+
+    An absolute path with no redundant components and normalized case.
+
+    """
+    if filename not in CANONICAL_FILENAME_CACHE:
+        if not os.path.isabs(filename):
+            for path in [os.curdir] + sys.path:
+                if path is None:
+                    continue
+                f = os.path.join(path, filename)
+                if os.path.exists(f):
+                    filename = f
+                    break
+        cf = abs_file(filename)
+        CANONICAL_FILENAME_CACHE[filename] = cf
+    return CANONICAL_FILENAME_CACHE[filename]
+
+
+def flat_rootname(filename):
+    """A base for a flat file name to correspond to this file.
+
+    Useful for writing files about the code where you want all the files in
+    the same directory, but need to differentiate same-named files from
+    different directories.
+
+    For example, the file a/b/c.py will return 'a_b_c_py'
+
+    """
+    name = ntpath.splitdrive(filename)[1]
+    return re.sub(r"[\\/.:]", "_", name)
+
+
+if env.WINDOWS:
+
+    _ACTUAL_PATH_CACHE = {}
+    _ACTUAL_PATH_LIST_CACHE = {}
 
     def actual_path(path):
         """Get the actual path of `path`, including the correct case."""
-        if path in actual_path.cache:
-            return actual_path.cache[path]
+        if env.PY2 and isinstance(path, unicode_class):
+            path = path.encode(sys.getfilesystemencoding())
+        if path in _ACTUAL_PATH_CACHE:
+            return _ACTUAL_PATH_CACHE[path]
 
         head, tail = os.path.split(path)
         if not tail:
-            actpath = head
+            # This means head is the drive spec: normalize it.
+            actpath = head.upper()
         elif not head:
             actpath = tail
         else:
             head = actual_path(head)
-            if head in actual_path.list_cache:
-                files = actual_path.list_cache[head]
+            if head in _ACTUAL_PATH_LIST_CACHE:
+                files = _ACTUAL_PATH_LIST_CACHE[head]
             else:
                 try:
                     files = os.listdir(head)
                 except OSError:
                     files = []
-                actual_path.list_cache[head] = files
+                _ACTUAL_PATH_LIST_CACHE[head] = files
             normtail = os.path.normcase(tail)
             for f in files:
                 if os.path.normcase(f) == normtail:
                     tail = f
                     break
             actpath = os.path.join(head, tail)
-        actual_path.cache[path] = actpath
+        _ACTUAL_PATH_CACHE[path] = actpath
         return actpath
 
-    actual_path.cache = {}
-    actual_path.list_cache = {}
-
 else:
     def actual_path(filename):
         """The actual path for non-Windows platforms."""
@@ -137,7 +149,7 @@
     """
     prepped = []
     for p in patterns or []:
-        if p.startswith("*") or p.startswith("?"):
+        if p.startswith(("*", "?")):
             prepped.append(p)
         else:
             prepped.append(abs_file(p))
@@ -147,7 +159,7 @@
 class TreeMatcher(object):
     """A matcher for files in a tree."""
     def __init__(self, directories):
-        self.dirs = directories[:]
+        self.dirs = list(directories)
 
     def __repr__(self):
         return "<TreeMatcher %r>" % self.dirs
@@ -156,10 +168,6 @@
         """A list of strings for displaying when dumping state."""
         return self.dirs
 
-    def add(self, directory):
-        """Add another directory to the list we match for."""
-        self.dirs.append(directory)
-
     def match(self, fpath):
         """Does `fpath` indicate a file in one of our trees?"""
         for d in self.dirs:
@@ -173,10 +181,49 @@
         return False
 
 
+class ModuleMatcher(object):
+    """A matcher for modules in a tree."""
+    def __init__(self, module_names):
+        self.modules = list(module_names)
+
+    def __repr__(self):
+        return "<ModuleMatcher %r>" % (self.modules)
+
+    def info(self):
+        """A list of strings for displaying when dumping state."""
+        return self.modules
+
+    def match(self, module_name):
+        """Does `module_name` indicate a module in one of our packages?"""
+        if not module_name:
+            return False
+
+        for m in self.modules:
+            if module_name.startswith(m):
+                if module_name == m:
+                    return True
+                if module_name[len(m)] == '.':
+                    # This is a module in the package
+                    return True
+
+        return False
+
+
 class FnmatchMatcher(object):
-    """A matcher for files by filename pattern."""
+    """A matcher for files by file name pattern."""
     def __init__(self, pats):
         self.pats = pats[:]
+        # fnmatch is platform-specific. On Windows, it does the Windows thing
+        # of treating / and \ as equivalent. But on other platforms, we need to
+        # take care of that ourselves.
+        fnpats = (fnmatch.translate(p) for p in pats)
+        fnpats = (p.replace(r"\/", r"[\\/]") for p in fnpats)
+        if env.WINDOWS:
+            # Windows is also case-insensitive.  BTW: the regex docs say that
+            # flags like (?i) have to be at the beginning, but fnmatch puts
+            # them at the end, and having two there seems to work fine.
+            fnpats = (p + "(?i)" for p in fnpats)
+        self.re = re.compile(join_regex(fnpats))
 
     def __repr__(self):
         return "<FnmatchMatcher %r>" % self.pats
@@ -186,11 +233,8 @@
         return self.pats
 
     def match(self, fpath):
-        """Does `fpath` match one of our filename patterns?"""
-        for pat in self.pats:
-            if fnmatch.fnmatch(fpath, pat):
-                return True
-        return False
+        """Does `fpath` match one of our file name patterns?"""
+        return self.re.match(fpath) is not None
 
 
 def sep(s):
@@ -213,12 +257,9 @@
     A `PathAliases` object tracks a list of pattern/result pairs, and can
     map a path through those aliases to produce a unified path.
 
-    `locator` is a FileLocator that is used to canonicalize the results.
-
     """
-    def __init__(self, locator=None):
+    def __init__(self):
         self.aliases = []
-        self.locator = locator
 
     def add(self, pattern, result):
         """Add the `pattern`/`result` pair to the list of aliases.
@@ -245,11 +286,10 @@
             pattern = abs_file(pattern)
         pattern += pattern_sep
 
-        # Make a regex from the pattern.  fnmatch always adds a \Z or $ to
+        # Make a regex from the pattern.  fnmatch always adds a \Z to
         # match the whole string, which we don't want.
         regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(')
-        if regex_pat.endswith("$"):
-            regex_pat = regex_pat[:-1]
+
         # We want */a/b.py to match on Windows too, so change slash to match
         # either separator.
         regex_pat = regex_pat.replace(r"\/", r"[\\/]")
@@ -272,6 +312,10 @@
         The separator style in the result is made to match that of the result
         in the alias.
 
+        Returns the mapped path.  If a mapping has happened, this is a
+        canonical path.  If no mapping has happened, it is the original value
+        of `path` unchanged.
+
         """
         for regex, result, pattern_sep, result_sep in self.aliases:
             m = regex.match(path)
@@ -279,8 +323,7 @@
                 new = path.replace(m.group(0), result)
                 if pattern_sep != result_sep:
                     new = new.replace(pattern_sep, result_sep)
-                if self.locator:
-                    new = self.locator.canonical_filename(new)
+                new = canonical_filename(new)
                 return new
         return path
 
@@ -291,7 +334,7 @@
     To be importable, the files have to be in a directory with a __init__.py,
     except for `dirname` itself, which isn't required to have one.  The
     assumption is that `dirname` was specified directly, so the user knows
-    best, but subdirectories are checked for a __init__.py to be sure we only
+    best, but sub-directories are checked for a __init__.py to be sure we only
     find the importable files.
 
     """

eric ide

mercurial