##// END OF EJS Templates
match: redefine always and never in terms of match and exact
Matt Mackall -
r8585:bbcd0da5 default
parent child Browse files
Show More
@@ -1,250 +1,250 b''
1 1 # match.py - file name matching
2 2 #
3 3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 import util, re
9 9
10 10 class _match(object):
11 11 def __init__(self, root, cwd, files, mf, ap):
12 12 self._root = root
13 13 self._cwd = cwd
14 14 self._files = files
15 15 self._fmap = set(files)
16 16 self.matchfn = mf
17 17 self._anypats = ap
18 18 def __call__(self, fn):
19 19 return self.matchfn(fn)
20 20 def __iter__(self):
21 21 for f in self._files:
22 22 yield f
23 23 def bad(self, f, msg):
24 24 return True
25 25 def dir(self, f):
26 26 pass
27 27 def missing(self, f):
28 28 pass
29 29 def exact(self, f):
30 30 return f in self._fmap
31 31 def rel(self, f):
32 32 return util.pathto(self._root, self._cwd, f)
33 33 def files(self):
34 34 return self._files
35 35 def anypats(self):
36 36 return self._anypats
37 37
38 class always(_match):
39 def __init__(self, root, cwd):
40 _match.__init__(self, root, cwd, [], lambda f: True, False)
41
42 class never(_match):
43 def __init__(self, root, cwd):
44 _match.__init__(self, root, cwd, [], lambda f: False, False)
45
46 class exact(_match):
47 def __init__(self, root, cwd, files):
48 _match.__init__(self, root, cwd, files, self.exact, False)
49
50 38 class match(_match):
51 39 def __init__(self, root, cwd, patterns, include=[], exclude=[],
52 40 default='glob'):
53 41 """build an object to match a set of file patterns
54 42
55 43 arguments:
56 44 root - the canonical root of the tree you're matching against
57 45 cwd - the current working directory, if relevant
58 46 patterns - patterns to find
59 47 include - patterns to include
60 48 exclude - patterns to exclude
61 49 default - if a pattern in names has no explicit type, assume this one
62 50
63 51 a pattern is one of:
64 52 'glob:<glob>' - a glob relative to cwd
65 53 're:<regexp>' - a regular expression
66 54 'path:<path>' - a path relative to canonroot
67 55 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
68 56 'relpath:<path>' - a path relative to cwd
69 57 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
70 58 '<something>' - one of the cases above, selected by the dflt_pat argument
71 59 """
72 60
73 61 roots = []
74 62 anypats = bool(include or exclude)
75 63
76 64 if patterns:
77 65 pats = _normalize(patterns, default, root, cwd)
78 66 roots = _roots(pats)
79 67 anypats = anypats or _anypats(pats)
80 68 pm = _buildmatch(pats, '$')
81 69 if include:
82 70 im = _buildmatch(_normalize(include, 'glob', root, cwd), '(?:/|$)')
83 71 if exclude:
84 72 em = _buildmatch(_normalize(exclude, 'glob', root, cwd), '(?:/|$)')
85 73
86 74 if patterns:
87 75 if include:
88 76 if exclude:
89 77 m = lambda f: im(f) and not em(f) and pm(f)
90 78 else:
91 79 m = lambda f: im(f) and pm(f)
92 80 else:
93 81 if exclude:
94 82 m = lambda f: not em(f) and pm(f)
95 83 else:
96 84 m = pm
97 85 else:
98 86 if include:
99 87 if exclude:
100 88 m = lambda f: im(f) and not em(f)
101 89 else:
102 90 m = im
103 91 else:
104 92 if exclude:
105 93 m = lambda f: not em(f)
106 94 else:
107 95 m = lambda f: True
108 96
109 97 _match.__init__(self, root, cwd, roots, m, anypats)
110 98
99 class exact(_match):
100 def __init__(self, root, cwd, files):
101 _match.__init__(self, root, cwd, files, self.exact, False)
102
103 class always(match):
104 def __init__(self, root, cwd):
105 match.__init__(self, root, cwd, [])
106
107 class never(exact):
108 def __init__(self, root, cwd):
109 exact.__init__(self, root, cwd, [])
110
111 111 def patkind(pat):
112 112 return _patsplit(pat, None)[0]
113 113
114 114 def _patsplit(pat, default):
115 115 """Split a string into an optional pattern kind prefix and the
116 116 actual pattern."""
117 117 if ':' in pat:
118 118 pat, val = pat.split(':', 1)
119 119 if pat in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre'):
120 120 return pat, val
121 121 return default, pat
122 122
123 123 def _globre(pat):
124 124 "convert a glob pattern into a regexp"
125 125 i, n = 0, len(pat)
126 126 res = ''
127 127 group = 0
128 128 escape = re.escape
129 129 def peek(): return i < n and pat[i]
130 130 while i < n:
131 131 c = pat[i]
132 132 i = i+1
133 133 if c not in '*?[{},\\':
134 134 res += escape(c)
135 135 elif c == '*':
136 136 if peek() == '*':
137 137 i += 1
138 138 res += '.*'
139 139 else:
140 140 res += '[^/]*'
141 141 elif c == '?':
142 142 res += '.'
143 143 elif c == '[':
144 144 j = i
145 145 if j < n and pat[j] in '!]':
146 146 j += 1
147 147 while j < n and pat[j] != ']':
148 148 j += 1
149 149 if j >= n:
150 150 res += '\\['
151 151 else:
152 152 stuff = pat[i:j].replace('\\','\\\\')
153 153 i = j + 1
154 154 if stuff[0] == '!':
155 155 stuff = '^' + stuff[1:]
156 156 elif stuff[0] == '^':
157 157 stuff = '\\' + stuff
158 158 res = '%s[%s]' % (res, stuff)
159 159 elif c == '{':
160 160 group += 1
161 161 res += '(?:'
162 162 elif c == '}' and group:
163 163 res += ')'
164 164 group -= 1
165 165 elif c == ',' and group:
166 166 res += '|'
167 167 elif c == '\\':
168 168 p = peek()
169 169 if p:
170 170 i += 1
171 171 res += escape(p)
172 172 else:
173 173 res += escape(c)
174 174 else:
175 175 res += escape(c)
176 176 return res
177 177
178 178 def _regex(kind, name, tail):
179 179 '''convert a pattern into a regular expression'''
180 180 if not name:
181 181 return ''
182 182 if kind == 're':
183 183 return name
184 184 elif kind == 'path':
185 185 return '^' + re.escape(name) + '(?:/|$)'
186 186 elif kind == 'relglob':
187 187 return '(?:|.*/)' + _globre(name) + tail
188 188 elif kind == 'relpath':
189 189 return re.escape(name) + '(?:/|$)'
190 190 elif kind == 'relre':
191 191 if name.startswith('^'):
192 192 return name
193 193 return '.*' + name
194 194 return _globre(name) + tail
195 195
196 196 def _buildmatch(pats, tail):
197 197 """build a matching function from a set of patterns"""
198 198 try:
199 199 pat = '(?:%s)' % '|'.join([_regex(k, p, tail) for (k, p) in pats])
200 200 if len(pat) > 20000:
201 201 raise OverflowError()
202 202 return re.compile(pat).match
203 203 except OverflowError:
204 204 # We're using a Python with a tiny regex engine and we
205 205 # made it explode, so we'll divide the pattern list in two
206 206 # until it works
207 207 l = len(pats)
208 208 if l < 2:
209 209 raise
210 210 a, b = _buildmatch(pats[:l//2], tail), _buildmatch(pats[l//2:], tail)
211 211 return lambda s: a(s) or b(s)
212 212 except re.error:
213 213 for k, p in pats:
214 214 try:
215 215 re.compile('(?:%s)' % _regex(k, p, tail))
216 216 except re.error:
217 217 raise util.Abort("invalid pattern (%s): %s" % (k, p))
218 218 raise util.Abort("invalid pattern")
219 219
220 220 def _normalize(names, default, root, cwd):
221 221 pats = []
222 222 for kind, name in [_patsplit(p, default) for p in names]:
223 223 if kind in ('glob', 'relpath'):
224 224 name = util.canonpath(root, cwd, name)
225 225 elif kind in ('relglob', 'path'):
226 226 name = util.normpath(name)
227 227
228 228 pats.append((kind, name))
229 229 return pats
230 230
231 231 def _roots(patterns):
232 232 r = []
233 233 for kind, name in patterns:
234 234 if kind == 'glob': # find the non-glob prefix
235 235 root = []
236 236 for p in name.split('/'):
237 237 if '[' in p or '{' in p or '*' in p or '?' in p:
238 238 break
239 239 root.append(p)
240 240 r.append('/'.join(root) or '.')
241 241 elif kind in ('relpath', 'path'):
242 242 r.append(name or '.')
243 243 elif kind == 'relglob':
244 244 r.append('.')
245 245 return r
246 246
247 247 def _anypats(patterns):
248 248 for kind, name in patterns:
249 249 if kind in ('glob', 're', 'relglob', 'relre'):
250 250 return True
General Comments 0
You need to be logged in to leave comments. Login now