Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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/.
5 import posixpath
6 import os
7 import re
9 '''
10 Like os.path, with a reduced set of functions, and with normalized path
11 separators (always use forward slashes).
12 Also contains a few additional utilities not found in os.path.
13 '''
16 def normsep(path):
17 '''
18 Normalize path separators, by using forward slashes instead of whatever
19 os.sep is.
20 '''
21 if os.sep != '/':
22 path = path.replace(os.sep, '/')
23 return path
26 def relpath(path, start):
27 rel = normsep(os.path.relpath(path, start))
28 return '' if rel == '.' else rel
31 def abspath(path):
32 return normsep(os.path.abspath(path))
35 def join(*paths):
36 return normsep(os.path.join(*paths))
39 def normpath(path):
40 return posixpath.normpath(normsep(path))
43 def dirname(path):
44 return posixpath.dirname(normsep(path))
47 def commonprefix(paths):
48 return posixpath.commonprefix([normsep(path) for path in paths])
51 def basename(path):
52 return os.path.basename(path)
55 def splitext(path):
56 return posixpath.splitext(normsep(path))
59 def split(path):
60 '''
61 Return the normalized path as a list of its components.
62 split('foo/bar/baz') returns ['foo', 'bar', 'baz']
63 '''
64 return normsep(path).split('/')
67 def basedir(path, bases):
68 '''
69 Given a list of directories (bases), return which one contains the given
70 path. If several matches are found, the deepest base directory is returned.
71 basedir('foo/bar/baz', ['foo', 'baz', 'foo/bar']) returns 'foo/bar'
72 ('foo' and 'foo/bar' both match, but 'foo/bar' is the deepest match)
73 '''
74 path = normsep(path)
75 bases = [normsep(b) for b in bases]
76 if path in bases:
77 return path
78 for b in sorted(bases, reverse=True):
79 if b == '' or path.startswith(b + '/'):
80 return b
83 re_cache = {}
85 def match(path, pattern):
86 '''
87 Return whether the given path matches the given pattern.
88 An asterisk can be used to match any string, including the null string, in
89 one part of the path:
90 'foo' matches '*', 'f*' or 'fo*o'
91 However, an asterisk matching a subdirectory may not match the null string:
92 'foo/bar' does *not* match 'foo/*/bar'
93 If the pattern matches one of the ancestor directories of the path, the
94 patch is considered matching:
95 'foo/bar' matches 'foo'
96 Two adjacent asterisks can be used to match files and zero or more
97 directories and subdirectories.
98 'foo/bar' matches 'foo/**/bar', or '**/bar'
99 '''
100 if not pattern:
101 return True
102 if not pattern in re_cache:
103 pattern = re.escape(pattern)
104 pattern = re.sub(r'(^|\\\/)\\\*\\\*\\\/', r'\1(?:.+/)?', pattern)
105 pattern = re.sub(r'(^|\\\/)\\\*\\\*$', r'(?:\1.+)?', pattern)
106 pattern = pattern.replace(r'\*', '[^/]*') + '(?:/.*)?$'
107 re_cache[pattern] = re.compile(pattern)
108 return re_cache[pattern].match(path) is not None
111 def rebase(oldbase, base, relativepath):
112 '''
113 Return relativepath relative to base instead of oldbase.
114 '''
115 if base == oldbase:
116 return relativepath
117 if len(base) < len(oldbase):
118 assert basedir(oldbase, [base]) == base
119 relbase = relpath(oldbase, base)
120 result = join(relbase, relativepath)
121 else:
122 assert basedir(base, [oldbase]) == oldbase
123 relbase = relpath(base, oldbase)
124 result = relpath(relativepath, relbase)
125 result = normpath(result)
126 if relativepath.endswith('/') and not result.endswith('/'):
127 result += '/'
128 return result