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 5""" 6Like :py:mod:`os.path`, with a reduced set of functions, and with normalized path 7separators (always use forward slashes). 8Also contains a few additional utilities not found in :py:mod:`os.path`. 9""" 10 11# Imported from 12# https://searchfox.org/mozilla-central/rev/c3ebaf6de2d481c262c04bb9657eaf76bf47e2ac/python/mozbuild/mozpack/path.py 13 14 15import posixpath 16import os 17import re 18 19 20def normsep(path): 21 """ 22 Normalize path separators, by using forward slashes instead of whatever 23 :py:const:`os.sep` is. 24 """ 25 if os.sep != "/": 26 path = path.replace(os.sep, "/") 27 if os.altsep and os.altsep != "/": 28 path = path.replace(os.altsep, "/") 29 return path 30 31 32def relpath(path, start): 33 rel = normsep(os.path.relpath(path, start)) 34 return "" if rel == "." else rel 35 36 37def realpath(path): 38 return normsep(os.path.realpath(path)) 39 40 41def abspath(path): 42 return normsep(os.path.abspath(path)) 43 44 45def join(*paths): 46 return normsep(os.path.join(*paths)) 47 48 49def normpath(path): 50 return posixpath.normpath(normsep(path)) 51 52 53def dirname(path): 54 return posixpath.dirname(normsep(path)) 55 56 57def commonprefix(paths): 58 return posixpath.commonprefix([normsep(path) for path in paths]) 59 60 61def basename(path): 62 return os.path.basename(path) 63 64 65def splitext(path): 66 return posixpath.splitext(normsep(path)) 67 68 69def split(path): 70 """ 71 Return the normalized path as a list of its components. 72 73 ``split('foo/bar/baz')`` returns ``['foo', 'bar', 'baz']`` 74 """ 75 return normsep(path).split("/") 76 77 78def basedir(path, bases): 79 """ 80 Given a list of directories (`bases`), return which one contains the given 81 path. If several matches are found, the deepest base directory is returned. 82 83 ``basedir('foo/bar/baz', ['foo', 'baz', 'foo/bar'])`` returns ``'foo/bar'`` 84 (`'foo'` and `'foo/bar'` both match, but `'foo/bar'` is the deepest match) 85 """ 86 path = normsep(path) 87 bases = [normsep(b) for b in bases] 88 if path in bases: 89 return path 90 for b in sorted(bases, reverse=True): 91 if b == "" or path.startswith(b + "/"): 92 return b 93 94 95re_cache = {} 96# Python versions < 3.7 return r'\/' for re.escape('/'). 97if re.escape("/") == "/": 98 MATCH_STAR_STAR_RE = re.compile(r"(^|/)\\\*\\\*/") 99 MATCH_STAR_STAR_END_RE = re.compile(r"(^|/)\\\*\\\*$") 100else: 101 MATCH_STAR_STAR_RE = re.compile(r"(^|\\\/)\\\*\\\*\\\/") 102 MATCH_STAR_STAR_END_RE = re.compile(r"(^|\\\/)\\\*\\\*$") 103 104 105def match(path, pattern): 106 """ 107 Return whether the given path matches the given pattern. 108 An asterisk can be used to match any string, including the null string, in 109 one part of the path: 110 111 ``foo`` matches ``*``, ``f*`` or ``fo*o`` 112 113 However, an asterisk matching a subdirectory may not match the null string: 114 115 ``foo/bar`` does *not* match ``foo/*/bar`` 116 117 If the pattern matches one of the ancestor directories of the path, the 118 patch is considered matching: 119 120 ``foo/bar`` matches ``foo`` 121 122 Two adjacent asterisks can be used to match files and zero or more 123 directories and subdirectories. 124 125 ``foo/bar`` matches ``foo/**/bar``, or ``**/bar`` 126 """ 127 if not pattern: 128 return True 129 if pattern not in re_cache: 130 p = re.escape(pattern) 131 p = MATCH_STAR_STAR_RE.sub(r"\1(?:.+/)?", p) 132 p = MATCH_STAR_STAR_END_RE.sub(r"(?:\1.+)?", p) 133 p = p.replace(r"\*", "[^/]*") + "(?:/.*)?$" 134 re_cache[pattern] = re.compile(p) 135 return re_cache[pattern].match(path) is not None 136 137 138def rebase(oldbase, base, relativepath): 139 """ 140 Return `relativepath` relative to `base` instead of `oldbase`. 141 """ 142 if base == oldbase: 143 return relativepath 144 if len(base) < len(oldbase): 145 assert basedir(oldbase, [base]) == base 146 relbase = relpath(oldbase, base) 147 result = join(relbase, relativepath) 148 else: 149 assert basedir(base, [oldbase]) == oldbase 150 relbase = relpath(base, oldbase) 151 result = relpath(relativepath, relbase) 152 result = normpath(result) 153 if relativepath.endswith("/") and not result.endswith("/"): 154 result += "/" 155 return result 156 157 158def ancestors(path): 159 """Emit the parent directories of a path. 160 161 Args: 162 path (str): Path to emit parents of. 163 164 Yields: 165 str: Path of parent directory. 166 """ 167 while path: 168 yield path 169 newpath = os.path.dirname(path) 170 if newpath == path: 171 break 172 path = newpath 173