##// END OF EJS Templates
py3: fix slicing of bytes in revset.formatspec()
Yuya Nishihara -
r31385:1c48a827 default
parent child Browse files
Show More
@@ -1,689 +1,689 b''
1 1 # revsetlang.py - parser, tokenizer and utility for revision set language
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 string
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 node,
16 16 parser,
17 17 pycompat,
18 18 )
19 19
20 20 elements = {
21 21 # token-type: binding-strength, primary, prefix, infix, suffix
22 22 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 23 "##": (20, None, None, ("_concat", 20), None),
24 24 "~": (18, None, None, ("ancestor", 18), None),
25 25 "^": (18, None, None, ("parent", 18), "parentpost"),
26 26 "-": (5, None, ("negate", 19), ("minus", 5), None),
27 27 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
28 28 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17), "dagrangepost"),
29 29 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), "rangepost"),
30 30 "not": (10, None, ("not", 10), None, None),
31 31 "!": (10, None, ("not", 10), None, None),
32 32 "and": (5, None, None, ("and", 5), None),
33 33 "&": (5, None, None, ("and", 5), None),
34 34 "%": (5, None, None, ("only", 5), "onlypost"),
35 35 "or": (4, None, None, ("or", 4), None),
36 36 "|": (4, None, None, ("or", 4), None),
37 37 "+": (4, None, None, ("or", 4), None),
38 38 "=": (3, None, None, ("keyvalue", 3), None),
39 39 ",": (2, None, None, ("list", 2), None),
40 40 ")": (0, None, None, None, None),
41 41 "symbol": (0, "symbol", None, None, None),
42 42 "string": (0, "string", None, None, None),
43 43 "end": (0, None, None, None, None),
44 44 }
45 45
46 46 keywords = set(['and', 'or', 'not'])
47 47
48 48 _quoteletters = set(['"', "'"])
49 49 _simpleopletters = set(pycompat.iterbytestr("():=,-|&+!~^%"))
50 50
51 51 # default set of valid characters for the initial letter of symbols
52 52 _syminitletters = set(pycompat.iterbytestr(
53 53 string.ascii_letters.encode('ascii') +
54 54 string.digits.encode('ascii') +
55 55 '._@')) | set(map(pycompat.bytechr, xrange(128, 256)))
56 56
57 57 # default set of valid characters for non-initial letters of symbols
58 58 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
59 59
60 60 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
61 61 '''
62 62 Parse a revset statement into a stream of tokens
63 63
64 64 ``syminitletters`` is the set of valid characters for the initial
65 65 letter of symbols.
66 66
67 67 By default, character ``c`` is recognized as valid for initial
68 68 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
69 69
70 70 ``symletters`` is the set of valid characters for non-initial
71 71 letters of symbols.
72 72
73 73 By default, character ``c`` is recognized as valid for non-initial
74 74 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
75 75
76 76 Check that @ is a valid unquoted token character (issue3686):
77 77 >>> list(tokenize("@::"))
78 78 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
79 79
80 80 '''
81 81 if syminitletters is None:
82 82 syminitletters = _syminitletters
83 83 if symletters is None:
84 84 symletters = _symletters
85 85
86 86 if program and lookup:
87 87 # attempt to parse old-style ranges first to deal with
88 88 # things like old-tag which contain query metacharacters
89 89 parts = program.split(':', 1)
90 90 if all(lookup(sym) for sym in parts if sym):
91 91 if parts[0]:
92 92 yield ('symbol', parts[0], 0)
93 93 if len(parts) > 1:
94 94 s = len(parts[0])
95 95 yield (':', None, s)
96 96 if parts[1]:
97 97 yield ('symbol', parts[1], s + 1)
98 98 yield ('end', None, len(program))
99 99 return
100 100
101 101 pos, l = 0, len(program)
102 102 while pos < l:
103 103 c = program[pos:pos + 1]
104 104 if c.isspace(): # skip inter-token whitespace
105 105 pass
106 106 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
107 107 yield ('::', None, pos)
108 108 pos += 1 # skip ahead
109 109 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
110 110 yield ('..', None, pos)
111 111 pos += 1 # skip ahead
112 112 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
113 113 yield ('##', None, pos)
114 114 pos += 1 # skip ahead
115 115 elif c in _simpleopletters: # handle simple operators
116 116 yield (c, None, pos)
117 117 elif (c in _quoteletters or c == 'r' and
118 118 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
119 119 if c == 'r':
120 120 pos += 1
121 121 c = program[pos:pos + 1]
122 122 decode = lambda x: x
123 123 else:
124 124 decode = parser.unescapestr
125 125 pos += 1
126 126 s = pos
127 127 while pos < l: # find closing quote
128 128 d = program[pos:pos + 1]
129 129 if d == '\\': # skip over escaped characters
130 130 pos += 2
131 131 continue
132 132 if d == c:
133 133 yield ('string', decode(program[s:pos]), s)
134 134 break
135 135 pos += 1
136 136 else:
137 137 raise error.ParseError(_("unterminated string"), s)
138 138 # gather up a symbol/keyword
139 139 elif c in syminitletters:
140 140 s = pos
141 141 pos += 1
142 142 while pos < l: # find end of symbol
143 143 d = program[pos:pos + 1]
144 144 if d not in symletters:
145 145 break
146 146 if (d == '.'
147 147 and program[pos - 1:pos] == '.'): # special case for ..
148 148 pos -= 1
149 149 break
150 150 pos += 1
151 151 sym = program[s:pos]
152 152 if sym in keywords: # operator keywords
153 153 yield (sym, None, s)
154 154 elif '-' in sym:
155 155 # some jerk gave us foo-bar-baz, try to check if it's a symbol
156 156 if lookup and lookup(sym):
157 157 # looks like a real symbol
158 158 yield ('symbol', sym, s)
159 159 else:
160 160 # looks like an expression
161 161 parts = sym.split('-')
162 162 for p in parts[:-1]:
163 163 if p: # possible consecutive -
164 164 yield ('symbol', p, s)
165 165 s += len(p)
166 166 yield ('-', None, pos)
167 167 s += 1
168 168 if parts[-1]: # possible trailing -
169 169 yield ('symbol', parts[-1], s)
170 170 else:
171 171 yield ('symbol', sym, s)
172 172 pos -= 1
173 173 else:
174 174 raise error.ParseError(_("syntax error in revset '%s'") %
175 175 program, pos)
176 176 pos += 1
177 177 yield ('end', None, pos)
178 178
179 179 # helpers
180 180
181 181 _notset = object()
182 182
183 183 def getsymbol(x):
184 184 if x and x[0] == 'symbol':
185 185 return x[1]
186 186 raise error.ParseError(_('not a symbol'))
187 187
188 188 def getstring(x, err):
189 189 if x and (x[0] == 'string' or x[0] == 'symbol'):
190 190 return x[1]
191 191 raise error.ParseError(err)
192 192
193 193 def getinteger(x, err, default=_notset):
194 194 if not x and default is not _notset:
195 195 return default
196 196 try:
197 197 return int(getstring(x, err))
198 198 except ValueError:
199 199 raise error.ParseError(err)
200 200
201 201 def getlist(x):
202 202 if not x:
203 203 return []
204 204 if x[0] == 'list':
205 205 return list(x[1:])
206 206 return [x]
207 207
208 208 def getrange(x, err):
209 209 if not x:
210 210 raise error.ParseError(err)
211 211 op = x[0]
212 212 if op == 'range':
213 213 return x[1], x[2]
214 214 elif op == 'rangepre':
215 215 return None, x[1]
216 216 elif op == 'rangepost':
217 217 return x[1], None
218 218 elif op == 'rangeall':
219 219 return None, None
220 220 raise error.ParseError(err)
221 221
222 222 def getargs(x, min, max, err):
223 223 l = getlist(x)
224 224 if len(l) < min or (max >= 0 and len(l) > max):
225 225 raise error.ParseError(err)
226 226 return l
227 227
228 228 def getargsdict(x, funcname, keys):
229 229 return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
230 230 keyvaluenode='keyvalue', keynode='symbol')
231 231
232 232 # Constants for ordering requirement, used in _analyze():
233 233 #
234 234 # If 'define', any nested functions and operations can change the ordering of
235 235 # the entries in the set. If 'follow', any nested functions and operations
236 236 # should take the ordering specified by the first operand to the '&' operator.
237 237 #
238 238 # For instance,
239 239 #
240 240 # X & (Y | Z)
241 241 # ^ ^^^^^^^
242 242 # | follow
243 243 # define
244 244 #
245 245 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
246 246 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
247 247 #
248 248 # 'any' means the order doesn't matter. For instance,
249 249 #
250 250 # X & !Y
251 251 # ^
252 252 # any
253 253 #
254 254 # 'y()' can either enforce its ordering requirement or take the ordering
255 255 # specified by 'x()' because 'not()' doesn't care the order.
256 256 #
257 257 # Transition of ordering requirement:
258 258 #
259 259 # 1. starts with 'define'
260 260 # 2. shifts to 'follow' by 'x & y'
261 261 # 3. changes back to 'define' on function call 'f(x)' or function-like
262 262 # operation 'x (f) y' because 'f' may have its own ordering requirement
263 263 # for 'x' and 'y' (e.g. 'first(x)')
264 264 #
265 265 anyorder = 'any' # don't care the order
266 266 defineorder = 'define' # should define the order
267 267 followorder = 'follow' # must follow the current order
268 268
269 269 # transition table for 'x & y', from the current expression 'x' to 'y'
270 270 _tofolloworder = {
271 271 anyorder: anyorder,
272 272 defineorder: followorder,
273 273 followorder: followorder,
274 274 }
275 275
276 276 def _matchonly(revs, bases):
277 277 """
278 278 >>> f = lambda *args: _matchonly(*map(parse, args))
279 279 >>> f('ancestors(A)', 'not ancestors(B)')
280 280 ('list', ('symbol', 'A'), ('symbol', 'B'))
281 281 """
282 282 if (revs is not None
283 283 and revs[0] == 'func'
284 284 and getsymbol(revs[1]) == 'ancestors'
285 285 and bases is not None
286 286 and bases[0] == 'not'
287 287 and bases[1][0] == 'func'
288 288 and getsymbol(bases[1][1]) == 'ancestors'):
289 289 return ('list', revs[2], bases[1][2])
290 290
291 291 def _fixops(x):
292 292 """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
293 293 handled well by our simple top-down parser"""
294 294 if not isinstance(x, tuple):
295 295 return x
296 296
297 297 op = x[0]
298 298 if op == 'parent':
299 299 # x^:y means (x^) : y, not x ^ (:y)
300 300 # x^: means (x^) :, not x ^ (:)
301 301 post = ('parentpost', x[1])
302 302 if x[2][0] == 'dagrangepre':
303 303 return _fixops(('dagrange', post, x[2][1]))
304 304 elif x[2][0] == 'rangepre':
305 305 return _fixops(('range', post, x[2][1]))
306 306 elif x[2][0] == 'rangeall':
307 307 return _fixops(('rangepost', post))
308 308 elif op == 'or':
309 309 # make number of arguments deterministic:
310 310 # x + y + z -> (or x y z) -> (or (list x y z))
311 311 return (op, _fixops(('list',) + x[1:]))
312 312
313 313 return (op,) + tuple(_fixops(y) for y in x[1:])
314 314
315 315 def _analyze(x, order):
316 316 if x is None:
317 317 return x
318 318
319 319 op = x[0]
320 320 if op == 'minus':
321 321 return _analyze(('and', x[1], ('not', x[2])), order)
322 322 elif op == 'only':
323 323 t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
324 324 return _analyze(t, order)
325 325 elif op == 'onlypost':
326 326 return _analyze(('func', ('symbol', 'only'), x[1]), order)
327 327 elif op == 'dagrangepre':
328 328 return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
329 329 elif op == 'dagrangepost':
330 330 return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
331 331 elif op == 'negate':
332 332 s = getstring(x[1], _("can't negate that"))
333 333 return _analyze(('string', '-' + s), order)
334 334 elif op in ('string', 'symbol'):
335 335 return x
336 336 elif op == 'and':
337 337 ta = _analyze(x[1], order)
338 338 tb = _analyze(x[2], _tofolloworder[order])
339 339 return (op, ta, tb, order)
340 340 elif op == 'or':
341 341 return (op, _analyze(x[1], order), order)
342 342 elif op == 'not':
343 343 return (op, _analyze(x[1], anyorder), order)
344 344 elif op == 'rangeall':
345 345 return (op, None, order)
346 346 elif op in ('rangepre', 'rangepost', 'parentpost'):
347 347 return (op, _analyze(x[1], defineorder), order)
348 348 elif op == 'group':
349 349 return _analyze(x[1], order)
350 350 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
351 351 ta = _analyze(x[1], defineorder)
352 352 tb = _analyze(x[2], defineorder)
353 353 return (op, ta, tb, order)
354 354 elif op == 'list':
355 355 return (op,) + tuple(_analyze(y, order) for y in x[1:])
356 356 elif op == 'keyvalue':
357 357 return (op, x[1], _analyze(x[2], order))
358 358 elif op == 'func':
359 359 f = getsymbol(x[1])
360 360 d = defineorder
361 361 if f == 'present':
362 362 # 'present(set)' is known to return the argument set with no
363 363 # modification, so forward the current order to its argument
364 364 d = order
365 365 return (op, x[1], _analyze(x[2], d), order)
366 366 raise ValueError('invalid operator %r' % op)
367 367
368 368 def analyze(x, order=defineorder):
369 369 """Transform raw parsed tree to evaluatable tree which can be fed to
370 370 optimize() or getset()
371 371
372 372 All pseudo operations should be mapped to real operations or functions
373 373 defined in methods or symbols table respectively.
374 374
375 375 'order' specifies how the current expression 'x' is ordered (see the
376 376 constants defined above.)
377 377 """
378 378 return _analyze(x, order)
379 379
380 380 def _optimize(x, small):
381 381 if x is None:
382 382 return 0, x
383 383
384 384 smallbonus = 1
385 385 if small:
386 386 smallbonus = .5
387 387
388 388 op = x[0]
389 389 if op in ('string', 'symbol'):
390 390 return smallbonus, x # single revisions are small
391 391 elif op == 'and':
392 392 wa, ta = _optimize(x[1], True)
393 393 wb, tb = _optimize(x[2], True)
394 394 order = x[3]
395 395 w = min(wa, wb)
396 396
397 397 # (::x and not ::y)/(not ::y and ::x) have a fast path
398 398 tm = _matchonly(ta, tb) or _matchonly(tb, ta)
399 399 if tm:
400 400 return w, ('func', ('symbol', 'only'), tm, order)
401 401
402 402 if tb is not None and tb[0] == 'not':
403 403 return wa, ('difference', ta, tb[1], order)
404 404
405 405 if wa > wb:
406 406 return w, (op, tb, ta, order)
407 407 return w, (op, ta, tb, order)
408 408 elif op == 'or':
409 409 # fast path for machine-generated expression, that is likely to have
410 410 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
411 411 order = x[2]
412 412 ws, ts, ss = [], [], []
413 413 def flushss():
414 414 if not ss:
415 415 return
416 416 if len(ss) == 1:
417 417 w, t = ss[0]
418 418 else:
419 419 s = '\0'.join(t[1] for w, t in ss)
420 420 y = ('func', ('symbol', '_list'), ('string', s), order)
421 421 w, t = _optimize(y, False)
422 422 ws.append(w)
423 423 ts.append(t)
424 424 del ss[:]
425 425 for y in getlist(x[1]):
426 426 w, t = _optimize(y, False)
427 427 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
428 428 ss.append((w, t))
429 429 continue
430 430 flushss()
431 431 ws.append(w)
432 432 ts.append(t)
433 433 flushss()
434 434 if len(ts) == 1:
435 435 return ws[0], ts[0] # 'or' operation is fully optimized out
436 436 # we can't reorder trees by weight because it would change the order.
437 437 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
438 438 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
439 439 return max(ws), (op, ('list',) + tuple(ts), order)
440 440 elif op == 'not':
441 441 # Optimize not public() to _notpublic() because we have a fast version
442 442 if x[1][:3] == ('func', ('symbol', 'public'), None):
443 443 order = x[1][3]
444 444 newsym = ('func', ('symbol', '_notpublic'), None, order)
445 445 o = _optimize(newsym, not small)
446 446 return o[0], o[1]
447 447 else:
448 448 o = _optimize(x[1], not small)
449 449 order = x[2]
450 450 return o[0], (op, o[1], order)
451 451 elif op == 'rangeall':
452 452 return smallbonus, x
453 453 elif op in ('rangepre', 'rangepost', 'parentpost'):
454 454 o = _optimize(x[1], small)
455 455 order = x[2]
456 456 return o[0], (op, o[1], order)
457 457 elif op in ('dagrange', 'range', 'parent', 'ancestor'):
458 458 wa, ta = _optimize(x[1], small)
459 459 wb, tb = _optimize(x[2], small)
460 460 order = x[3]
461 461 return wa + wb, (op, ta, tb, order)
462 462 elif op == 'list':
463 463 ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
464 464 return sum(ws), (op,) + ts
465 465 elif op == 'keyvalue':
466 466 w, t = _optimize(x[2], small)
467 467 return w, (op, x[1], t)
468 468 elif op == 'func':
469 469 f = getsymbol(x[1])
470 470 wa, ta = _optimize(x[2], small)
471 471 if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
472 472 'keyword', 'outgoing', 'user', 'destination'):
473 473 w = 10 # slow
474 474 elif f in ('modifies', 'adds', 'removes'):
475 475 w = 30 # slower
476 476 elif f == "contains":
477 477 w = 100 # very slow
478 478 elif f == "ancestor":
479 479 w = 1 * smallbonus
480 480 elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
481 481 w = 0
482 482 elif f == "sort":
483 483 w = 10 # assume most sorts look at changelog
484 484 else:
485 485 w = 1
486 486 order = x[3]
487 487 return w + wa, (op, x[1], ta, order)
488 488 raise ValueError('invalid operator %r' % op)
489 489
490 490 def optimize(tree):
491 491 """Optimize evaluatable tree
492 492
493 493 All pseudo operations should be transformed beforehand.
494 494 """
495 495 _weight, newtree = _optimize(tree, small=True)
496 496 return newtree
497 497
498 498 # the set of valid characters for the initial letter of symbols in
499 499 # alias declarations and definitions
500 500 _aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
501 501
502 502 def _parsewith(spec, lookup=None, syminitletters=None):
503 503 """Generate a parse tree of given spec with given tokenizing options
504 504
505 505 >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
506 506 ('func', ('symbol', 'foo'), ('symbol', '$1'))
507 507 >>> _parsewith('$1')
508 508 Traceback (most recent call last):
509 509 ...
510 510 ParseError: ("syntax error in revset '$1'", 0)
511 511 >>> _parsewith('foo bar')
512 512 Traceback (most recent call last):
513 513 ...
514 514 ParseError: ('invalid token', 4)
515 515 """
516 516 p = parser.parser(elements)
517 517 tree, pos = p.parse(tokenize(spec, lookup=lookup,
518 518 syminitletters=syminitletters))
519 519 if pos != len(spec):
520 520 raise error.ParseError(_('invalid token'), pos)
521 521 return _fixops(parser.simplifyinfixops(tree, ('list', 'or')))
522 522
523 523 class _aliasrules(parser.basealiasrules):
524 524 """Parsing and expansion rule set of revset aliases"""
525 525 _section = _('revset alias')
526 526
527 527 @staticmethod
528 528 def _parse(spec):
529 529 """Parse alias declaration/definition ``spec``
530 530
531 531 This allows symbol names to use also ``$`` as an initial letter
532 532 (for backward compatibility), and callers of this function should
533 533 examine whether ``$`` is used also for unexpected symbols or not.
534 534 """
535 535 return _parsewith(spec, syminitletters=_aliassyminitletters)
536 536
537 537 @staticmethod
538 538 def _trygetfunc(tree):
539 539 if tree[0] == 'func' and tree[1][0] == 'symbol':
540 540 return tree[1][1], getlist(tree[2])
541 541
542 542 def expandaliases(ui, tree):
543 543 aliases = _aliasrules.buildmap(ui.configitems('revsetalias'))
544 544 tree = _aliasrules.expand(aliases, tree)
545 545 # warn about problematic (but not referred) aliases
546 546 for name, alias in sorted(aliases.iteritems()):
547 547 if alias.error and not alias.warned:
548 548 ui.warn(_('warning: %s\n') % (alias.error))
549 549 alias.warned = True
550 550 return tree
551 551
552 552 def foldconcat(tree):
553 553 """Fold elements to be concatenated by `##`
554 554 """
555 555 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
556 556 return tree
557 557 if tree[0] == '_concat':
558 558 pending = [tree]
559 559 l = []
560 560 while pending:
561 561 e = pending.pop()
562 562 if e[0] == '_concat':
563 563 pending.extend(reversed(e[1:]))
564 564 elif e[0] in ('string', 'symbol'):
565 565 l.append(e[1])
566 566 else:
567 567 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
568 568 raise error.ParseError(msg)
569 569 return ('string', ''.join(l))
570 570 else:
571 571 return tuple(foldconcat(t) for t in tree)
572 572
573 573 def parse(spec, lookup=None):
574 574 return _parsewith(spec, lookup=lookup)
575 575
576 576 def formatspec(expr, *args):
577 577 '''
578 578 This is a convenience function for using revsets internally, and
579 579 escapes arguments appropriately. Aliases are intentionally ignored
580 580 so that intended expression behavior isn't accidentally subverted.
581 581
582 582 Supported arguments:
583 583
584 584 %r = revset expression, parenthesized
585 585 %d = int(arg), no quoting
586 586 %s = string(arg), escaped and single-quoted
587 587 %b = arg.branch(), escaped and single-quoted
588 588 %n = hex(arg), single-quoted
589 589 %% = a literal '%'
590 590
591 591 Prefixing the type with 'l' specifies a parenthesized list of that type.
592 592
593 593 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
594 594 '(10 or 11):: and ((this()) or (that()))'
595 595 >>> formatspec('%d:: and not %d::', 10, 20)
596 596 '10:: and not 20::'
597 597 >>> formatspec('%ld or %ld', [], [1])
598 598 "_list('') or 1"
599 599 >>> formatspec('keyword(%s)', 'foo\\xe9')
600 600 "keyword('foo\\\\xe9')"
601 601 >>> b = lambda: 'default'
602 602 >>> b.branch = b
603 603 >>> formatspec('branch(%b)', b)
604 604 "branch('default')"
605 605 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
606 606 "root(_list('a\\x00b\\x00c\\x00d'))"
607 607 '''
608 608
609 609 def quote(s):
610 610 return repr(str(s))
611 611
612 612 def argtype(c, arg):
613 613 if c == 'd':
614 614 return str(int(arg))
615 615 elif c == 's':
616 616 return quote(arg)
617 617 elif c == 'r':
618 618 parse(arg) # make sure syntax errors are confined
619 619 return '(%s)' % arg
620 620 elif c == 'n':
621 621 return quote(node.hex(arg))
622 622 elif c == 'b':
623 623 return quote(arg.branch())
624 624
625 625 def listexp(s, t):
626 626 l = len(s)
627 627 if l == 0:
628 628 return "_list('')"
629 629 elif l == 1:
630 630 return argtype(t, s[0])
631 631 elif t == 'd':
632 632 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
633 633 elif t == 's':
634 634 return "_list('%s')" % "\0".join(s)
635 635 elif t == 'n':
636 636 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
637 637 elif t == 'b':
638 638 return "_list('%s')" % "\0".join(a.branch() for a in s)
639 639
640 640 m = l // 2
641 641 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
642 642
643 643 ret = ''
644 644 pos = 0
645 645 arg = 0
646 646 while pos < len(expr):
647 c = expr[pos]
647 c = expr[pos:pos + 1]
648 648 if c == '%':
649 649 pos += 1
650 d = expr[pos]
650 d = expr[pos:pos + 1]
651 651 if d == '%':
652 652 ret += d
653 653 elif d in 'dsnbr':
654 654 ret += argtype(d, args[arg])
655 655 arg += 1
656 656 elif d == 'l':
657 657 # a list of some type
658 658 pos += 1
659 d = expr[pos]
659 d = expr[pos:pos + 1]
660 660 ret += listexp(list(args[arg]), d)
661 661 arg += 1
662 662 else:
663 663 raise error.Abort(_('unexpected revspec format character %s')
664 664 % d)
665 665 else:
666 666 ret += c
667 667 pos += 1
668 668
669 669 return ret
670 670
671 671 def prettyformat(tree):
672 672 return parser.prettyformat(tree, ('string', 'symbol'))
673 673
674 674 def depth(tree):
675 675 if isinstance(tree, tuple):
676 676 return max(map(depth, tree)) + 1
677 677 else:
678 678 return 0
679 679
680 680 def funcsused(tree):
681 681 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
682 682 return set()
683 683 else:
684 684 funcs = set()
685 685 for s in tree[1:]:
686 686 funcs |= funcsused(s)
687 687 if tree[0] == 'func':
688 688 funcs.add(tree[1][1])
689 689 return funcs
General Comments 0
You need to be logged in to leave comments. Login now