--- a/DebugClients/Python/coverage/files.py Fri Sep 02 19:08:02 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,381 +0,0 @@ -# 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 -import ntpath -import os -import os.path -import posixpath -import re -import sys - -from coverage import env -from coverage.backward import unicode_class -from coverage.misc import contract, CoverageException, join_regex, isolate_module - - -os = isolate_module(os) - - -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 - - -@contract(returns='unicode') -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 unicode_filename(filename) - - -@contract(returns='unicode') -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 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: - # 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] - 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 - -else: - def actual_path(filename): - """The actual path for non-Windows platforms.""" - return filename - - -if env.PY2: - @contract(returns='unicode') - def unicode_filename(filename): - """Return a Unicode version of `filename`.""" - if isinstance(filename, str): - encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - filename = filename.decode(encoding, "replace") - return filename -else: - @contract(filename='unicode', returns='unicode') - def unicode_filename(filename): - """Return a Unicode version of `filename`.""" - return filename - - -@contract(returns='unicode') -def abs_file(filename): - """Return the absolute normalized form of `filename`.""" - path = os.path.expandvars(os.path.expanduser(filename)) - path = os.path.abspath(os.path.realpath(path)) - path = actual_path(path) - path = unicode_filename(path) - return path - - -RELATIVE_DIR = None -CANONICAL_FILENAME_CACHE = None -set_relative_directory() - - -def isabs_anywhere(filename): - """Is `filename` an absolute path on any OS?""" - return ntpath.isabs(filename) or posixpath.isabs(filename) - - -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(("*", "?")): - 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): - self.dirs = list(directories) - - def __repr__(self): - return "<TreeMatcher %r>" % self.dirs - - def info(self): - """A list of strings for displaying when dumping state.""" - return self.dirs - - def match(self, fpath): - """Does `fpath` indicate a file in one of our trees?""" - for d in self.dirs: - if fpath.startswith(d): - if fpath == d: - # This is the same file! - return True - if fpath[len(d)] == os.sep: - # This is a file in the directory - return True - 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 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 - - 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 file name patterns?""" - return self.re.match(fpath) is not None - - -def sep(s): - """Find the path separator used in this string, or os.sep if none.""" - sep_match = re.search(r"[\\/]", s) - if sep_match: - the_sep = sep_match.group(0) - else: - the_sep = os.sep - return the_sep - - -class PathAliases(object): - """A collection of aliases for paths. - - When combining data files from remote machines, often the paths to source - code are different, for example, due to OS differences, or because of - serialized checkouts on continuous integration machines. - - A `PathAliases` object tracks a list of pattern/result pairs, and can - map a path through those aliases to produce a unified path. - - """ - def __init__(self): - self.aliases = [] - - def add(self, pattern, result): - """Add the `pattern`/`result` pair to the list of aliases. - - `pattern` is an `fnmatch`-style pattern. `result` is a simple - string. When mapping paths, if a path starts with a match against - `pattern`, then that match is replaced with `result`. This models - isomorphic source trees being rooted at different places on two - different machines. - - `pattern` can't end with a wildcard component, since that would - match an entire tree, and not just its root. - - """ - # The pattern can't end with a wildcard component. - pattern = pattern.rstrip(r"\/") - if pattern.endswith("*"): - raise CoverageException("Pattern must not end with wildcards.") - pattern_sep = sep(pattern) - - # The pattern is meant to match a filepath. Let's make it absolute - # unless it already is, or is meant to match any prefix. - if not pattern.startswith('*') and not isabs_anywhere(pattern): - pattern = abs_file(pattern) - pattern += pattern_sep - - # 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(', '(') - - # We want */a/b.py to match on Windows too, so change slash to match - # either separator. - regex_pat = regex_pat.replace(r"\/", r"[\\/]") - # We want case-insensitive matching, so add that flag. - regex = re.compile(r"(?i)" + regex_pat) - - # Normalize the result: it must end with a path separator. - result_sep = sep(result) - result = result.rstrip(r"\/") + result_sep - self.aliases.append((regex, result, pattern_sep, result_sep)) - - def map(self, path): - """Map `path` through the aliases. - - `path` is checked against all of the patterns. The first pattern to - match is used to replace the root of the path with the result root. - Only one pattern is ever used. If no patterns match, `path` is - returned unchanged. - - 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) - if m: - new = path.replace(m.group(0), result) - if pattern_sep != result_sep: - new = new.replace(pattern_sep, result_sep) - new = canonical_filename(new) - return new - return path - - -def find_python_files(dirname): - """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 sub-directories 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[:] - continue - for filename in filenames: - # We're only interested in files that look like reasonable Python - # files: Must end with .py or .pyw, and must not have certain funny - # characters that probably mean they are editor junk. - if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename): - yield os.path.join(dirpath, filename) - -# -# eflag: FileType = Python2