summaryrefslogtreecommitdiff
path: root/coverage/files.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/files.py')
-rw-r--r--coverage/files.py116
1 files changed, 95 insertions, 21 deletions
diff --git a/coverage/files.py b/coverage/files.py
index e6dc4aa1..8d154c6f 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -2,23 +2,19 @@
from coverage.backward import to_string
from coverage.misc import CoverageException
-import fnmatch, os, re, sys
+import fnmatch, os, os.path, re, sys
class FileLocator(object):
"""Understand how filenames work."""
def __init__(self):
# The absolute path to our current directory.
- self.relative_dir = self.abs_file(os.curdir) + os.sep
+ 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 abs_file(self, filename):
- """Return the absolute normalized form of `filename`."""
- return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
-
def relative_filename(self, filename):
"""Return the relative form of `filename`.
@@ -26,8 +22,9 @@ class FileLocator(object):
`FileLocator` was constructed.
"""
- if filename.startswith(self.relative_dir):
- filename = filename.replace(self.relative_dir, "")
+ fnorm = os.path.normcase(filename)
+ if fnorm.startswith(self.relative_dir):
+ filename = filename[len(self.relative_dir):]
return filename
def canonical_filename(self, filename):
@@ -37,19 +34,15 @@ class FileLocator(object):
"""
if filename not in self.canonical_filename_cache:
- f = filename
- if os.path.isabs(f) and not os.path.exists(f):
- if self.get_zip_data(f) is None:
- f = os.path.basename(f)
- if not os.path.isabs(f):
+ if not os.path.isabs(filename):
for path in [os.curdir] + sys.path:
if path is None:
continue
- g = os.path.join(path, f)
- if os.path.exists(g):
- f = g
+ f = os.path.join(path, filename)
+ if os.path.exists(f):
+ filename = f
break
- cf = self.abs_file(f)
+ cf = abs_file(filename)
self.canonical_filename_cache[filename] = cf
return self.canonical_filename_cache[filename]
@@ -78,6 +71,71 @@ class FileLocator(object):
return None
+if sys.platform == 'win32':
+
+ 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]
+
+ head, tail = os.path.split(path)
+ if not tail:
+ actpath = head
+ elif not head:
+ actpath = tail
+ else:
+ head = actual_path(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
+ 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
+ return actpath
+
+ actual_path.cache = {}
+ actual_path.list_cache = {}
+
+else:
+ def actual_path(filename):
+ """The actual path for non-Windows platforms."""
+ return filename
+
+def abs_file(filename):
+ """Return the absolute normalized form of `filename`."""
+ path = os.path.abspath(os.path.realpath(filename))
+ path = actual_path(path)
+ return path
+
+
+def prep_patterns(patterns):
+ """Prepare the file patterns for use in a `FnmatchMatcher`.
+
+ If a pattern starts with a wildcard, it is used as a pattern
+ as-is. If it does not start with a wildcard, then it is made
+ absolute with the current directory.
+
+ If `patterns` is None, an empty list is returned.
+
+ """
+ prepped = []
+ for p in patterns or []:
+ if p.startswith("*") or p.startswith("?"):
+ prepped.append(p)
+ else:
+ prepped.append(abs_file(p))
+ return prepped
+
+
class TreeMatcher(object):
"""A matcher for files in a tree."""
def __init__(self, directories):
@@ -86,6 +144,10 @@ class TreeMatcher(object):
def __repr__(self):
return "<TreeMatcher %r>" % self.dirs
+ def info(self):
+ """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)
@@ -111,6 +173,10 @@ class FnmatchMatcher(object):
def __repr__(self):
return "<FnmatchMatcher %r>" % self.pats
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.pats
+
def match(self, fpath):
"""Does `fpath` match one of our filename patterns?"""
for pat in self.pats:
@@ -175,7 +241,7 @@ class PathAliases(object):
# either separator.
regex_pat = regex_pat.replace(r"\/", r"[\\/]")
# We want case-insensitive matching, so add that flag.
- regex = re.compile("(?i)" + regex_pat)
+ regex = re.compile(r"(?i)" + regex_pat)
# Normalize the result: it must end with a path separator.
result_sep = sep(result)
@@ -207,9 +273,17 @@ class PathAliases(object):
def find_python_files(dirname):
- """Yield all of the importable Python files in `dirname`, recursively."""
- for dirpath, dirnames, filenames in os.walk(dirname, topdown=True):
- if '__init__.py' not in filenames:
+ """Yield all of the importable Python files in `dirname`, recursively.
+
+ 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
+ find the importable files.
+
+ """
+ for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)):
+ if i > 0 and '__init__.py' not in filenames:
# If a directory doesn't have __init__.py, then it isn't
# importable and neither are its files
del dirnames[:]