| OLD | NEW |
| 1 """File wrangling.""" | 1 """File wrangling.""" |
| 2 | 2 |
| 3 from coverage.backward import to_string | 3 from coverage.backward import to_string |
| 4 from coverage.misc import CoverageException | 4 from coverage.misc import CoverageException |
| 5 import fnmatch, os, os.path, re, sys | 5 import fnmatch, os, os.path, re, sys |
| 6 import ntpath, posixpath |
| 6 | 7 |
| 7 class FileLocator(object): | 8 class FileLocator(object): |
| 8 """Understand how filenames work.""" | 9 """Understand how filenames work.""" |
| 9 | 10 |
| 10 def __init__(self): | 11 def __init__(self): |
| 11 # The absolute path to our current directory. | 12 # The absolute path to our current directory. |
| 12 self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep) | 13 self.relative_dir = os.path.normcase(abs_file(os.curdir) + os.sep) |
| 13 | 14 |
| 14 # Cache of results of calling the canonical_filename() method, to | 15 # Cache of results of calling the canonical_filename() method, to |
| 15 # avoid duplicating work. | 16 # avoid duplicating work. |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 103 return actpath | 104 return actpath |
| 104 | 105 |
| 105 actual_path.cache = {} | 106 actual_path.cache = {} |
| 106 actual_path.list_cache = {} | 107 actual_path.list_cache = {} |
| 107 | 108 |
| 108 else: | 109 else: |
| 109 def actual_path(filename): | 110 def actual_path(filename): |
| 110 """The actual path for non-Windows platforms.""" | 111 """The actual path for non-Windows platforms.""" |
| 111 return filename | 112 return filename |
| 112 | 113 |
| 114 |
| 113 def abs_file(filename): | 115 def abs_file(filename): |
| 114 """Return the absolute normalized form of `filename`.""" | 116 """Return the absolute normalized form of `filename`.""" |
| 115 path = os.path.abspath(os.path.realpath(filename)) | 117 path = os.path.expandvars(os.path.expanduser(filename)) |
| 118 path = os.path.abspath(os.path.realpath(path)) |
| 116 path = actual_path(path) | 119 path = actual_path(path) |
| 117 return path | 120 return path |
| 118 | 121 |
| 119 | 122 |
| 123 def isabs_anywhere(filename): |
| 124 """Is `filename` an absolute path on any OS?""" |
| 125 return ntpath.isabs(filename) or posixpath.isabs(filename) |
| 126 |
| 127 |
| 120 def prep_patterns(patterns): | 128 def prep_patterns(patterns): |
| 121 """Prepare the file patterns for use in a `FnmatchMatcher`. | 129 """Prepare the file patterns for use in a `FnmatchMatcher`. |
| 122 | 130 |
| 123 If a pattern starts with a wildcard, it is used as a pattern | 131 If a pattern starts with a wildcard, it is used as a pattern |
| 124 as-is. If it does not start with a wildcard, then it is made | 132 as-is. If it does not start with a wildcard, then it is made |
| 125 absolute with the current directory. | 133 absolute with the current directory. |
| 126 | 134 |
| 127 If `patterns` is None, an empty list is returned. | 135 If `patterns` is None, an empty list is returned. |
| 128 | 136 |
| 129 """ | 137 """ |
| 130 patterns = patterns or [] | |
| 131 prepped = [] | 138 prepped = [] |
| 132 for p in patterns or []: | 139 for p in patterns or []: |
| 133 if p.startswith("*") or p.startswith("?"): | 140 if p.startswith("*") or p.startswith("?"): |
| 134 prepped.append(p) | 141 prepped.append(p) |
| 135 else: | 142 else: |
| 136 prepped.append(abs_file(p)) | 143 prepped.append(abs_file(p)) |
| 137 return prepped | 144 return prepped |
| 138 | 145 |
| 139 | 146 |
| 140 class TreeMatcher(object): | 147 class TreeMatcher(object): |
| 141 """A matcher for files in a tree.""" | 148 """A matcher for files in a tree.""" |
| 142 def __init__(self, directories): | 149 def __init__(self, directories): |
| 143 self.dirs = directories[:] | 150 self.dirs = directories[:] |
| 144 | 151 |
| 145 def __repr__(self): | 152 def __repr__(self): |
| 146 return "<TreeMatcher %r>" % self.dirs | 153 return "<TreeMatcher %r>" % self.dirs |
| 147 | 154 |
| 155 def info(self): |
| 156 """A list of strings for displaying when dumping state.""" |
| 157 return self.dirs |
| 158 |
| 148 def add(self, directory): | 159 def add(self, directory): |
| 149 """Add another directory to the list we match for.""" | 160 """Add another directory to the list we match for.""" |
| 150 self.dirs.append(directory) | 161 self.dirs.append(directory) |
| 151 | 162 |
| 152 def match(self, fpath): | 163 def match(self, fpath): |
| 153 """Does `fpath` indicate a file in one of our trees?""" | 164 """Does `fpath` indicate a file in one of our trees?""" |
| 154 for d in self.dirs: | 165 for d in self.dirs: |
| 155 if fpath.startswith(d): | 166 if fpath.startswith(d): |
| 156 if fpath == d: | 167 if fpath == d: |
| 157 # This is the same file! | 168 # This is the same file! |
| 158 return True | 169 return True |
| 159 if fpath[len(d)] == os.sep: | 170 if fpath[len(d)] == os.sep: |
| 160 # This is a file in the directory | 171 # This is a file in the directory |
| 161 return True | 172 return True |
| 162 return False | 173 return False |
| 163 | 174 |
| 164 | 175 |
| 165 class FnmatchMatcher(object): | 176 class FnmatchMatcher(object): |
| 166 """A matcher for files by filename pattern.""" | 177 """A matcher for files by filename pattern.""" |
| 167 def __init__(self, pats): | 178 def __init__(self, pats): |
| 168 self.pats = pats[:] | 179 self.pats = pats[:] |
| 169 | 180 |
| 170 def __repr__(self): | 181 def __repr__(self): |
| 171 return "<FnmatchMatcher %r>" % self.pats | 182 return "<FnmatchMatcher %r>" % self.pats |
| 172 | 183 |
| 184 def info(self): |
| 185 """A list of strings for displaying when dumping state.""" |
| 186 return self.pats |
| 187 |
| 173 def match(self, fpath): | 188 def match(self, fpath): |
| 174 """Does `fpath` match one of our filename patterns?""" | 189 """Does `fpath` match one of our filename patterns?""" |
| 175 for pat in self.pats: | 190 for pat in self.pats: |
| 176 if fnmatch.fnmatch(fpath, pat): | 191 if fnmatch.fnmatch(fpath, pat): |
| 177 return True | 192 return True |
| 178 return False | 193 return False |
| 179 | 194 |
| 180 | 195 |
| 181 def sep(s): | 196 def sep(s): |
| 182 """Find the path separator used in this string, or os.sep if none.""" | 197 """Find the path separator used in this string, or os.sep if none.""" |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 216 | 231 |
| 217 `pattern` can't end with a wildcard component, since that would | 232 `pattern` can't end with a wildcard component, since that would |
| 218 match an entire tree, and not just its root. | 233 match an entire tree, and not just its root. |
| 219 | 234 |
| 220 """ | 235 """ |
| 221 # The pattern can't end with a wildcard component. | 236 # The pattern can't end with a wildcard component. |
| 222 pattern = pattern.rstrip(r"\/") | 237 pattern = pattern.rstrip(r"\/") |
| 223 if pattern.endswith("*"): | 238 if pattern.endswith("*"): |
| 224 raise CoverageException("Pattern must not end with wildcards.") | 239 raise CoverageException("Pattern must not end with wildcards.") |
| 225 pattern_sep = sep(pattern) | 240 pattern_sep = sep(pattern) |
| 241 |
| 242 # The pattern is meant to match a filepath. Let's make it absolute |
| 243 # unless it already is, or is meant to match any prefix. |
| 244 if not pattern.startswith('*') and not isabs_anywhere(pattern): |
| 245 pattern = abs_file(pattern) |
| 226 pattern += pattern_sep | 246 pattern += pattern_sep |
| 227 | 247 |
| 228 # Make a regex from the pattern. fnmatch always adds a \Z or $ to | 248 # Make a regex from the pattern. fnmatch always adds a \Z or $ to |
| 229 # match the whole string, which we don't want. | 249 # match the whole string, which we don't want. |
| 230 regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(') | 250 regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(') |
| 231 if regex_pat.endswith("$"): | 251 if regex_pat.endswith("$"): |
| 232 regex_pat = regex_pat[:-1] | 252 regex_pat = regex_pat[:-1] |
| 233 # We want */a/b.py to match on Windows to, so change slash to match | 253 # We want */a/b.py to match on Windows too, so change slash to match |
| 234 # either separator. | 254 # either separator. |
| 235 regex_pat = regex_pat.replace(r"\/", r"[\\/]") | 255 regex_pat = regex_pat.replace(r"\/", r"[\\/]") |
| 236 # We want case-insensitive matching, so add that flag. | 256 # We want case-insensitive matching, so add that flag. |
| 237 regex = re.compile(r"(?i)" + regex_pat) | 257 regex = re.compile(r"(?i)" + regex_pat) |
| 238 | 258 |
| 239 # Normalize the result: it must end with a path separator. | 259 # Normalize the result: it must end with a path separator. |
| 240 result_sep = sep(result) | 260 result_sep = sep(result) |
| 241 result = result.rstrip(r"\/") + result_sep | 261 result = result.rstrip(r"\/") + result_sep |
| 242 self.aliases.append((regex, result, pattern_sep, result_sep)) | 262 self.aliases.append((regex, result, pattern_sep, result_sep)) |
| 243 | 263 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 276 | 296 |
| 277 """ | 297 """ |
| 278 for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)): | 298 for i, (dirpath, dirnames, filenames) in enumerate(os.walk(dirname)): |
| 279 if i > 0 and '__init__.py' not in filenames: | 299 if i > 0 and '__init__.py' not in filenames: |
| 280 # If a directory doesn't have __init__.py, then it isn't | 300 # If a directory doesn't have __init__.py, then it isn't |
| 281 # importable and neither are its files | 301 # importable and neither are its files |
| 282 del dirnames[:] | 302 del dirnames[:] |
| 283 continue | 303 continue |
| 284 for filename in filenames: | 304 for filename in filenames: |
| 285 # We're only interested in files that look like reasonable Python | 305 # We're only interested in files that look like reasonable Python |
| 286 # files: Must end with .py, and must not have certain funny | 306 # files: Must end with .py or .pyw, and must not have certain funny |
| 287 # characters that probably mean they are editor junk. | 307 # characters that probably mean they are editor junk. |
| 288 if re.match(r"^[^.#~!$@%^&*()+=,]+\.py$", filename): | 308 if re.match(r"^[^.#~!$@%^&*()+=,]+\.pyw?$", filename): |
| 289 yield os.path.join(dirpath, filename) | 309 yield os.path.join(dirpath, filename) |
| OLD | NEW |