DebugClients/Python/coverage/files.py

branch
maintenance
changeset 6693
3629d88ae235
parent 6649
f1b3a73831c9
equal deleted inserted replaced
6647:2a11e1b2dcbe 6693:3629d88ae235
258 258
259 259
260 class FnmatchMatcher(object): 260 class FnmatchMatcher(object):
261 """A matcher for files by file name pattern.""" 261 """A matcher for files by file name pattern."""
262 def __init__(self, pats): 262 def __init__(self, pats):
263 self.pats = pats[:] 263 self.pats = list(pats)
264 # fnmatch is platform-specific. On Windows, it does the Windows thing 264 self.re = fnmatches_to_regex(self.pats, case_insensitive=env.WINDOWS)
265 # of treating / and \ as equivalent. But on other platforms, we need to
266 # take care of that ourselves.
267 fnpats = (fnmatch.translate(p) for p in pats)
268 # Python3.7 fnmatch translates "/" as "/", before that, it translates as "\/",
269 # so we have to deal with maybe a backslash.
270 fnpats = (re.sub(r"\\?/", r"[\\\\/]", p) for p in fnpats)
271 flags = 0
272 if env.WINDOWS:
273 # Windows is also case-insensitive, so make the regex case-insensitive.
274 flags |= re.IGNORECASE
275 self.re = re.compile(join_regex(fnpats), flags=flags)
276 265
277 def __repr__(self): 266 def __repr__(self):
278 return "<FnmatchMatcher %r>" % self.pats 267 return "<FnmatchMatcher %r>" % self.pats
279 268
280 def info(self): 269 def info(self):
294 else: 283 else:
295 the_sep = os.sep 284 the_sep = os.sep
296 return the_sep 285 return the_sep
297 286
298 287
288 def fnmatches_to_regex(patterns, case_insensitive=False, partial=False):
289 """Convert fnmatch patterns to a compiled regex that matches any of them.
290
291 Slashes are always converted to match either slash or backslash, for
292 Windows support, even when running elsewhere.
293
294 If `partial` is true, then the pattern will match if the target string
295 starts with the pattern. Otherwise, it must match the entire string.
296
297 Returns: a compiled regex object. Use the .match method to compare target
298 strings.
299
300 """
301 regexes = (fnmatch.translate(pattern) for pattern in patterns)
302 # Python3.7 fnmatch translates "/" as "/". Before that, it translates as "\/",
303 # so we have to deal with maybe a backslash.
304 regexes = (re.sub(r"\\?/", r"[\\\\/]", regex) for regex in regexes)
305
306 if partial:
307 # fnmatch always adds a \Z to match the whole string, which we don't
308 # want, so we remove the \Z. While removing it, we only replace \Z if
309 # followed by paren (introducing flags), or at end, to keep from
310 # destroying a literal \Z in the pattern.
311 regexes = (re.sub(r'\\Z(\(\?|$)', r'\1', regex) for regex in regexes)
312
313 flags = 0
314 if case_insensitive:
315 flags |= re.IGNORECASE
316 compiled = re.compile(join_regex(regexes), flags=flags)
317
318 return compiled
319
320
299 class PathAliases(object): 321 class PathAliases(object):
300 """A collection of aliases for paths. 322 """A collection of aliases for paths.
301 323
302 When combining data files from remote machines, often the paths to source 324 When combining data files from remote machines, often the paths to source
303 code are different, for example, due to OS differences, or because of 325 code are different, for example, due to OS differences, or because of
341 if not pattern.startswith('*') and not isabs_anywhere(pattern): 363 if not pattern.startswith('*') and not isabs_anywhere(pattern):
342 pattern = abs_file(pattern) 364 pattern = abs_file(pattern)
343 if not pattern.endswith(pattern_sep): 365 if not pattern.endswith(pattern_sep):
344 pattern += pattern_sep 366 pattern += pattern_sep
345 367
346 # Make a regex from the pattern. fnmatch always adds a \Z to 368 # Make a regex from the pattern.
347 # match the whole string, which we don't want, so we remove the \Z. 369 regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True)
348 # While removing it, we only replace \Z if followed by paren, or at
349 # end, to keep from destroying a literal \Z in the pattern.
350 regex_pat = fnmatch.translate(pattern)
351 regex_pat = re.sub(r'\\Z(\(|$)', r'\1', regex_pat)
352
353 # We want */a/b.py to match on Windows too, so change slash to match
354 # either separator.
355 regex_pat = regex_pat.replace(r"\/", r"[\\/]")
356 # We want case-insensitive matching, so add that flag.
357 regex = re.compile(r"(?i)" + regex_pat)
358 370
359 # Normalize the result: it must end with a path separator. 371 # Normalize the result: it must end with a path separator.
360 result_sep = sep(result) 372 result_sep = sep(result)
361 result = result.rstrip(r"\/") + result_sep 373 result = result.rstrip(r"\/") + result_sep
362 self.aliases.append((regex, result)) 374 self.aliases.append((regex, result))

eric ide

mercurial