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