Mercurial > public > mercurial-scm > hg
comparison mercurial/templateutil.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 832c59d1196e |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
19 from .utils import ( | 19 from .utils import ( |
20 dateutil, | 20 dateutil, |
21 stringutil, | 21 stringutil, |
22 ) | 22 ) |
23 | 23 |
24 | |
24 class ResourceUnavailable(error.Abort): | 25 class ResourceUnavailable(error.Abort): |
25 pass | 26 pass |
26 | 27 |
28 | |
27 class TemplateNotFound(error.Abort): | 29 class TemplateNotFound(error.Abort): |
28 pass | 30 pass |
31 | |
29 | 32 |
30 class wrapped(object): | 33 class wrapped(object): |
31 """Object requiring extra conversion prior to displaying or processing | 34 """Object requiring extra conversion prior to displaying or processing |
32 as value | 35 as value |
33 | 36 |
101 """Move the inner value object out or create a value representation | 104 """Move the inner value object out or create a value representation |
102 | 105 |
103 A returned value must be serializable by templaterfilters.json(). | 106 A returned value must be serializable by templaterfilters.json(). |
104 """ | 107 """ |
105 | 108 |
109 | |
106 class mappable(object): | 110 class mappable(object): |
107 """Object which can be converted to a single template mapping""" | 111 """Object which can be converted to a single template mapping""" |
108 | 112 |
109 def itermaps(self, context): | 113 def itermaps(self, context): |
110 yield self.tomap(context) | 114 yield self.tomap(context) |
111 | 115 |
112 @abc.abstractmethod | 116 @abc.abstractmethod |
113 def tomap(self, context): | 117 def tomap(self, context): |
114 """Create a single template mapping representing this""" | 118 """Create a single template mapping representing this""" |
115 | 119 |
120 | |
116 class wrappedbytes(wrapped): | 121 class wrappedbytes(wrapped): |
117 """Wrapper for byte string""" | 122 """Wrapper for byte string""" |
118 | 123 |
119 def __init__(self, value): | 124 def __init__(self, value): |
120 self._value = value | 125 self._value = value |
122 def contains(self, context, mapping, item): | 127 def contains(self, context, mapping, item): |
123 item = stringify(context, mapping, item) | 128 item = stringify(context, mapping, item) |
124 return item in self._value | 129 return item in self._value |
125 | 130 |
126 def getmember(self, context, mapping, key): | 131 def getmember(self, context, mapping, key): |
127 raise error.ParseError(_('%r is not a dictionary') | 132 raise error.ParseError( |
128 % pycompat.bytestr(self._value)) | 133 _('%r is not a dictionary') % pycompat.bytestr(self._value) |
134 ) | |
129 | 135 |
130 def getmin(self, context, mapping): | 136 def getmin(self, context, mapping): |
131 return self._getby(context, mapping, min) | 137 return self._getby(context, mapping, min) |
132 | 138 |
133 def getmax(self, context, mapping): | 139 def getmax(self, context, mapping): |
137 if not self._value: | 143 if not self._value: |
138 raise error.ParseError(_('empty string')) | 144 raise error.ParseError(_('empty string')) |
139 return func(pycompat.iterbytestr(self._value)) | 145 return func(pycompat.iterbytestr(self._value)) |
140 | 146 |
141 def filter(self, context, mapping, select): | 147 def filter(self, context, mapping, select): |
142 raise error.ParseError(_('%r is not filterable') | 148 raise error.ParseError( |
143 % pycompat.bytestr(self._value)) | 149 _('%r is not filterable') % pycompat.bytestr(self._value) |
150 ) | |
144 | 151 |
145 def itermaps(self, context): | 152 def itermaps(self, context): |
146 raise error.ParseError(_('%r is not iterable of mappings') | 153 raise error.ParseError( |
147 % pycompat.bytestr(self._value)) | 154 _('%r is not iterable of mappings') % pycompat.bytestr(self._value) |
155 ) | |
148 | 156 |
149 def join(self, context, mapping, sep): | 157 def join(self, context, mapping, sep): |
150 return joinitems(pycompat.iterbytestr(self._value), sep) | 158 return joinitems(pycompat.iterbytestr(self._value), sep) |
151 | 159 |
152 def show(self, context, mapping): | 160 def show(self, context, mapping): |
156 return bool(self._value) | 164 return bool(self._value) |
157 | 165 |
158 def tovalue(self, context, mapping): | 166 def tovalue(self, context, mapping): |
159 return self._value | 167 return self._value |
160 | 168 |
169 | |
161 class wrappedvalue(wrapped): | 170 class wrappedvalue(wrapped): |
162 """Generic wrapper for pure non-list/dict/bytes value""" | 171 """Generic wrapper for pure non-list/dict/bytes value""" |
163 | 172 |
164 def __init__(self, value): | 173 def __init__(self, value): |
165 self._value = value | 174 self._value = value |
178 | 187 |
179 def filter(self, context, mapping, select): | 188 def filter(self, context, mapping, select): |
180 raise error.ParseError(_("%r is not iterable") % self._value) | 189 raise error.ParseError(_("%r is not iterable") % self._value) |
181 | 190 |
182 def itermaps(self, context): | 191 def itermaps(self, context): |
183 raise error.ParseError(_('%r is not iterable of mappings') | 192 raise error.ParseError( |
184 % self._value) | 193 _('%r is not iterable of mappings') % self._value |
194 ) | |
185 | 195 |
186 def join(self, context, mapping, sep): | 196 def join(self, context, mapping, sep): |
187 raise error.ParseError(_('%r is not iterable') % self._value) | 197 raise error.ParseError(_('%r is not iterable') % self._value) |
188 | 198 |
189 def show(self, context, mapping): | 199 def show(self, context, mapping): |
200 return bool(pycompat.bytestr(self._value)) | 210 return bool(pycompat.bytestr(self._value)) |
201 | 211 |
202 def tovalue(self, context, mapping): | 212 def tovalue(self, context, mapping): |
203 return self._value | 213 return self._value |
204 | 214 |
215 | |
205 class date(mappable, wrapped): | 216 class date(mappable, wrapped): |
206 """Wrapper for date tuple""" | 217 """Wrapper for date tuple""" |
207 | 218 |
208 def __init__(self, value, showfmt='%d %d'): | 219 def __init__(self, value, showfmt='%d %d'): |
209 # value may be (float, int), but public interface shouldn't support | 220 # value may be (float, int), but public interface shouldn't support |
238 def tobool(self, context, mapping): | 249 def tobool(self, context, mapping): |
239 return True | 250 return True |
240 | 251 |
241 def tovalue(self, context, mapping): | 252 def tovalue(self, context, mapping): |
242 return (self._unixtime, self._tzoffset) | 253 return (self._unixtime, self._tzoffset) |
254 | |
243 | 255 |
244 class hybrid(wrapped): | 256 class hybrid(wrapped): |
245 """Wrapper for list or dict to support legacy template | 257 """Wrapper for list or dict to support legacy template |
246 | 258 |
247 This class allows us to handle both: | 259 This class allows us to handle both: |
291 return val | 303 return val |
292 return hybriditem(None, key, val, self._makemap) | 304 return hybriditem(None, key, val, self._makemap) |
293 | 305 |
294 def filter(self, context, mapping, select): | 306 def filter(self, context, mapping, select): |
295 if util.safehasattr(self._values, 'get'): | 307 if util.safehasattr(self._values, 'get'): |
296 values = {k: v for k, v in self._values.iteritems() | 308 values = { |
297 if select(self._wrapvalue(k, v))} | 309 k: v |
310 for k, v in self._values.iteritems() | |
311 if select(self._wrapvalue(k, v)) | |
312 } | |
298 else: | 313 else: |
299 values = [v for v in self._values if select(self._wrapvalue(v, v))] | 314 values = [v for v in self._values if select(self._wrapvalue(v, v))] |
300 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype) | 315 return hybrid(None, values, self._makemap, self._joinfmt, self._keytype) |
301 | 316 |
302 def itermaps(self, context): | 317 def itermaps(self, context): |
322 | 337 |
323 def tovalue(self, context, mapping): | 338 def tovalue(self, context, mapping): |
324 # TODO: make it non-recursive for trivial lists/dicts | 339 # TODO: make it non-recursive for trivial lists/dicts |
325 xs = self._values | 340 xs = self._values |
326 if util.safehasattr(xs, 'get'): | 341 if util.safehasattr(xs, 'get'): |
327 return {k: unwrapvalue(context, mapping, v) | 342 return { |
328 for k, v in xs.iteritems()} | 343 k: unwrapvalue(context, mapping, v) for k, v in xs.iteritems() |
344 } | |
329 return [unwrapvalue(context, mapping, x) for x in xs] | 345 return [unwrapvalue(context, mapping, x) for x in xs] |
346 | |
330 | 347 |
331 class hybriditem(mappable, wrapped): | 348 class hybriditem(mappable, wrapped): |
332 """Wrapper for non-list/dict object to support map operation | 349 """Wrapper for non-list/dict object to support map operation |
333 | 350 |
334 This class allows us to handle both: | 351 This class allows us to handle both: |
383 w = makewrapped(context, mapping, self._value) | 400 w = makewrapped(context, mapping, self._value) |
384 return w.tobool(context, mapping) | 401 return w.tobool(context, mapping) |
385 | 402 |
386 def tovalue(self, context, mapping): | 403 def tovalue(self, context, mapping): |
387 return _unthunk(context, mapping, self._value) | 404 return _unthunk(context, mapping, self._value) |
405 | |
388 | 406 |
389 class _mappingsequence(wrapped): | 407 class _mappingsequence(wrapped): |
390 """Wrapper for sequence of template mappings | 408 """Wrapper for sequence of template mappings |
391 | 409 |
392 This represents an inner template structure (i.e. a list of dicts), | 410 This represents an inner template structure (i.e. a list of dicts), |
435 knownres = context.knownresourcekeys() | 453 knownres = context.knownresourcekeys() |
436 items = [] | 454 items = [] |
437 for nm in self.itermaps(context): | 455 for nm in self.itermaps(context): |
438 # drop internal resources (recursively) which shouldn't be displayed | 456 # drop internal resources (recursively) which shouldn't be displayed |
439 lm = context.overlaymap(mapping, nm) | 457 lm = context.overlaymap(mapping, nm) |
440 items.append({k: unwrapvalue(context, lm, v) | 458 items.append( |
441 for k, v in nm.iteritems() if k not in knownres}) | 459 { |
460 k: unwrapvalue(context, lm, v) | |
461 for k, v in nm.iteritems() | |
462 if k not in knownres | |
463 } | |
464 ) | |
442 return items | 465 return items |
466 | |
443 | 467 |
444 class mappinggenerator(_mappingsequence): | 468 class mappinggenerator(_mappingsequence): |
445 """Wrapper for generator of template mappings | 469 """Wrapper for generator of template mappings |
446 | 470 |
447 The function ``make(context, *args)`` should return a generator of | 471 The function ``make(context, *args)`` should return a generator of |
457 return self._make(context, *self._args) | 481 return self._make(context, *self._args) |
458 | 482 |
459 def tobool(self, context, mapping): | 483 def tobool(self, context, mapping): |
460 return _nonempty(self.itermaps(context)) | 484 return _nonempty(self.itermaps(context)) |
461 | 485 |
486 | |
462 class mappinglist(_mappingsequence): | 487 class mappinglist(_mappingsequence): |
463 """Wrapper for list of template mappings""" | 488 """Wrapper for list of template mappings""" |
464 | 489 |
465 def __init__(self, mappings, name=None, tmpl=None, sep=''): | 490 def __init__(self, mappings, name=None, tmpl=None, sep=''): |
466 super(mappinglist, self).__init__(name, tmpl, sep) | 491 super(mappinglist, self).__init__(name, tmpl, sep) |
469 def itermaps(self, context): | 494 def itermaps(self, context): |
470 return iter(self._mappings) | 495 return iter(self._mappings) |
471 | 496 |
472 def tobool(self, context, mapping): | 497 def tobool(self, context, mapping): |
473 return bool(self._mappings) | 498 return bool(self._mappings) |
499 | |
474 | 500 |
475 class mappingdict(mappable, _mappingsequence): | 501 class mappingdict(mappable, _mappingsequence): |
476 """Wrapper for a single template mapping | 502 """Wrapper for a single template mapping |
477 | 503 |
478 This isn't a sequence in a way that the underlying dict won't be iterated | 504 This isn't a sequence in a way that the underlying dict won't be iterated |
493 return True | 519 return True |
494 | 520 |
495 def tovalue(self, context, mapping): | 521 def tovalue(self, context, mapping): |
496 return super(mappingdict, self).tovalue(context, mapping)[0] | 522 return super(mappingdict, self).tovalue(context, mapping)[0] |
497 | 523 |
524 | |
498 class mappingnone(wrappedvalue): | 525 class mappingnone(wrappedvalue): |
499 """Wrapper for None, but supports map operation | 526 """Wrapper for None, but supports map operation |
500 | 527 |
501 This represents None of Optional[mappable]. It's similar to | 528 This represents None of Optional[mappable]. It's similar to |
502 mapplinglist([]), but the underlying value is not [], but None. | 529 mapplinglist([]), but the underlying value is not [], but None. |
505 def __init__(self): | 532 def __init__(self): |
506 super(mappingnone, self).__init__(None) | 533 super(mappingnone, self).__init__(None) |
507 | 534 |
508 def itermaps(self, context): | 535 def itermaps(self, context): |
509 return iter([]) | 536 return iter([]) |
537 | |
510 | 538 |
511 class mappedgenerator(wrapped): | 539 class mappedgenerator(wrapped): |
512 """Wrapper for generator of strings which acts as a list | 540 """Wrapper for generator of strings which acts as a list |
513 | 541 |
514 The function ``make(context, *args)`` should return a generator of | 542 The function ``make(context, *args)`` should return a generator of |
566 return _nonempty(self._gen(context)) | 594 return _nonempty(self._gen(context)) |
567 | 595 |
568 def tovalue(self, context, mapping): | 596 def tovalue(self, context, mapping): |
569 return [stringify(context, mapping, x) for x in self._gen(context)] | 597 return [stringify(context, mapping, x) for x in self._gen(context)] |
570 | 598 |
599 | |
571 def hybriddict(data, key='key', value='value', fmt=None, gen=None): | 600 def hybriddict(data, key='key', value='value', fmt=None, gen=None): |
572 """Wrap data to support both dict-like and string-like operations""" | 601 """Wrap data to support both dict-like and string-like operations""" |
573 prefmt = pycompat.identity | 602 prefmt = pycompat.identity |
574 if fmt is None: | 603 if fmt is None: |
575 fmt = '%s=%s' | 604 fmt = '%s=%s' |
576 prefmt = pycompat.bytestr | 605 prefmt = pycompat.bytestr |
577 return hybrid(gen, data, lambda k: {key: k, value: data[k]}, | 606 return hybrid( |
578 lambda k: fmt % (prefmt(k), prefmt(data[k]))) | 607 gen, |
608 data, | |
609 lambda k: {key: k, value: data[k]}, | |
610 lambda k: fmt % (prefmt(k), prefmt(data[k])), | |
611 ) | |
612 | |
579 | 613 |
580 def hybridlist(data, name, fmt=None, gen=None): | 614 def hybridlist(data, name, fmt=None, gen=None): |
581 """Wrap data to support both list-like and string-like operations""" | 615 """Wrap data to support both list-like and string-like operations""" |
582 prefmt = pycompat.identity | 616 prefmt = pycompat.identity |
583 if fmt is None: | 617 if fmt is None: |
584 fmt = '%s' | 618 fmt = '%s' |
585 prefmt = pycompat.bytestr | 619 prefmt = pycompat.bytestr |
586 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) | 620 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) |
587 | 621 |
588 def compatdict(context, mapping, name, data, key='key', value='value', | 622 |
589 fmt=None, plural=None, separator=' '): | 623 def compatdict( |
624 context, | |
625 mapping, | |
626 name, | |
627 data, | |
628 key='key', | |
629 value='value', | |
630 fmt=None, | |
631 plural=None, | |
632 separator=' ', | |
633 ): | |
590 """Wrap data like hybriddict(), but also supports old-style list template | 634 """Wrap data like hybriddict(), but also supports old-style list template |
591 | 635 |
592 This exists for backward compatibility with the old-style template. Use | 636 This exists for backward compatibility with the old-style template. Use |
593 hybriddict() for new template keywords. | 637 hybriddict() for new template keywords. |
594 """ | 638 """ |
595 c = [{key: k, value: v} for k, v in data.iteritems()] | 639 c = [{key: k, value: v} for k, v in data.iteritems()] |
596 f = _showcompatlist(context, mapping, name, c, plural, separator) | 640 f = _showcompatlist(context, mapping, name, c, plural, separator) |
597 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) | 641 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f) |
598 | 642 |
599 def compatlist(context, mapping, name, data, element=None, fmt=None, | 643 |
600 plural=None, separator=' '): | 644 def compatlist( |
645 context, | |
646 mapping, | |
647 name, | |
648 data, | |
649 element=None, | |
650 fmt=None, | |
651 plural=None, | |
652 separator=' ', | |
653 ): | |
601 """Wrap data like hybridlist(), but also supports old-style list template | 654 """Wrap data like hybridlist(), but also supports old-style list template |
602 | 655 |
603 This exists for backward compatibility with the old-style template. Use | 656 This exists for backward compatibility with the old-style template. Use |
604 hybridlist() for new template keywords. | 657 hybridlist() for new template keywords. |
605 """ | 658 """ |
606 f = _showcompatlist(context, mapping, name, data, plural, separator) | 659 f = _showcompatlist(context, mapping, name, data, plural, separator) |
607 return hybridlist(data, name=element or name, fmt=fmt, gen=f) | 660 return hybridlist(data, name=element or name, fmt=fmt, gen=f) |
661 | |
608 | 662 |
609 def compatfilecopiesdict(context, mapping, name, copies): | 663 def compatfilecopiesdict(context, mapping, name, copies): |
610 """Wrap list of (dest, source) file names to support old-style list | 664 """Wrap list of (dest, source) file names to support old-style list |
611 template and field names | 665 template and field names |
612 | 666 |
615 """ | 669 """ |
616 # no need to provide {path} to old-style list template | 670 # no need to provide {path} to old-style list template |
617 c = [{'name': k, 'source': v} for k, v in copies] | 671 c = [{'name': k, 'source': v} for k, v in copies] |
618 f = _showcompatlist(context, mapping, name, c, plural='file_copies') | 672 f = _showcompatlist(context, mapping, name, c, plural='file_copies') |
619 copies = util.sortdict(copies) | 673 copies = util.sortdict(copies) |
620 return hybrid(f, copies, | 674 return hybrid( |
621 lambda k: {'name': k, 'path': k, 'source': copies[k]}, | 675 f, |
622 lambda k: '%s (%s)' % (k, copies[k])) | 676 copies, |
677 lambda k: {'name': k, 'path': k, 'source': copies[k]}, | |
678 lambda k: '%s (%s)' % (k, copies[k]), | |
679 ) | |
680 | |
623 | 681 |
624 def compatfileslist(context, mapping, name, files): | 682 def compatfileslist(context, mapping, name, files): |
625 """Wrap list of file names to support old-style list template and field | 683 """Wrap list of file names to support old-style list template and field |
626 names | 684 names |
627 | 685 |
628 This exists for backward compatibility. Use hybridlist for new template | 686 This exists for backward compatibility. Use hybridlist for new template |
629 keywords. | 687 keywords. |
630 """ | 688 """ |
631 f = _showcompatlist(context, mapping, name, files) | 689 f = _showcompatlist(context, mapping, name, files) |
632 return hybrid(f, files, lambda x: {'file': x, 'path': x}, | 690 return hybrid(f, files, lambda x: {'file': x, 'path': x}, pycompat.identity) |
633 pycompat.identity) | 691 |
634 | 692 |
635 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '): | 693 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '): |
636 """Return a generator that renders old-style list template | 694 """Return a generator that renders old-style list template |
637 | 695 |
638 name is name of key in template map. | 696 name is name of key in template map. |
671 yield r | 729 yield r |
672 return | 730 return |
673 startname = 'start_' + plural | 731 startname = 'start_' + plural |
674 if context.preload(startname): | 732 if context.preload(startname): |
675 yield context.process(startname, mapping) | 733 yield context.process(startname, mapping) |
734 | |
676 def one(v, tag=name): | 735 def one(v, tag=name): |
677 vmapping = {} | 736 vmapping = {} |
678 try: | 737 try: |
679 vmapping.update(v) | 738 vmapping.update(v) |
680 # Python 2 raises ValueError if the type of v is wrong. Python | 739 # Python 2 raises ValueError if the type of v is wrong. Python |
687 vmapping[a] = b | 746 vmapping[a] = b |
688 except (TypeError, ValueError): | 747 except (TypeError, ValueError): |
689 vmapping[name] = v | 748 vmapping[name] = v |
690 vmapping = context.overlaymap(mapping, vmapping) | 749 vmapping = context.overlaymap(mapping, vmapping) |
691 return context.process(tag, vmapping) | 750 return context.process(tag, vmapping) |
751 | |
692 lastname = 'last_' + name | 752 lastname = 'last_' + name |
693 if context.preload(lastname): | 753 if context.preload(lastname): |
694 last = values.pop() | 754 last = values.pop() |
695 else: | 755 else: |
696 last = None | 756 last = None |
700 yield one(last, tag=lastname) | 760 yield one(last, tag=lastname) |
701 endname = 'end_' + plural | 761 endname = 'end_' + plural |
702 if context.preload(endname): | 762 if context.preload(endname): |
703 yield context.process(endname, mapping) | 763 yield context.process(endname, mapping) |
704 | 764 |
765 | |
705 def flatten(context, mapping, thing): | 766 def flatten(context, mapping, thing): |
706 """Yield a single stream from a possibly nested set of iterators""" | 767 """Yield a single stream from a possibly nested set of iterators""" |
707 if isinstance(thing, wrapped): | 768 if isinstance(thing, wrapped): |
708 thing = thing.show(context, mapping) | 769 thing = thing.show(context, mapping) |
709 if isinstance(thing, bytes): | 770 if isinstance(thing, bytes): |
710 yield thing | 771 yield thing |
711 elif isinstance(thing, str): | 772 elif isinstance(thing, str): |
712 # We can only hit this on Python 3, and it's here to guard | 773 # We can only hit this on Python 3, and it's here to guard |
713 # against infinite recursion. | 774 # against infinite recursion. |
714 raise error.ProgrammingError('Mercurial IO including templates is done' | 775 raise error.ProgrammingError( |
715 ' with bytes, not strings, got %r' % thing) | 776 'Mercurial IO including templates is done' |
777 ' with bytes, not strings, got %r' % thing | |
778 ) | |
716 elif thing is None: | 779 elif thing is None: |
717 pass | 780 pass |
718 elif not util.safehasattr(thing, '__iter__'): | 781 elif not util.safehasattr(thing, '__iter__'): |
719 yield pycompat.bytestr(thing) | 782 yield pycompat.bytestr(thing) |
720 else: | 783 else: |
729 yield pycompat.bytestr(i) | 792 yield pycompat.bytestr(i) |
730 else: | 793 else: |
731 for j in flatten(context, mapping, i): | 794 for j in flatten(context, mapping, i): |
732 yield j | 795 yield j |
733 | 796 |
797 | |
734 def stringify(context, mapping, thing): | 798 def stringify(context, mapping, thing): |
735 """Turn values into bytes by converting into text and concatenating them""" | 799 """Turn values into bytes by converting into text and concatenating them""" |
736 if isinstance(thing, bytes): | 800 if isinstance(thing, bytes): |
737 return thing # retain localstr to be round-tripped | 801 return thing # retain localstr to be round-tripped |
738 return b''.join(flatten(context, mapping, thing)) | 802 return b''.join(flatten(context, mapping, thing)) |
803 | |
739 | 804 |
740 def findsymbolicname(arg): | 805 def findsymbolicname(arg): |
741 """Find symbolic name for the given compiled expression; returns None | 806 """Find symbolic name for the given compiled expression; returns None |
742 if nothing found reliably""" | 807 if nothing found reliably""" |
743 while True: | 808 while True: |
747 elif func is runfilter: | 812 elif func is runfilter: |
748 arg = data[0] | 813 arg = data[0] |
749 else: | 814 else: |
750 return None | 815 return None |
751 | 816 |
817 | |
752 def _nonempty(xiter): | 818 def _nonempty(xiter): |
753 try: | 819 try: |
754 next(xiter) | 820 next(xiter) |
755 return True | 821 return True |
756 except StopIteration: | 822 except StopIteration: |
757 return False | 823 return False |
758 | 824 |
825 | |
759 def _unthunk(context, mapping, thing): | 826 def _unthunk(context, mapping, thing): |
760 """Evaluate a lazy byte string into value""" | 827 """Evaluate a lazy byte string into value""" |
761 if not isinstance(thing, types.GeneratorType): | 828 if not isinstance(thing, types.GeneratorType): |
762 return thing | 829 return thing |
763 return stringify(context, mapping, thing) | 830 return stringify(context, mapping, thing) |
764 | 831 |
832 | |
765 def evalrawexp(context, mapping, arg): | 833 def evalrawexp(context, mapping, arg): |
766 """Evaluate given argument as a bare template object which may require | 834 """Evaluate given argument as a bare template object which may require |
767 further processing (such as folding generator of strings)""" | 835 further processing (such as folding generator of strings)""" |
768 func, data = arg | 836 func, data = arg |
769 return func(context, mapping, data) | 837 return func(context, mapping, data) |
770 | 838 |
839 | |
771 def evalwrapped(context, mapping, arg): | 840 def evalwrapped(context, mapping, arg): |
772 """Evaluate given argument to wrapped object""" | 841 """Evaluate given argument to wrapped object""" |
773 thing = evalrawexp(context, mapping, arg) | 842 thing = evalrawexp(context, mapping, arg) |
774 return makewrapped(context, mapping, thing) | 843 return makewrapped(context, mapping, thing) |
844 | |
775 | 845 |
776 def makewrapped(context, mapping, thing): | 846 def makewrapped(context, mapping, thing): |
777 """Lift object to a wrapped type""" | 847 """Lift object to a wrapped type""" |
778 if isinstance(thing, wrapped): | 848 if isinstance(thing, wrapped): |
779 return thing | 849 return thing |
780 thing = _unthunk(context, mapping, thing) | 850 thing = _unthunk(context, mapping, thing) |
781 if isinstance(thing, bytes): | 851 if isinstance(thing, bytes): |
782 return wrappedbytes(thing) | 852 return wrappedbytes(thing) |
783 return wrappedvalue(thing) | 853 return wrappedvalue(thing) |
784 | 854 |
855 | |
785 def evalfuncarg(context, mapping, arg): | 856 def evalfuncarg(context, mapping, arg): |
786 """Evaluate given argument as value type""" | 857 """Evaluate given argument as value type""" |
787 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg)) | 858 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg)) |
859 | |
788 | 860 |
789 def unwrapvalue(context, mapping, thing): | 861 def unwrapvalue(context, mapping, thing): |
790 """Move the inner value object out of the wrapper""" | 862 """Move the inner value object out of the wrapper""" |
791 if isinstance(thing, wrapped): | 863 if isinstance(thing, wrapped): |
792 return thing.tovalue(context, mapping) | 864 return thing.tovalue(context, mapping) |
793 # evalrawexp() may return string, generator of strings or arbitrary object | 865 # evalrawexp() may return string, generator of strings or arbitrary object |
794 # such as date tuple, but filter does not want generator. | 866 # such as date tuple, but filter does not want generator. |
795 return _unthunk(context, mapping, thing) | 867 return _unthunk(context, mapping, thing) |
868 | |
796 | 869 |
797 def evalboolean(context, mapping, arg): | 870 def evalboolean(context, mapping, arg): |
798 """Evaluate given argument as boolean, but also takes boolean literals""" | 871 """Evaluate given argument as boolean, but also takes boolean literals""" |
799 func, data = arg | 872 func, data = arg |
800 if func is runsymbol: | 873 if func is runsymbol: |
804 thing = stringutil.parsebool(data) | 877 thing = stringutil.parsebool(data) |
805 else: | 878 else: |
806 thing = func(context, mapping, data) | 879 thing = func(context, mapping, data) |
807 return makewrapped(context, mapping, thing).tobool(context, mapping) | 880 return makewrapped(context, mapping, thing).tobool(context, mapping) |
808 | 881 |
882 | |
809 def evaldate(context, mapping, arg, err=None): | 883 def evaldate(context, mapping, arg, err=None): |
810 """Evaluate given argument as a date tuple or a date string; returns | 884 """Evaluate given argument as a date tuple or a date string; returns |
811 a (unixtime, offset) tuple""" | 885 a (unixtime, offset) tuple""" |
812 thing = evalrawexp(context, mapping, arg) | 886 thing = evalrawexp(context, mapping, arg) |
813 return unwrapdate(context, mapping, thing, err) | 887 return unwrapdate(context, mapping, thing, err) |
888 | |
814 | 889 |
815 def unwrapdate(context, mapping, thing, err=None): | 890 def unwrapdate(context, mapping, thing, err=None): |
816 if isinstance(thing, date): | 891 if isinstance(thing, date): |
817 return thing.tovalue(context, mapping) | 892 return thing.tovalue(context, mapping) |
818 # TODO: update hgweb to not return bare tuple; then just stringify 'thing' | 893 # TODO: update hgweb to not return bare tuple; then just stringify 'thing' |
824 except error.ParseError: | 899 except error.ParseError: |
825 if not err: | 900 if not err: |
826 raise | 901 raise |
827 raise error.ParseError(err) | 902 raise error.ParseError(err) |
828 | 903 |
904 | |
829 def evalinteger(context, mapping, arg, err=None): | 905 def evalinteger(context, mapping, arg, err=None): |
830 thing = evalrawexp(context, mapping, arg) | 906 thing = evalrawexp(context, mapping, arg) |
831 return unwrapinteger(context, mapping, thing, err) | 907 return unwrapinteger(context, mapping, thing, err) |
908 | |
832 | 909 |
833 def unwrapinteger(context, mapping, thing, err=None): | 910 def unwrapinteger(context, mapping, thing, err=None): |
834 thing = unwrapvalue(context, mapping, thing) | 911 thing = unwrapvalue(context, mapping, thing) |
835 try: | 912 try: |
836 return int(thing) | 913 return int(thing) |
837 except (TypeError, ValueError): | 914 except (TypeError, ValueError): |
838 raise error.ParseError(err or _('not an integer')) | 915 raise error.ParseError(err or _('not an integer')) |
839 | 916 |
917 | |
840 def evalstring(context, mapping, arg): | 918 def evalstring(context, mapping, arg): |
841 return stringify(context, mapping, evalrawexp(context, mapping, arg)) | 919 return stringify(context, mapping, evalrawexp(context, mapping, arg)) |
920 | |
842 | 921 |
843 def evalstringliteral(context, mapping, arg): | 922 def evalstringliteral(context, mapping, arg): |
844 """Evaluate given argument as string template, but returns symbol name | 923 """Evaluate given argument as string template, but returns symbol name |
845 if it is unknown""" | 924 if it is unknown""" |
846 func, data = arg | 925 func, data = arg |
848 thing = func(context, mapping, data, default=data) | 927 thing = func(context, mapping, data, default=data) |
849 else: | 928 else: |
850 thing = func(context, mapping, data) | 929 thing = func(context, mapping, data) |
851 return stringify(context, mapping, thing) | 930 return stringify(context, mapping, thing) |
852 | 931 |
932 | |
853 _unwrapfuncbytype = { | 933 _unwrapfuncbytype = { |
854 None: unwrapvalue, | 934 None: unwrapvalue, |
855 bytes: stringify, | 935 bytes: stringify, |
856 date: unwrapdate, | 936 date: unwrapdate, |
857 int: unwrapinteger, | 937 int: unwrapinteger, |
858 } | 938 } |
939 | |
859 | 940 |
860 def unwrapastype(context, mapping, thing, typ): | 941 def unwrapastype(context, mapping, thing, typ): |
861 """Move the inner value object out of the wrapper and coerce its type""" | 942 """Move the inner value object out of the wrapper and coerce its type""" |
862 try: | 943 try: |
863 f = _unwrapfuncbytype[typ] | 944 f = _unwrapfuncbytype[typ] |
864 except KeyError: | 945 except KeyError: |
865 raise error.ProgrammingError('invalid type specified: %r' % typ) | 946 raise error.ProgrammingError('invalid type specified: %r' % typ) |
866 return f(context, mapping, thing) | 947 return f(context, mapping, thing) |
867 | 948 |
949 | |
868 def runinteger(context, mapping, data): | 950 def runinteger(context, mapping, data): |
869 return int(data) | 951 return int(data) |
870 | 952 |
953 | |
871 def runstring(context, mapping, data): | 954 def runstring(context, mapping, data): |
872 return data | 955 return data |
956 | |
873 | 957 |
874 def _recursivesymbolblocker(key): | 958 def _recursivesymbolblocker(key): |
875 def showrecursion(context, mapping): | 959 def showrecursion(context, mapping): |
876 raise error.Abort(_("recursive reference '%s' in template") % key) | 960 raise error.Abort(_("recursive reference '%s' in template") % key) |
961 | |
877 return showrecursion | 962 return showrecursion |
963 | |
878 | 964 |
879 def runsymbol(context, mapping, key, default=''): | 965 def runsymbol(context, mapping, key, default=''): |
880 v = context.symbol(mapping, key) | 966 v = context.symbol(mapping, key) |
881 if v is None: | 967 if v is None: |
882 # put poison to cut recursion. we can't move this to parsing phase | 968 # put poison to cut recursion. we can't move this to parsing phase |
894 except ResourceUnavailable: | 980 except ResourceUnavailable: |
895 # unsupported keyword is mapped to empty just like unknown keyword | 981 # unsupported keyword is mapped to empty just like unknown keyword |
896 return None | 982 return None |
897 return v | 983 return v |
898 | 984 |
985 | |
899 def runtemplate(context, mapping, template): | 986 def runtemplate(context, mapping, template): |
900 for arg in template: | 987 for arg in template: |
901 yield evalrawexp(context, mapping, arg) | 988 yield evalrawexp(context, mapping, arg) |
989 | |
902 | 990 |
903 def runfilter(context, mapping, data): | 991 def runfilter(context, mapping, data): |
904 arg, filt = data | 992 arg, filt = data |
905 thing = evalrawexp(context, mapping, arg) | 993 thing = evalrawexp(context, mapping, arg) |
906 intype = getattr(filt, '_intype', None) | 994 intype = getattr(filt, '_intype', None) |
908 thing = unwrapastype(context, mapping, thing, intype) | 996 thing = unwrapastype(context, mapping, thing, intype) |
909 return filt(thing) | 997 return filt(thing) |
910 except error.ParseError as e: | 998 except error.ParseError as e: |
911 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt)) | 999 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt)) |
912 | 1000 |
1001 | |
913 def _formatfiltererror(arg, filt): | 1002 def _formatfiltererror(arg, filt): |
914 fn = pycompat.sysbytes(filt.__name__) | 1003 fn = pycompat.sysbytes(filt.__name__) |
915 sym = findsymbolicname(arg) | 1004 sym = findsymbolicname(arg) |
916 if not sym: | 1005 if not sym: |
917 return _("incompatible use of template filter '%s'") % fn | 1006 return _("incompatible use of template filter '%s'") % fn |
918 return (_("template filter '%s' is not compatible with keyword '%s'") | 1007 return _("template filter '%s' is not compatible with keyword '%s'") % ( |
919 % (fn, sym)) | 1008 fn, |
1009 sym, | |
1010 ) | |
1011 | |
920 | 1012 |
921 def _iteroverlaymaps(context, origmapping, newmappings): | 1013 def _iteroverlaymaps(context, origmapping, newmappings): |
922 """Generate combined mappings from the original mapping and an iterable | 1014 """Generate combined mappings from the original mapping and an iterable |
923 of partial mappings to override the original""" | 1015 of partial mappings to override the original""" |
924 for i, nm in enumerate(newmappings): | 1016 for i, nm in enumerate(newmappings): |
925 lm = context.overlaymap(origmapping, nm) | 1017 lm = context.overlaymap(origmapping, nm) |
926 lm['index'] = i | 1018 lm['index'] = i |
927 yield lm | 1019 yield lm |
1020 | |
928 | 1021 |
929 def _applymap(context, mapping, d, darg, targ): | 1022 def _applymap(context, mapping, d, darg, targ): |
930 try: | 1023 try: |
931 diter = d.itermaps(context) | 1024 diter = d.itermaps(context) |
932 except error.ParseError as err: | 1025 except error.ParseError as err: |
936 hint = _("keyword '%s' does not support map operation") % sym | 1029 hint = _("keyword '%s' does not support map operation") % sym |
937 raise error.ParseError(bytes(err), hint=hint) | 1030 raise error.ParseError(bytes(err), hint=hint) |
938 for lm in _iteroverlaymaps(context, mapping, diter): | 1031 for lm in _iteroverlaymaps(context, mapping, diter): |
939 yield evalrawexp(context, lm, targ) | 1032 yield evalrawexp(context, lm, targ) |
940 | 1033 |
1034 | |
941 def runmap(context, mapping, data): | 1035 def runmap(context, mapping, data): |
942 darg, targ = data | 1036 darg, targ = data |
943 d = evalwrapped(context, mapping, darg) | 1037 d = evalwrapped(context, mapping, darg) |
944 return mappedgenerator(_applymap, args=(mapping, d, darg, targ)) | 1038 return mappedgenerator(_applymap, args=(mapping, d, darg, targ)) |
1039 | |
945 | 1040 |
946 def runmember(context, mapping, data): | 1041 def runmember(context, mapping, data): |
947 darg, memb = data | 1042 darg, memb = data |
948 d = evalwrapped(context, mapping, darg) | 1043 d = evalwrapped(context, mapping, darg) |
949 if isinstance(d, mappable): | 1044 if isinstance(d, mappable): |
956 if not sym: | 1051 if not sym: |
957 raise | 1052 raise |
958 hint = _("keyword '%s' does not support member operation") % sym | 1053 hint = _("keyword '%s' does not support member operation") % sym |
959 raise error.ParseError(bytes(err), hint=hint) | 1054 raise error.ParseError(bytes(err), hint=hint) |
960 | 1055 |
1056 | |
961 def runnegate(context, mapping, data): | 1057 def runnegate(context, mapping, data): |
962 data = evalinteger(context, mapping, data, | 1058 data = evalinteger( |
963 _('negation needs an integer argument')) | 1059 context, mapping, data, _('negation needs an integer argument') |
1060 ) | |
964 return -data | 1061 return -data |
1062 | |
965 | 1063 |
966 def runarithmetic(context, mapping, data): | 1064 def runarithmetic(context, mapping, data): |
967 func, left, right = data | 1065 func, left, right = data |
968 left = evalinteger(context, mapping, left, | 1066 left = evalinteger( |
969 _('arithmetic only defined on integers')) | 1067 context, mapping, left, _('arithmetic only defined on integers') |
970 right = evalinteger(context, mapping, right, | 1068 ) |
971 _('arithmetic only defined on integers')) | 1069 right = evalinteger( |
1070 context, mapping, right, _('arithmetic only defined on integers') | |
1071 ) | |
972 try: | 1072 try: |
973 return func(left, right) | 1073 return func(left, right) |
974 except ZeroDivisionError: | 1074 except ZeroDivisionError: |
975 raise error.Abort(_('division by zero is not defined')) | 1075 raise error.Abort(_('division by zero is not defined')) |
1076 | |
976 | 1077 |
977 def joinitems(itemiter, sep): | 1078 def joinitems(itemiter, sep): |
978 """Join items with the separator; Returns generator of bytes""" | 1079 """Join items with the separator; Returns generator of bytes""" |
979 first = True | 1080 first = True |
980 for x in itemiter: | 1081 for x in itemiter: |