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