summaryrefslogtreecommitdiff
path: root/coverage/files.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/files.py')
-rw-r--r--coverage/files.py113
1 files changed, 64 insertions, 49 deletions
diff --git a/coverage/files.py b/coverage/files.py
index f7fc9693..e3ebd6ce 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -1,3 +1,6 @@
+# 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."""
import fnmatch
@@ -9,86 +12,98 @@ import re
import sys
from coverage import env
+from coverage.backward import unicode_class
from coverage.misc import CoverageException, join_regex
-class FileLocator(object):
- """Understand how filenames work."""
+RELATIVE_DIR = None
+CANONICAL_FILENAME_CACHE = {}
- 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 set_relative_directory():
+ """Set the directory that `relative_filename` will be relative to."""
+ global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
- def relative_filename(self, filename):
- """Return the relative form of `filename`.
+ # The absolute path to our current directory.
+ RELATIVE_DIR = os.path.normcase(abs_file(os.curdir) + os.sep)
- The filename will be relative to the current directory when the
- `FileLocator` was constructed.
+ # Cache of results of calling the canonical_filename() method, to
+ # avoid duplicating work.
+ CANONICAL_FILENAME_CACHE = {}
- """
- fnorm = os.path.normcase(filename)
- if fnorm.startswith(self.relative_dir):
- filename = filename[len(self.relative_dir):]
- return filename
+def relative_directory():
+ """Return the directory that `relative_filename` is relative to."""
+ return RELATIVE_DIR
- def canonical_filename(self, filename):
- """Return a canonical filename for `filename`.
+def relative_filename(filename):
+ """Return the relative form of `filename`.
- An absolute path with no redundant components and normalized case.
+ The filename will be relative to the current directory when the
+ `set_relative_directory` was called.
- """
- 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]
+ """
+ fnorm = os.path.normcase(filename)
+ if fnorm.startswith(RELATIVE_DIR):
+ filename = filename[len(RELATIVE_DIR):]
+ return filename
+
+def canonical_filename(filename):
+ """Return a canonical filename 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]
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."""
@@ -228,12 +243,9 @@ class PathAliases(object):
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.
@@ -286,6 +298,10 @@ class PathAliases(object):
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)
@@ -293,8 +309,7 @@ class PathAliases(object):
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