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