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