diff options
Diffstat (limited to 'coverage/files.py')
-rw-r--r-- | coverage/files.py | 113 |
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 |