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