match.py
251 lines
| 7.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / match.py
|
r8231 | # match.py - file name matching | ||
# | ||||
# Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2, incorporated herein by reference. | ||||
|
r8570 | import util, re | ||
|
r6576 | |||
|
r6604 | class _match(object): | ||
def __init__(self, root, cwd, files, mf, ap): | ||||
|
r6576 | self._root = root | ||
self._cwd = cwd | ||||
|
r6604 | self._files = files | ||
|
r8152 | self._fmap = set(files) | ||
|
r6834 | self.matchfn = mf | ||
|
r6576 | self._anypats = ap | ||
def __call__(self, fn): | ||||
|
r6834 | return self.matchfn(fn) | ||
|
r6576 | def __iter__(self): | ||
for f in self._files: | ||||
yield f | ||||
def bad(self, f, msg): | ||||
return True | ||||
def dir(self, f): | ||||
pass | ||||
def missing(self, f): | ||||
pass | ||||
def exact(self, f): | ||||
return f in self._fmap | ||||
def rel(self, f): | ||||
return util.pathto(self._root, self._cwd, f) | ||||
def files(self): | ||||
return self._files | ||||
def anypats(self): | ||||
return self._anypats | ||||
|
r6596 | |||
|
r6604 | class always(_match): | ||
def __init__(self, root, cwd): | ||||
_match.__init__(self, root, cwd, [], lambda f: True, False) | ||||
class never(_match): | ||||
def __init__(self, root, cwd): | ||||
_match.__init__(self, root, cwd, [], lambda f: False, False) | ||||
|
r6596 | |||
|
r6604 | class exact(_match): | ||
def __init__(self, root, cwd, files): | ||||
|
r8522 | _match.__init__(self, root, cwd, files, self.exact, False) | ||
|
r6596 | |||
|
r6604 | class match(_match): | ||
|
r8567 | def __init__(self, root, cwd, patterns, include=[], exclude=[], | ||
default='glob'): | ||||
|
r8581 | """build an object to match a set of file patterns | ||
arguments: | ||||
root - the canonical root of the tree you're matching against | ||||
cwd - the current working directory, if relevant | ||||
patterns - patterns to find | ||||
include - patterns to include | ||||
exclude - patterns to exclude | ||||
default - if a pattern in names has no explicit type, assume this one | ||||
a pattern is one of: | ||||
'glob:<glob>' - a glob relative to cwd | ||||
're:<regexp>' - a regular expression | ||||
'path:<path>' - a path relative to canonroot | ||||
'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs) | ||||
'relpath:<path>' - a path relative to cwd | ||||
'relre:<regexp>' - a regexp that doesn't have to match the start of a name | ||||
'<something>' - one of the cases above, selected by the dflt_pat argument | ||||
""" | ||||
roots = [] | ||||
anypats = bool(include or exclude) | ||||
if patterns: | ||||
pats = _normalize(patterns, default, root, cwd) | ||||
roots = _roots(pats) | ||||
anypats = anypats or _anypats(pats) | ||||
pm = _buildmatch(pats, '$') | ||||
if include: | ||||
im = _buildmatch(_normalize(include, 'glob', root, cwd), '(?:/|$)') | ||||
if exclude: | ||||
em = _buildmatch(_normalize(exclude, 'glob', root, cwd), '(?:/|$)') | ||||
if patterns: | ||||
if include: | ||||
if exclude: | ||||
m = lambda f: im(f) and not em(f) and pm(f) | ||||
else: | ||||
m = lambda f: im(f) and pm(f) | ||||
else: | ||||
if exclude: | ||||
m = lambda f: not em(f) and pm(f) | ||||
else: | ||||
m = pm | ||||
else: | ||||
if include: | ||||
if exclude: | ||||
m = lambda f: im(f) and not em(f) | ||||
else: | ||||
m = im | ||||
else: | ||||
if exclude: | ||||
m = lambda f: not em(f) | ||||
else: | ||||
m = lambda f: True | ||||
_match.__init__(self, root, cwd, roots, m, anypats) | ||||
|
r8568 | |||
def patkind(pat): | ||||
|
r8570 | return _patsplit(pat, None)[0] | ||
def _patsplit(pat, default): | ||||
"""Split a string into an optional pattern kind prefix and the | ||||
actual pattern.""" | ||||
|
r8579 | if ':' in pat: | ||
pat, val = pat.split(':', 1) | ||||
if pat in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre'): | ||||
return pat, val | ||||
|
r8570 | return default, pat | ||
|
r8573 | def _globre(pat, head, tail): | ||
|
r8570 | "convert a glob pattern into a regexp" | ||
i, n = 0, len(pat) | ||||
res = '' | ||||
group = 0 | ||||
def peek(): return i < n and pat[i] | ||||
while i < n: | ||||
c = pat[i] | ||||
i = i+1 | ||||
if c == '*': | ||||
if peek() == '*': | ||||
i += 1 | ||||
res += '.*' | ||||
else: | ||||
res += '[^/]*' | ||||
elif c == '?': | ||||
res += '.' | ||||
elif c == '[': | ||||
j = i | ||||
if j < n and pat[j] in '!]': | ||||
j += 1 | ||||
while j < n and pat[j] != ']': | ||||
j += 1 | ||||
if j >= n: | ||||
res += '\\[' | ||||
else: | ||||
stuff = pat[i:j].replace('\\','\\\\') | ||||
i = j + 1 | ||||
if stuff[0] == '!': | ||||
stuff = '^' + stuff[1:] | ||||
elif stuff[0] == '^': | ||||
stuff = '\\' + stuff | ||||
res = '%s[%s]' % (res, stuff) | ||||
elif c == '{': | ||||
group += 1 | ||||
res += '(?:' | ||||
elif c == '}' and group: | ||||
res += ')' | ||||
group -= 1 | ||||
elif c == ',' and group: | ||||
res += '|' | ||||
elif c == '\\': | ||||
p = peek() | ||||
if p: | ||||
i += 1 | ||||
res += re.escape(p) | ||||
else: | ||||
res += re.escape(c) | ||||
else: | ||||
res += re.escape(c) | ||||
return head + res + tail | ||||
|
r8574 | def _regex(kind, name, tail): | ||
'''convert a pattern into a regular expression''' | ||||
if not name: | ||||
return '' | ||||
if kind == 're': | ||||
return name | ||||
elif kind == 'path': | ||||
return '^' + re.escape(name) + '(?:/|$)' | ||||
elif kind == 'relglob': | ||||
return _globre(name, '(?:|.*/)', tail) | ||||
elif kind == 'relpath': | ||||
return re.escape(name) + '(?:/|$)' | ||||
elif kind == 'relre': | ||||
if name.startswith('^'): | ||||
return name | ||||
return '.*' + name | ||||
return _globre(name, '', tail) | ||||
|
r8580 | def _buildmatch(pats, tail): | ||
|
r8574 | """build a matching function from a set of patterns""" | ||
try: | ||||
pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats]) | ||||
if len(pat) > 20000: | ||||
raise OverflowError() | ||||
return re.compile(pat).match | ||||
except OverflowError: | ||||
# We're using a Python with a tiny regex engine and we | ||||
# made it explode, so we'll divide the pattern list in two | ||||
# until it works | ||||
l = len(pats) | ||||
if l < 2: | ||||
raise | ||||
|
r8580 | a, b = _buildmatch(pats[:l//2], tail), _buildmatch(pats[l//2:], tail) | ||
|
r8574 | return lambda s: a(s) or b(s) | ||
except re.error: | ||||
for k, p in pats: | ||||
try: | ||||
re.compile('(?:%s)' % _regex(k, p, tail)) | ||||
except re.error: | ||||
raise util.Abort("invalid pattern (%s): %s" % (k, p)) | ||||
raise util.Abort("invalid pattern") | ||||
def _globprefix(pat): | ||||
'''return the non-glob prefix of a path, e.g. foo/* -> foo''' | ||||
root = [] | ||||
for p in pat.split('/'): | ||||
|
r8575 | if '[' in p or '{' in p or '*' in p or '?' in p: | ||
break | ||||
|
r8574 | root.append(p) | ||
return '/'.join(root) or '.' | ||||
|
r8578 | def _normalize(names, default, root, cwd): | ||
|
r8574 | pats = [] | ||
for kind, name in [_patsplit(p, default) for p in names]: | ||||
if kind in ('glob', 'relpath'): | ||||
|
r8578 | name = util.canonpath(root, cwd, name) | ||
|
r8574 | elif kind in ('relglob', 'path'): | ||
name = util.normpath(name) | ||||
pats.append((kind, name)) | ||||
|
r8576 | return pats | ||
|
r8574 | |||
|
r8576 | def _roots(patterns): | ||
r = [] | ||||
for kind, name in patterns: | ||||
|
r8574 | if kind == 'glob': | ||
|
r8576 | r.append(_globprefix(name)) | ||
|
r8574 | elif kind in ('relpath', 'path'): | ||
|
r8576 | r.append(name or '.') | ||
|
r8574 | elif kind == 'relglob': | ||
|
r8576 | r.append('.') | ||
return r | ||||
def _anypats(patterns): | ||||
for kind, name in patterns: | ||||
if kind in ('glob', 're', 'relglob', 'relre'): | ||||
return True | ||||