##// END OF EJS Templates
fileset: replace predicate by filesetpredicate of registrar (API)...
FUJIWARA Katsunori -
r28448:7108834c default
parent child Browse files
Show More
@@ -1,574 +1,551
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 merge,
15 merge,
16 parser,
16 parser,
17 registrar,
17 util,
18 util,
18 )
19 )
19
20
20 elements = {
21 elements = {
21 # token-type: binding-strength, primary, prefix, infix, suffix
22 # token-type: binding-strength, primary, prefix, infix, suffix
22 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 "-": (5, None, ("negate", 19), ("minus", 5), None),
24 "-": (5, None, ("negate", 19), ("minus", 5), None),
24 "not": (10, None, ("not", 10), None, None),
25 "not": (10, None, ("not", 10), None, None),
25 "!": (10, None, ("not", 10), None, None),
26 "!": (10, None, ("not", 10), None, None),
26 "and": (5, None, None, ("and", 5), None),
27 "and": (5, None, None, ("and", 5), None),
27 "&": (5, None, None, ("and", 5), None),
28 "&": (5, None, None, ("and", 5), None),
28 "or": (4, None, None, ("or", 4), None),
29 "or": (4, None, None, ("or", 4), None),
29 "|": (4, None, None, ("or", 4), None),
30 "|": (4, None, None, ("or", 4), None),
30 "+": (4, None, None, ("or", 4), None),
31 "+": (4, None, None, ("or", 4), None),
31 ",": (2, None, None, ("list", 2), None),
32 ",": (2, None, None, ("list", 2), None),
32 ")": (0, None, None, None, None),
33 ")": (0, None, None, None, None),
33 "symbol": (0, "symbol", None, None, None),
34 "symbol": (0, "symbol", None, None, None),
34 "string": (0, "string", None, None, None),
35 "string": (0, "string", None, None, None),
35 "end": (0, None, None, None, None),
36 "end": (0, None, None, None, None),
36 }
37 }
37
38
38 keywords = set(['and', 'or', 'not'])
39 keywords = set(['and', 'or', 'not'])
39
40
40 globchars = ".*{}[]?/\\_"
41 globchars = ".*{}[]?/\\_"
41
42
42 def tokenize(program):
43 def tokenize(program):
43 pos, l = 0, len(program)
44 pos, l = 0, len(program)
44 while pos < l:
45 while pos < l:
45 c = program[pos]
46 c = program[pos]
46 if c.isspace(): # skip inter-token whitespace
47 if c.isspace(): # skip inter-token whitespace
47 pass
48 pass
48 elif c in "(),-|&+!": # handle simple operators
49 elif c in "(),-|&+!": # handle simple operators
49 yield (c, None, pos)
50 yield (c, None, pos)
50 elif (c in '"\'' or c == 'r' and
51 elif (c in '"\'' or c == 'r' and
51 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
52 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
52 if c == 'r':
53 if c == 'r':
53 pos += 1
54 pos += 1
54 c = program[pos]
55 c = program[pos]
55 decode = lambda x: x
56 decode = lambda x: x
56 else:
57 else:
57 decode = parser.unescapestr
58 decode = parser.unescapestr
58 pos += 1
59 pos += 1
59 s = pos
60 s = pos
60 while pos < l: # find closing quote
61 while pos < l: # find closing quote
61 d = program[pos]
62 d = program[pos]
62 if d == '\\': # skip over escaped characters
63 if d == '\\': # skip over escaped characters
63 pos += 2
64 pos += 2
64 continue
65 continue
65 if d == c:
66 if d == c:
66 yield ('string', decode(program[s:pos]), s)
67 yield ('string', decode(program[s:pos]), s)
67 break
68 break
68 pos += 1
69 pos += 1
69 else:
70 else:
70 raise error.ParseError(_("unterminated string"), s)
71 raise error.ParseError(_("unterminated string"), s)
71 elif c.isalnum() or c in globchars or ord(c) > 127:
72 elif c.isalnum() or c in globchars or ord(c) > 127:
72 # gather up a symbol/keyword
73 # gather up a symbol/keyword
73 s = pos
74 s = pos
74 pos += 1
75 pos += 1
75 while pos < l: # find end of symbol
76 while pos < l: # find end of symbol
76 d = program[pos]
77 d = program[pos]
77 if not (d.isalnum() or d in globchars or ord(d) > 127):
78 if not (d.isalnum() or d in globchars or ord(d) > 127):
78 break
79 break
79 pos += 1
80 pos += 1
80 sym = program[s:pos]
81 sym = program[s:pos]
81 if sym in keywords: # operator keywords
82 if sym in keywords: # operator keywords
82 yield (sym, None, s)
83 yield (sym, None, s)
83 else:
84 else:
84 yield ('symbol', sym, s)
85 yield ('symbol', sym, s)
85 pos -= 1
86 pos -= 1
86 else:
87 else:
87 raise error.ParseError(_("syntax error"), pos)
88 raise error.ParseError(_("syntax error"), pos)
88 pos += 1
89 pos += 1
89 yield ('end', None, pos)
90 yield ('end', None, pos)
90
91
91 def parse(expr):
92 def parse(expr):
92 p = parser.parser(elements)
93 p = parser.parser(elements)
93 tree, pos = p.parse(tokenize(expr))
94 tree, pos = p.parse(tokenize(expr))
94 if pos != len(expr):
95 if pos != len(expr):
95 raise error.ParseError(_("invalid token"), pos)
96 raise error.ParseError(_("invalid token"), pos)
96 return tree
97 return tree
97
98
98 def getstring(x, err):
99 def getstring(x, err):
99 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 return x[1]
101 return x[1]
101 raise error.ParseError(err)
102 raise error.ParseError(err)
102
103
103 def getset(mctx, x):
104 def getset(mctx, x):
104 if not x:
105 if not x:
105 raise error.ParseError(_("missing argument"))
106 raise error.ParseError(_("missing argument"))
106 return methods[x[0]](mctx, *x[1:])
107 return methods[x[0]](mctx, *x[1:])
107
108
108 def stringset(mctx, x):
109 def stringset(mctx, x):
109 m = mctx.matcher([x])
110 m = mctx.matcher([x])
110 return [f for f in mctx.subset if m(f)]
111 return [f for f in mctx.subset if m(f)]
111
112
112 def andset(mctx, x, y):
113 def andset(mctx, x, y):
113 return getset(mctx.narrow(getset(mctx, x)), y)
114 return getset(mctx.narrow(getset(mctx, x)), y)
114
115
115 def orset(mctx, x, y):
116 def orset(mctx, x, y):
116 # needs optimizing
117 # needs optimizing
117 xl = getset(mctx, x)
118 xl = getset(mctx, x)
118 yl = getset(mctx, y)
119 yl = getset(mctx, y)
119 return xl + [f for f in yl if f not in xl]
120 return xl + [f for f in yl if f not in xl]
120
121
121 def notset(mctx, x):
122 def notset(mctx, x):
122 s = set(getset(mctx, x))
123 s = set(getset(mctx, x))
123 return [r for r in mctx.subset if r not in s]
124 return [r for r in mctx.subset if r not in s]
124
125
125 def minusset(mctx, x, y):
126 def minusset(mctx, x, y):
126 xl = getset(mctx, x)
127 xl = getset(mctx, x)
127 yl = set(getset(mctx, y))
128 yl = set(getset(mctx, y))
128 return [f for f in xl if f not in yl]
129 return [f for f in xl if f not in yl]
129
130
130 def listset(mctx, a, b):
131 def listset(mctx, a, b):
131 raise error.ParseError(_("can't use a list in this context"),
132 raise error.ParseError(_("can't use a list in this context"),
132 hint=_('see hg help "filesets.x or y"'))
133 hint=_('see hg help "filesets.x or y"'))
133
134
134 # symbols are callable like:
135 # symbols are callable like:
135 # fun(mctx, x)
136 # fun(mctx, x)
136 # with:
137 # with:
137 # mctx - current matchctx instance
138 # mctx - current matchctx instance
138 # x - argument in tree form
139 # x - argument in tree form
139 symbols = {}
140 symbols = {}
140
141
141 # filesets using matchctx.status()
142 # filesets using matchctx.status()
142 _statuscallers = set()
143 _statuscallers = set()
143
144
144 # filesets using matchctx.existing()
145 # filesets using matchctx.existing()
145 _existingcallers = set()
146 _existingcallers = set()
146
147
147 def predicate(decl, callstatus=False, callexisting=False):
148 predicate = registrar.filesetpredicate()
148 """Return a decorator for fileset predicate function
149
150 'decl' argument is the declaration (including argument list like
151 'adds(pattern)') or the name (for internal use only) of predicate.
152
153 Optional 'callstatus' argument indicates whether predicate implies
154 'matchctx.status()' at runtime or not (False, by default).
155
156 Optional 'callexisting' argument indicates whether predicate
157 implies 'matchctx.existing()' at runtime or not (False, by
158 default).
159 """
160 def decorator(func):
161 i = decl.find('(')
162 if i > 0:
163 name = decl[:i]
164 else:
165 name = decl
166 symbols[name] = func
167 if callstatus:
168 _statuscallers.add(name)
169 if callexisting:
170 _existingcallers.add(name)
171 if func.__doc__:
172 func.__doc__ = "``%s``\n %s" % (decl, func.__doc__.strip())
173 return func
174 return decorator
175
149
176 @predicate('modified()', callstatus=True)
150 @predicate('modified()', callstatus=True)
177 def modified(mctx, x):
151 def modified(mctx, x):
178 """File that is modified according to :hg:`status`.
152 """File that is modified according to :hg:`status`.
179 """
153 """
180 # i18n: "modified" is a keyword
154 # i18n: "modified" is a keyword
181 getargs(x, 0, 0, _("modified takes no arguments"))
155 getargs(x, 0, 0, _("modified takes no arguments"))
182 s = mctx.status().modified
156 s = mctx.status().modified
183 return [f for f in mctx.subset if f in s]
157 return [f for f in mctx.subset if f in s]
184
158
185 @predicate('added()', callstatus=True)
159 @predicate('added()', callstatus=True)
186 def added(mctx, x):
160 def added(mctx, x):
187 """File that is added according to :hg:`status`.
161 """File that is added according to :hg:`status`.
188 """
162 """
189 # i18n: "added" is a keyword
163 # i18n: "added" is a keyword
190 getargs(x, 0, 0, _("added takes no arguments"))
164 getargs(x, 0, 0, _("added takes no arguments"))
191 s = mctx.status().added
165 s = mctx.status().added
192 return [f for f in mctx.subset if f in s]
166 return [f for f in mctx.subset if f in s]
193
167
194 @predicate('removed()', callstatus=True)
168 @predicate('removed()', callstatus=True)
195 def removed(mctx, x):
169 def removed(mctx, x):
196 """File that is removed according to :hg:`status`.
170 """File that is removed according to :hg:`status`.
197 """
171 """
198 # i18n: "removed" is a keyword
172 # i18n: "removed" is a keyword
199 getargs(x, 0, 0, _("removed takes no arguments"))
173 getargs(x, 0, 0, _("removed takes no arguments"))
200 s = mctx.status().removed
174 s = mctx.status().removed
201 return [f for f in mctx.subset if f in s]
175 return [f for f in mctx.subset if f in s]
202
176
203 @predicate('deleted()', callstatus=True)
177 @predicate('deleted()', callstatus=True)
204 def deleted(mctx, x):
178 def deleted(mctx, x):
205 """Alias for ``missing()``.
179 """Alias for ``missing()``.
206 """
180 """
207 # i18n: "deleted" is a keyword
181 # i18n: "deleted" is a keyword
208 getargs(x, 0, 0, _("deleted takes no arguments"))
182 getargs(x, 0, 0, _("deleted takes no arguments"))
209 s = mctx.status().deleted
183 s = mctx.status().deleted
210 return [f for f in mctx.subset if f in s]
184 return [f for f in mctx.subset if f in s]
211
185
212 @predicate('missing()', callstatus=True)
186 @predicate('missing()', callstatus=True)
213 def missing(mctx, x):
187 def missing(mctx, x):
214 """File that is missing according to :hg:`status`.
188 """File that is missing according to :hg:`status`.
215 """
189 """
216 # i18n: "missing" is a keyword
190 # i18n: "missing" is a keyword
217 getargs(x, 0, 0, _("missing takes no arguments"))
191 getargs(x, 0, 0, _("missing takes no arguments"))
218 s = mctx.status().deleted
192 s = mctx.status().deleted
219 return [f for f in mctx.subset if f in s]
193 return [f for f in mctx.subset if f in s]
220
194
221 @predicate('unknown()', callstatus=True)
195 @predicate('unknown()', callstatus=True)
222 def unknown(mctx, x):
196 def unknown(mctx, x):
223 """File that is unknown according to :hg:`status`. These files will only be
197 """File that is unknown according to :hg:`status`. These files will only be
224 considered if this predicate is used.
198 considered if this predicate is used.
225 """
199 """
226 # i18n: "unknown" is a keyword
200 # i18n: "unknown" is a keyword
227 getargs(x, 0, 0, _("unknown takes no arguments"))
201 getargs(x, 0, 0, _("unknown takes no arguments"))
228 s = mctx.status().unknown
202 s = mctx.status().unknown
229 return [f for f in mctx.subset if f in s]
203 return [f for f in mctx.subset if f in s]
230
204
231 @predicate('ignored()', callstatus=True)
205 @predicate('ignored()', callstatus=True)
232 def ignored(mctx, x):
206 def ignored(mctx, x):
233 """File that is ignored according to :hg:`status`. These files will only be
207 """File that is ignored according to :hg:`status`. These files will only be
234 considered if this predicate is used.
208 considered if this predicate is used.
235 """
209 """
236 # i18n: "ignored" is a keyword
210 # i18n: "ignored" is a keyword
237 getargs(x, 0, 0, _("ignored takes no arguments"))
211 getargs(x, 0, 0, _("ignored takes no arguments"))
238 s = mctx.status().ignored
212 s = mctx.status().ignored
239 return [f for f in mctx.subset if f in s]
213 return [f for f in mctx.subset if f in s]
240
214
241 @predicate('clean()', callstatus=True)
215 @predicate('clean()', callstatus=True)
242 def clean(mctx, x):
216 def clean(mctx, x):
243 """File that is clean according to :hg:`status`.
217 """File that is clean according to :hg:`status`.
244 """
218 """
245 # i18n: "clean" is a keyword
219 # i18n: "clean" is a keyword
246 getargs(x, 0, 0, _("clean takes no arguments"))
220 getargs(x, 0, 0, _("clean takes no arguments"))
247 s = mctx.status().clean
221 s = mctx.status().clean
248 return [f for f in mctx.subset if f in s]
222 return [f for f in mctx.subset if f in s]
249
223
250 def func(mctx, a, b):
224 def func(mctx, a, b):
251 if a[0] == 'symbol' and a[1] in symbols:
225 if a[0] == 'symbol' and a[1] in symbols:
252 funcname = a[1]
226 funcname = a[1]
253 enabled = mctx._existingenabled
227 enabled = mctx._existingenabled
254 mctx._existingenabled = funcname in _existingcallers
228 mctx._existingenabled = funcname in _existingcallers
255 try:
229 try:
256 return symbols[funcname](mctx, b)
230 return symbols[funcname](mctx, b)
257 finally:
231 finally:
258 mctx._existingenabled = enabled
232 mctx._existingenabled = enabled
259
233
260 keep = lambda fn: getattr(fn, '__doc__', None) is not None
234 keep = lambda fn: getattr(fn, '__doc__', None) is not None
261
235
262 syms = [s for (s, fn) in symbols.items() if keep(fn)]
236 syms = [s for (s, fn) in symbols.items() if keep(fn)]
263 raise error.UnknownIdentifier(a[1], syms)
237 raise error.UnknownIdentifier(a[1], syms)
264
238
265 def getlist(x):
239 def getlist(x):
266 if not x:
240 if not x:
267 return []
241 return []
268 if x[0] == 'list':
242 if x[0] == 'list':
269 return getlist(x[1]) + [x[2]]
243 return getlist(x[1]) + [x[2]]
270 return [x]
244 return [x]
271
245
272 def getargs(x, min, max, err):
246 def getargs(x, min, max, err):
273 l = getlist(x)
247 l = getlist(x)
274 if len(l) < min or len(l) > max:
248 if len(l) < min or len(l) > max:
275 raise error.ParseError(err)
249 raise error.ParseError(err)
276 return l
250 return l
277
251
278 @predicate('binary()', callexisting=True)
252 @predicate('binary()', callexisting=True)
279 def binary(mctx, x):
253 def binary(mctx, x):
280 """File that appears to be binary (contains NUL bytes).
254 """File that appears to be binary (contains NUL bytes).
281 """
255 """
282 # i18n: "binary" is a keyword
256 # i18n: "binary" is a keyword
283 getargs(x, 0, 0, _("binary takes no arguments"))
257 getargs(x, 0, 0, _("binary takes no arguments"))
284 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
258 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
285
259
286 @predicate('exec()', callexisting=True)
260 @predicate('exec()', callexisting=True)
287 def exec_(mctx, x):
261 def exec_(mctx, x):
288 """File that is marked as executable.
262 """File that is marked as executable.
289 """
263 """
290 # i18n: "exec" is a keyword
264 # i18n: "exec" is a keyword
291 getargs(x, 0, 0, _("exec takes no arguments"))
265 getargs(x, 0, 0, _("exec takes no arguments"))
292 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
266 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
293
267
294 @predicate('symlink()', callexisting=True)
268 @predicate('symlink()', callexisting=True)
295 def symlink(mctx, x):
269 def symlink(mctx, x):
296 """File that is marked as a symlink.
270 """File that is marked as a symlink.
297 """
271 """
298 # i18n: "symlink" is a keyword
272 # i18n: "symlink" is a keyword
299 getargs(x, 0, 0, _("symlink takes no arguments"))
273 getargs(x, 0, 0, _("symlink takes no arguments"))
300 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
274 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
301
275
302 @predicate('resolved()')
276 @predicate('resolved()')
303 def resolved(mctx, x):
277 def resolved(mctx, x):
304 """File that is marked resolved according to :hg:`resolve -l`.
278 """File that is marked resolved according to :hg:`resolve -l`.
305 """
279 """
306 # i18n: "resolved" is a keyword
280 # i18n: "resolved" is a keyword
307 getargs(x, 0, 0, _("resolved takes no arguments"))
281 getargs(x, 0, 0, _("resolved takes no arguments"))
308 if mctx.ctx.rev() is not None:
282 if mctx.ctx.rev() is not None:
309 return []
283 return []
310 ms = merge.mergestate.read(mctx.ctx.repo())
284 ms = merge.mergestate.read(mctx.ctx.repo())
311 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
285 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
312
286
313 @predicate('unresolved()')
287 @predicate('unresolved()')
314 def unresolved(mctx, x):
288 def unresolved(mctx, x):
315 """File that is marked unresolved according to :hg:`resolve -l`.
289 """File that is marked unresolved according to :hg:`resolve -l`.
316 """
290 """
317 # i18n: "unresolved" is a keyword
291 # i18n: "unresolved" is a keyword
318 getargs(x, 0, 0, _("unresolved takes no arguments"))
292 getargs(x, 0, 0, _("unresolved takes no arguments"))
319 if mctx.ctx.rev() is not None:
293 if mctx.ctx.rev() is not None:
320 return []
294 return []
321 ms = merge.mergestate.read(mctx.ctx.repo())
295 ms = merge.mergestate.read(mctx.ctx.repo())
322 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
296 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
323
297
324 @predicate('hgignore()')
298 @predicate('hgignore()')
325 def hgignore(mctx, x):
299 def hgignore(mctx, x):
326 """File that matches the active .hgignore pattern.
300 """File that matches the active .hgignore pattern.
327 """
301 """
328 # i18n: "hgignore" is a keyword
302 # i18n: "hgignore" is a keyword
329 getargs(x, 0, 0, _("hgignore takes no arguments"))
303 getargs(x, 0, 0, _("hgignore takes no arguments"))
330 ignore = mctx.ctx.repo().dirstate._ignore
304 ignore = mctx.ctx.repo().dirstate._ignore
331 return [f for f in mctx.subset if ignore(f)]
305 return [f for f in mctx.subset if ignore(f)]
332
306
333 @predicate('portable()')
307 @predicate('portable()')
334 def portable(mctx, x):
308 def portable(mctx, x):
335 """File that has a portable name. (This doesn't include filenames with case
309 """File that has a portable name. (This doesn't include filenames with case
336 collisions.)
310 collisions.)
337 """
311 """
338 # i18n: "portable" is a keyword
312 # i18n: "portable" is a keyword
339 getargs(x, 0, 0, _("portable takes no arguments"))
313 getargs(x, 0, 0, _("portable takes no arguments"))
340 checkwinfilename = util.checkwinfilename
314 checkwinfilename = util.checkwinfilename
341 return [f for f in mctx.subset if checkwinfilename(f) is None]
315 return [f for f in mctx.subset if checkwinfilename(f) is None]
342
316
343 @predicate('grep(regex)', callexisting=True)
317 @predicate('grep(regex)', callexisting=True)
344 def grep(mctx, x):
318 def grep(mctx, x):
345 """File contains the given regular expression.
319 """File contains the given regular expression.
346 """
320 """
347 try:
321 try:
348 # i18n: "grep" is a keyword
322 # i18n: "grep" is a keyword
349 r = re.compile(getstring(x, _("grep requires a pattern")))
323 r = re.compile(getstring(x, _("grep requires a pattern")))
350 except re.error as e:
324 except re.error as e:
351 raise error.ParseError(_('invalid match pattern: %s') % e)
325 raise error.ParseError(_('invalid match pattern: %s') % e)
352 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
326 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
353
327
354 def _sizetomax(s):
328 def _sizetomax(s):
355 try:
329 try:
356 s = s.strip().lower()
330 s = s.strip().lower()
357 for k, v in util._sizeunits:
331 for k, v in util._sizeunits:
358 if s.endswith(k):
332 if s.endswith(k):
359 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
333 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
360 n = s[:-len(k)]
334 n = s[:-len(k)]
361 inc = 1.0
335 inc = 1.0
362 if "." in n:
336 if "." in n:
363 inc /= 10 ** len(n.split(".")[1])
337 inc /= 10 ** len(n.split(".")[1])
364 return int((float(n) + inc) * v) - 1
338 return int((float(n) + inc) * v) - 1
365 # no extension, this is a precise value
339 # no extension, this is a precise value
366 return int(s)
340 return int(s)
367 except ValueError:
341 except ValueError:
368 raise error.ParseError(_("couldn't parse size: %s") % s)
342 raise error.ParseError(_("couldn't parse size: %s") % s)
369
343
370 @predicate('size(expression)', callexisting=True)
344 @predicate('size(expression)', callexisting=True)
371 def size(mctx, x):
345 def size(mctx, x):
372 """File size matches the given expression. Examples:
346 """File size matches the given expression. Examples:
373
347
374 - 1k (files from 1024 to 2047 bytes)
348 - 1k (files from 1024 to 2047 bytes)
375 - < 20k (files less than 20480 bytes)
349 - < 20k (files less than 20480 bytes)
376 - >= .5MB (files at least 524288 bytes)
350 - >= .5MB (files at least 524288 bytes)
377 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
351 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
378 """
352 """
379
353
380 # i18n: "size" is a keyword
354 # i18n: "size" is a keyword
381 expr = getstring(x, _("size requires an expression")).strip()
355 expr = getstring(x, _("size requires an expression")).strip()
382 if '-' in expr: # do we have a range?
356 if '-' in expr: # do we have a range?
383 a, b = expr.split('-', 1)
357 a, b = expr.split('-', 1)
384 a = util.sizetoint(a)
358 a = util.sizetoint(a)
385 b = util.sizetoint(b)
359 b = util.sizetoint(b)
386 m = lambda x: x >= a and x <= b
360 m = lambda x: x >= a and x <= b
387 elif expr.startswith("<="):
361 elif expr.startswith("<="):
388 a = util.sizetoint(expr[2:])
362 a = util.sizetoint(expr[2:])
389 m = lambda x: x <= a
363 m = lambda x: x <= a
390 elif expr.startswith("<"):
364 elif expr.startswith("<"):
391 a = util.sizetoint(expr[1:])
365 a = util.sizetoint(expr[1:])
392 m = lambda x: x < a
366 m = lambda x: x < a
393 elif expr.startswith(">="):
367 elif expr.startswith(">="):
394 a = util.sizetoint(expr[2:])
368 a = util.sizetoint(expr[2:])
395 m = lambda x: x >= a
369 m = lambda x: x >= a
396 elif expr.startswith(">"):
370 elif expr.startswith(">"):
397 a = util.sizetoint(expr[1:])
371 a = util.sizetoint(expr[1:])
398 m = lambda x: x > a
372 m = lambda x: x > a
399 elif expr[0].isdigit or expr[0] == '.':
373 elif expr[0].isdigit or expr[0] == '.':
400 a = util.sizetoint(expr)
374 a = util.sizetoint(expr)
401 b = _sizetomax(expr)
375 b = _sizetomax(expr)
402 m = lambda x: x >= a and x <= b
376 m = lambda x: x >= a and x <= b
403 else:
377 else:
404 raise error.ParseError(_("couldn't parse size: %s") % expr)
378 raise error.ParseError(_("couldn't parse size: %s") % expr)
405
379
406 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
380 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
407
381
408 @predicate('encoding(name)', callexisting=True)
382 @predicate('encoding(name)', callexisting=True)
409 def encoding(mctx, x):
383 def encoding(mctx, x):
410 """File can be successfully decoded with the given character
384 """File can be successfully decoded with the given character
411 encoding. May not be useful for encodings other than ASCII and
385 encoding. May not be useful for encodings other than ASCII and
412 UTF-8.
386 UTF-8.
413 """
387 """
414
388
415 # i18n: "encoding" is a keyword
389 # i18n: "encoding" is a keyword
416 enc = getstring(x, _("encoding requires an encoding name"))
390 enc = getstring(x, _("encoding requires an encoding name"))
417
391
418 s = []
392 s = []
419 for f in mctx.existing():
393 for f in mctx.existing():
420 d = mctx.ctx[f].data()
394 d = mctx.ctx[f].data()
421 try:
395 try:
422 d.decode(enc)
396 d.decode(enc)
423 except LookupError:
397 except LookupError:
424 raise error.Abort(_("unknown encoding '%s'") % enc)
398 raise error.Abort(_("unknown encoding '%s'") % enc)
425 except UnicodeDecodeError:
399 except UnicodeDecodeError:
426 continue
400 continue
427 s.append(f)
401 s.append(f)
428
402
429 return s
403 return s
430
404
431 @predicate('eol(style)', callexisting=True)
405 @predicate('eol(style)', callexisting=True)
432 def eol(mctx, x):
406 def eol(mctx, x):
433 """File contains newlines of the given style (dos, unix, mac). Binary
407 """File contains newlines of the given style (dos, unix, mac). Binary
434 files are excluded, files with mixed line endings match multiple
408 files are excluded, files with mixed line endings match multiple
435 styles.
409 styles.
436 """
410 """
437
411
438 # i18n: "eol" is a keyword
412 # i18n: "eol" is a keyword
439 enc = getstring(x, _("eol requires a style name"))
413 enc = getstring(x, _("eol requires a style name"))
440
414
441 s = []
415 s = []
442 for f in mctx.existing():
416 for f in mctx.existing():
443 d = mctx.ctx[f].data()
417 d = mctx.ctx[f].data()
444 if util.binary(d):
418 if util.binary(d):
445 continue
419 continue
446 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
420 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
447 s.append(f)
421 s.append(f)
448 elif enc == 'unix' and re.search('(?<!\r)\n', d):
422 elif enc == 'unix' and re.search('(?<!\r)\n', d):
449 s.append(f)
423 s.append(f)
450 elif enc == 'mac' and re.search('\r(?!\n)', d):
424 elif enc == 'mac' and re.search('\r(?!\n)', d):
451 s.append(f)
425 s.append(f)
452 return s
426 return s
453
427
454 @predicate('copied()')
428 @predicate('copied()')
455 def copied(mctx, x):
429 def copied(mctx, x):
456 """File that is recorded as being copied.
430 """File that is recorded as being copied.
457 """
431 """
458 # i18n: "copied" is a keyword
432 # i18n: "copied" is a keyword
459 getargs(x, 0, 0, _("copied takes no arguments"))
433 getargs(x, 0, 0, _("copied takes no arguments"))
460 s = []
434 s = []
461 for f in mctx.subset:
435 for f in mctx.subset:
462 p = mctx.ctx[f].parents()
436 p = mctx.ctx[f].parents()
463 if p and p[0].path() != f:
437 if p and p[0].path() != f:
464 s.append(f)
438 s.append(f)
465 return s
439 return s
466
440
467 @predicate('subrepo([pattern])')
441 @predicate('subrepo([pattern])')
468 def subrepo(mctx, x):
442 def subrepo(mctx, x):
469 """Subrepositories whose paths match the given pattern.
443 """Subrepositories whose paths match the given pattern.
470 """
444 """
471 # i18n: "subrepo" is a keyword
445 # i18n: "subrepo" is a keyword
472 getargs(x, 0, 1, _("subrepo takes at most one argument"))
446 getargs(x, 0, 1, _("subrepo takes at most one argument"))
473 ctx = mctx.ctx
447 ctx = mctx.ctx
474 sstate = sorted(ctx.substate)
448 sstate = sorted(ctx.substate)
475 if x:
449 if x:
476 # i18n: "subrepo" is a keyword
450 # i18n: "subrepo" is a keyword
477 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
451 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
478
452
479 from . import match as matchmod # avoid circular import issues
453 from . import match as matchmod # avoid circular import issues
480 fast = not matchmod.patkind(pat)
454 fast = not matchmod.patkind(pat)
481 if fast:
455 if fast:
482 def m(s):
456 def m(s):
483 return (s == pat)
457 return (s == pat)
484 else:
458 else:
485 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
459 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
486 return [sub for sub in sstate if m(sub)]
460 return [sub for sub in sstate if m(sub)]
487 else:
461 else:
488 return [sub for sub in sstate]
462 return [sub for sub in sstate]
489
463
490 methods = {
464 methods = {
491 'string': stringset,
465 'string': stringset,
492 'symbol': stringset,
466 'symbol': stringset,
493 'and': andset,
467 'and': andset,
494 'or': orset,
468 'or': orset,
495 'minus': minusset,
469 'minus': minusset,
496 'list': listset,
470 'list': listset,
497 'group': getset,
471 'group': getset,
498 'not': notset,
472 'not': notset,
499 'func': func,
473 'func': func,
500 }
474 }
501
475
502 class matchctx(object):
476 class matchctx(object):
503 def __init__(self, ctx, subset=None, status=None):
477 def __init__(self, ctx, subset=None, status=None):
504 self.ctx = ctx
478 self.ctx = ctx
505 self.subset = subset
479 self.subset = subset
506 self._status = status
480 self._status = status
507 self._existingenabled = False
481 self._existingenabled = False
508 def status(self):
482 def status(self):
509 return self._status
483 return self._status
510 def matcher(self, patterns):
484 def matcher(self, patterns):
511 return self.ctx.match(patterns)
485 return self.ctx.match(patterns)
512 def filter(self, files):
486 def filter(self, files):
513 return [f for f in files if f in self.subset]
487 return [f for f in files if f in self.subset]
514 def existing(self):
488 def existing(self):
515 assert self._existingenabled, 'unexpected existing() invocation'
489 assert self._existingenabled, 'unexpected existing() invocation'
516 if self._status is not None:
490 if self._status is not None:
517 removed = set(self._status[3])
491 removed = set(self._status[3])
518 unknown = set(self._status[4] + self._status[5])
492 unknown = set(self._status[4] + self._status[5])
519 else:
493 else:
520 removed = set()
494 removed = set()
521 unknown = set()
495 unknown = set()
522 return (f for f in self.subset
496 return (f for f in self.subset
523 if (f in self.ctx and f not in removed) or f in unknown)
497 if (f in self.ctx and f not in removed) or f in unknown)
524 def narrow(self, files):
498 def narrow(self, files):
525 return matchctx(self.ctx, self.filter(files), self._status)
499 return matchctx(self.ctx, self.filter(files), self._status)
526
500
527 def _intree(funcs, tree):
501 def _intree(funcs, tree):
528 if isinstance(tree, tuple):
502 if isinstance(tree, tuple):
529 if tree[0] == 'func' and tree[1][0] == 'symbol':
503 if tree[0] == 'func' and tree[1][0] == 'symbol':
530 if tree[1][1] in funcs:
504 if tree[1][1] in funcs:
531 return True
505 return True
532 for s in tree[1:]:
506 for s in tree[1:]:
533 if _intree(funcs, s):
507 if _intree(funcs, s):
534 return True
508 return True
535 return False
509 return False
536
510
537 def getfileset(ctx, expr):
511 def getfileset(ctx, expr):
538 tree = parse(expr)
512 tree = parse(expr)
539
513
540 # do we need status info?
514 # do we need status info?
541 if (_intree(_statuscallers, tree) or
515 if (_intree(_statuscallers, tree) or
542 # Using matchctx.existing() on a workingctx requires us to check
516 # Using matchctx.existing() on a workingctx requires us to check
543 # for deleted files.
517 # for deleted files.
544 (ctx.rev() is None and _intree(_existingcallers, tree))):
518 (ctx.rev() is None and _intree(_existingcallers, tree))):
545 unknown = _intree(['unknown'], tree)
519 unknown = _intree(['unknown'], tree)
546 ignored = _intree(['ignored'], tree)
520 ignored = _intree(['ignored'], tree)
547
521
548 r = ctx.repo()
522 r = ctx.repo()
549 status = r.status(ctx.p1(), ctx,
523 status = r.status(ctx.p1(), ctx,
550 unknown=unknown, ignored=ignored, clean=True)
524 unknown=unknown, ignored=ignored, clean=True)
551 subset = []
525 subset = []
552 for c in status:
526 for c in status:
553 subset.extend(c)
527 subset.extend(c)
554 else:
528 else:
555 status = None
529 status = None
556 subset = list(ctx.walk(ctx.match([])))
530 subset = list(ctx.walk(ctx.match([])))
557
531
558 return getset(matchctx(ctx, subset, status), tree)
532 return getset(matchctx(ctx, subset, status), tree)
559
533
560 def prettyformat(tree):
534 def prettyformat(tree):
561 return parser.prettyformat(tree, ('string', 'symbol'))
535 return parser.prettyformat(tree, ('string', 'symbol'))
562
536
563 def loadpredicate(ui, extname, registrarobj):
537 def loadpredicate(ui, extname, registrarobj):
564 """Load fileset predicates from specified registrarobj
538 """Load fileset predicates from specified registrarobj
565 """
539 """
566 for name, func in registrarobj._table.iteritems():
540 for name, func in registrarobj._table.iteritems():
567 symbols[name] = func
541 symbols[name] = func
568 if func._callstatus:
542 if func._callstatus:
569 _statuscallers.add(name)
543 _statuscallers.add(name)
570 if func._callexisting:
544 if func._callexisting:
571 _existingcallers.add(name)
545 _existingcallers.add(name)
572
546
547 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
548 loadpredicate(None, None, predicate)
549
573 # tell hggettext to extract docstrings from these functions:
550 # tell hggettext to extract docstrings from these functions:
574 i18nfunctions = symbols.values()
551 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now