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: