"""File wrangling.""" from coverage.backward import to_string import fnmatch, os, 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 # 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`. The filename will be relative to the current directory when the `FileLocator` was constructed. """ if filename.startswith(self.relative_dir): filename = filename.replace(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. """ 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): 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 break cf = self.abs_file(f) 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. 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 class TreeMatcher(object): """A matcher for files in a tree.""" def __init__(self, directories): self.dirs = directories[:] def __repr__(self): 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: 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 FnmatchMatcher(object): """A matcher for files by filename pattern.""" def __init__(self, pats): self.pats = pats[:] def __repr__(self): 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 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: # 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: if fnmatch.fnmatch(filename, "*.py"): yield os.path.join(dirpath, filename)