path.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from __future__ import absolute_import
  5. import posixpath
  6. import os
  7. import re
  8. '''
  9. Like os.path, with a reduced set of functions, and with normalized path
  10. separators (always use forward slashes).
  11. Also contains a few additional utilities not found in os.path.
  12. '''
  13. def normsep(path):
  14. '''
  15. Normalize path separators, by using forward slashes instead of whatever
  16. os.sep is.
  17. '''
  18. if os.sep != '/':
  19. path = path.replace(os.sep, '/')
  20. if os.altsep and os.altsep != '/':
  21. path = path.replace(os.altsep, '/')
  22. return path
  23. def relpath(path, start):
  24. rel = normsep(os.path.relpath(path, start))
  25. return '' if rel == '.' else rel
  26. def realpath(path):
  27. return normsep(os.path.realpath(path))
  28. def abspath(path):
  29. return normsep(os.path.abspath(path))
  30. def join(*paths):
  31. return normsep(os.path.join(*paths))
  32. def normpath(path):
  33. return posixpath.normpath(normsep(path))
  34. def dirname(path):
  35. return posixpath.dirname(normsep(path))
  36. def commonprefix(paths):
  37. return posixpath.commonprefix([normsep(path) for path in paths])
  38. def basename(path):
  39. return os.path.basename(path)
  40. def splitext(path):
  41. return posixpath.splitext(normsep(path))
  42. def split(path):
  43. '''
  44. Return the normalized path as a list of its components.
  45. split('foo/bar/baz') returns ['foo', 'bar', 'baz']
  46. '''
  47. return normsep(path).split('/')
  48. def basedir(path, bases):
  49. '''
  50. Given a list of directories (bases), return which one contains the given
  51. path. If several matches are found, the deepest base directory is returned.
  52. basedir('foo/bar/baz', ['foo', 'baz', 'foo/bar']) returns 'foo/bar'
  53. ('foo' and 'foo/bar' both match, but 'foo/bar' is the deepest match)
  54. '''
  55. path = normsep(path)
  56. bases = [normsep(b) for b in bases]
  57. if path in bases:
  58. return path
  59. for b in sorted(bases, reverse=True):
  60. if b == '' or path.startswith(b + '/'):
  61. return b
  62. re_cache = {}
  63. def match(path, pattern):
  64. '''
  65. Return whether the given path matches the given pattern.
  66. An asterisk can be used to match any string, including the null string, in
  67. one part of the path:
  68. 'foo' matches '*', 'f*' or 'fo*o'
  69. However, an asterisk matching a subdirectory may not match the null string:
  70. 'foo/bar' does *not* match 'foo/*/bar'
  71. If the pattern matches one of the ancestor directories of the path, the
  72. patch is considered matching:
  73. 'foo/bar' matches 'foo'
  74. Two adjacent asterisks can be used to match files and zero or more
  75. directories and subdirectories.
  76. 'foo/bar' matches 'foo/**/bar', or '**/bar'
  77. '''
  78. if not pattern:
  79. return True
  80. if pattern not in re_cache:
  81. p = re.escape(pattern)
  82. p = re.sub(r'(^|\\\/)\\\*\\\*\\\/', r'\1(?:.+/)?', p)
  83. p = re.sub(r'(^|\\\/)\\\*\\\*$', r'(?:\1.+)?', p)
  84. p = p.replace(r'\*', '[^/]*') + '(?:/.*)?$'
  85. re_cache[pattern] = re.compile(p)
  86. return re_cache[pattern].match(path) is not None
  87. def rebase(oldbase, base, relativepath):
  88. '''
  89. Return relativepath relative to base instead of oldbase.
  90. '''
  91. if base == oldbase:
  92. return relativepath
  93. if len(base) < len(oldbase):
  94. assert basedir(oldbase, [base]) == base
  95. relbase = relpath(oldbase, base)
  96. result = join(relbase, relativepath)
  97. else:
  98. assert basedir(base, [oldbase]) == oldbase
  99. relbase = relpath(base, oldbase)
  100. result = relpath(relativepath, relbase)
  101. result = normpath(result)
  102. if relativepath.endswith('/') and not result.endswith('/'):
  103. result += '/'
  104. return result