src/eric7/DebugClients/Python/coverage/files.py

branch
eric7
changeset 9374
ed79209469ad
parent 9252
32dd11232e06
equal deleted inserted replaced
9373:e074358157f4 9374:ed79209469ad
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt 2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3 3
4 """File wrangling.""" 4 """File wrangling."""
5 5
6 import fnmatch
6 import hashlib 7 import hashlib
7 import fnmatch
8 import ntpath 8 import ntpath
9 import os 9 import os
10 import os.path 10 import os.path
11 import posixpath 11 import posixpath
12 import re 12 import re
22 22
23 def set_relative_directory(): 23 def set_relative_directory():
24 """Set the directory that `relative_filename` will be relative to.""" 24 """Set the directory that `relative_filename` will be relative to."""
25 global RELATIVE_DIR, CANONICAL_FILENAME_CACHE 25 global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
26 26
27 # The current directory
28 abs_curdir = abs_file(os.curdir)
29 if not abs_curdir.endswith(os.sep):
30 # Suffix with separator only if not at the system root
31 abs_curdir = abs_curdir + os.sep
32
27 # The absolute path to our current directory. 33 # The absolute path to our current directory.
28 RELATIVE_DIR = os.path.normcase(abs_file(os.curdir) + os.sep) 34 RELATIVE_DIR = os.path.normcase(abs_curdir)
29 35
30 # Cache of results of calling the canonical_filename() method, to 36 # Cache of results of calling the canonical_filename() method, to
31 # avoid duplicating work. 37 # avoid duplicating work.
32 CANONICAL_FILENAME_CACHE = {} 38 CANONICAL_FILENAME_CACHE = {}
33 39
318 324
319 A `PathAliases` object tracks a list of pattern/result pairs, and can 325 A `PathAliases` object tracks a list of pattern/result pairs, and can
320 map a path through those aliases to produce a unified path. 326 map a path through those aliases to produce a unified path.
321 327
322 """ 328 """
323 def __init__(self, relative=False): 329 def __init__(self, debugfn=None, relative=False):
324 self.aliases = [] 330 self.aliases = [] # A list of (original_pattern, regex, result)
331 self.debugfn = debugfn or (lambda msg: 0)
325 self.relative = relative 332 self.relative = relative
326 333 self.pprinted = False
327 def pprint(self): # pragma: debugging 334
335 def pprint(self):
328 """Dump the important parts of the PathAliases, for debugging.""" 336 """Dump the important parts of the PathAliases, for debugging."""
329 print(f"Aliases (relative={self.relative}):") 337 self.debugfn(f"Aliases (relative={self.relative}):")
330 for regex, result in self.aliases: 338 for original_pattern, regex, result in self.aliases:
331 print(f"{regex.pattern!r} --> {result!r}") 339 self.debugfn(f" Rule: {original_pattern!r} -> {result!r} using regex {regex.pattern!r}")
332 340
333 def add(self, pattern, result): 341 def add(self, pattern, result):
334 """Add the `pattern`/`result` pair to the list of aliases. 342 """Add the `pattern`/`result` pair to the list of aliases.
335 343
336 `pattern` is an `fnmatch`-style pattern. `result` is a simple 344 `pattern` is an `fnmatch`-style pattern. `result` is a simple
341 349
342 `pattern` can't end with a wildcard component, since that would 350 `pattern` can't end with a wildcard component, since that would
343 match an entire tree, and not just its root. 351 match an entire tree, and not just its root.
344 352
345 """ 353 """
354 original_pattern = pattern
346 pattern_sep = sep(pattern) 355 pattern_sep = sep(pattern)
347 356
348 if len(pattern) > 1: 357 if len(pattern) > 1:
349 pattern = pattern.rstrip(r"\/") 358 pattern = pattern.rstrip(r"\/")
350 359
352 if pattern.endswith("*"): 361 if pattern.endswith("*"):
353 raise ConfigError("Pattern must not end with wildcards.") 362 raise ConfigError("Pattern must not end with wildcards.")
354 363
355 # The pattern is meant to match a filepath. Let's make it absolute 364 # The pattern is meant to match a filepath. Let's make it absolute
356 # unless it already is, or is meant to match any prefix. 365 # unless it already is, or is meant to match any prefix.
357 if not pattern.startswith('*') and not isabs_anywhere(pattern + 366 if not pattern.startswith('*') and not isabs_anywhere(pattern + pattern_sep):
358 pattern_sep):
359 pattern = abs_file(pattern) 367 pattern = abs_file(pattern)
360 if not pattern.endswith(pattern_sep): 368 if not pattern.endswith(pattern_sep):
361 pattern += pattern_sep 369 pattern += pattern_sep
362 370
363 # Make a regex from the pattern. 371 # Make a regex from the pattern.
364 regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True) 372 regex = fnmatches_to_regex([pattern], case_insensitive=True, partial=True)
365 373
366 # Normalize the result: it must end with a path separator. 374 # Normalize the result: it must end with a path separator.
367 result_sep = sep(result) 375 result_sep = sep(result)
368 result = result.rstrip(r"\/") + result_sep 376 result = result.rstrip(r"\/") + result_sep
369 self.aliases.append((regex, result)) 377 self.aliases.append((original_pattern, regex, result))
370 378
371 def map(self, path): 379 def map(self, path):
372 """Map `path` through the aliases. 380 """Map `path` through the aliases.
373 381
374 `path` is checked against all of the patterns. The first pattern to 382 `path` is checked against all of the patterns. The first pattern to
382 Returns the mapped path. If a mapping has happened, this is a 390 Returns the mapped path. If a mapping has happened, this is a
383 canonical path. If no mapping has happened, it is the original value 391 canonical path. If no mapping has happened, it is the original value
384 of `path` unchanged. 392 of `path` unchanged.
385 393
386 """ 394 """
387 for regex, result in self.aliases: 395 if not self.pprinted:
396 self.pprint()
397 self.pprinted = True
398
399 for original_pattern, regex, result in self.aliases:
388 m = regex.match(path) 400 m = regex.match(path)
389 if m: 401 if m:
390 new = path.replace(m[0], result) 402 new = path.replace(m[0], result)
391 new = new.replace(sep(path), sep(result)) 403 new = new.replace(sep(path), sep(result))
392 if not self.relative: 404 if not self.relative:
393 new = canonical_filename(new) 405 new = canonical_filename(new)
406 self.debugfn(
407 f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, " +
408 f"producing {new!r}"
409 )
394 return new 410 return new
411 self.debugfn(f"No rules match, path {path!r} is unchanged")
395 return path 412 return path
396 413
397 414
398 def find_python_files(dirname): 415 def find_python_files(dirname):
399 """Yield all of the importable Python files in `dirname`, recursively. 416 """Yield all of the importable Python files in `dirname`, recursively.

eric ide

mercurial