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