comparison mercurial/templateutil.py @ 38289:f9c426385853

templater: abstract truth testing to fix {if(list_of_empty_strings)} Non-empty list should always be True even if it's stringified to ''. Spotted by Martin von Zweigbergk.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 09 Jun 2018 13:34:47 +0900
parents 851fc9d42d6d
children 88e7105b5cd9
comparison
equal deleted inserted replaced
38288:a9de1d28681c 38289:f9c426385853
83 A pre-configured template may be rendered if the underlying object is 83 A pre-configured template may be rendered if the underlying object is
84 not printable. 84 not printable.
85 """ 85 """
86 86
87 @abc.abstractmethod 87 @abc.abstractmethod
88 def tobool(self, context, mapping):
89 """Return a boolean representation of the inner value"""
90
91 @abc.abstractmethod
88 def tovalue(self, context, mapping): 92 def tovalue(self, context, mapping):
89 """Move the inner value object out or create a value representation 93 """Move the inner value object out or create a value representation
90 94
91 A returned value must be serializable by templaterfilters.json(). 95 A returned value must be serializable by templaterfilters.json().
92 """ 96 """
134 return joinitems(pycompat.iterbytestr(self._value), sep) 138 return joinitems(pycompat.iterbytestr(self._value), sep)
135 139
136 def show(self, context, mapping): 140 def show(self, context, mapping):
137 return self._value 141 return self._value
138 142
143 def tobool(self, context, mapping):
144 return bool(self._value)
145
139 def tovalue(self, context, mapping): 146 def tovalue(self, context, mapping):
140 return self._value 147 return self._value
141 148
142 class wrappedvalue(wrapped): 149 class wrappedvalue(wrapped):
143 """Generic wrapper for pure non-list/dict/bytes value""" 150 """Generic wrapper for pure non-list/dict/bytes value"""
167 def show(self, context, mapping): 174 def show(self, context, mapping):
168 if self._value is None: 175 if self._value is None:
169 return b'' 176 return b''
170 return pycompat.bytestr(self._value) 177 return pycompat.bytestr(self._value)
171 178
179 def tobool(self, context, mapping):
180 if self._value is None:
181 return False
182 if isinstance(self._value, bool):
183 return self._value
184 # otherwise evaluate as string, which means 0 is True
185 return bool(pycompat.bytestr(self._value))
186
172 def tovalue(self, context, mapping): 187 def tovalue(self, context, mapping):
173 return self._value 188 return self._value
174 189
175 class date(mappable, wrapped): 190 class date(mappable, wrapped):
176 """Wrapper for date tuple""" 191 """Wrapper for date tuple"""
198 def show(self, context, mapping): 213 def show(self, context, mapping):
199 return '%d %d' % (self._unixtime, self._tzoffset) 214 return '%d %d' % (self._unixtime, self._tzoffset)
200 215
201 def tomap(self, context): 216 def tomap(self, context):
202 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset} 217 return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
218
219 def tobool(self, context, mapping):
220 return True
203 221
204 def tovalue(self, context, mapping): 222 def tovalue(self, context, mapping):
205 return (self._unixtime, self._tzoffset) 223 return (self._unixtime, self._tzoffset)
206 224
207 class hybrid(wrapped): 225 class hybrid(wrapped):
270 return self.join(context, mapping, ' ') 288 return self.join(context, mapping, ' ')
271 if callable(gen): 289 if callable(gen):
272 return gen() 290 return gen()
273 return gen 291 return gen
274 292
293 def tobool(self, context, mapping):
294 return bool(self._values)
295
275 def tovalue(self, context, mapping): 296 def tovalue(self, context, mapping):
276 # TODO: make it non-recursive for trivial lists/dicts 297 # TODO: make it non-recursive for trivial lists/dicts
277 xs = self._values 298 xs = self._values
278 if util.safehasattr(xs, 'get'): 299 if util.safehasattr(xs, 'get'):
279 return {k: unwrapvalue(context, mapping, v) 300 return {k: unwrapvalue(context, mapping, v)
324 if gen is None: 345 if gen is None:
325 return pycompat.bytestr(self._value) 346 return pycompat.bytestr(self._value)
326 if callable(gen): 347 if callable(gen):
327 return gen() 348 return gen()
328 return gen 349 return gen
350
351 def tobool(self, context, mapping):
352 return bool(self.tovalue(context, mapping))
329 353
330 def tovalue(self, context, mapping): 354 def tovalue(self, context, mapping):
331 return _unthunk(context, mapping, self._value) 355 return _unthunk(context, mapping, self._value)
332 356
333 class _mappingsequence(wrapped): 357 class _mappingsequence(wrapped):
394 self._args = args 418 self._args = args
395 419
396 def itermaps(self, context): 420 def itermaps(self, context):
397 return self._make(context, *self._args) 421 return self._make(context, *self._args)
398 422
423 def tobool(self, context, mapping):
424 return _nonempty(self.itermaps(context))
425
399 class mappinglist(_mappingsequence): 426 class mappinglist(_mappingsequence):
400 """Wrapper for list of template mappings""" 427 """Wrapper for list of template mappings"""
401 428
402 def __init__(self, mappings, name=None, tmpl=None, sep=''): 429 def __init__(self, mappings, name=None, tmpl=None, sep=''):
403 super(mappinglist, self).__init__(name, tmpl, sep) 430 super(mappinglist, self).__init__(name, tmpl, sep)
404 self._mappings = mappings 431 self._mappings = mappings
405 432
406 def itermaps(self, context): 433 def itermaps(self, context):
407 return iter(self._mappings) 434 return iter(self._mappings)
435
436 def tobool(self, context, mapping):
437 return bool(self._mappings)
408 438
409 class mappedgenerator(wrapped): 439 class mappedgenerator(wrapped):
410 """Wrapper for generator of strings which acts as a list 440 """Wrapper for generator of strings which acts as a list
411 441
412 The function ``make(context, *args)`` should return a generator of 442 The function ``make(context, *args)`` should return a generator of
446 def join(self, context, mapping, sep): 476 def join(self, context, mapping, sep):
447 return joinitems(self._gen(context), sep) 477 return joinitems(self._gen(context), sep)
448 478
449 def show(self, context, mapping): 479 def show(self, context, mapping):
450 return self.join(context, mapping, '') 480 return self.join(context, mapping, '')
481
482 def tobool(self, context, mapping):
483 return _nonempty(self._gen(context))
451 484
452 def tovalue(self, context, mapping): 485 def tovalue(self, context, mapping):
453 return [stringify(context, mapping, x) for x in self._gen(context)] 486 return [stringify(context, mapping, x) for x in self._gen(context)]
454 487
455 def hybriddict(data, key='key', value='value', fmt=None, gen=None): 488 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
605 elif func is runfilter: 638 elif func is runfilter:
606 arg = data[0] 639 arg = data[0]
607 else: 640 else:
608 return None 641 return None
609 642
643 def _nonempty(xiter):
644 try:
645 next(xiter)
646 return True
647 except StopIteration:
648 return False
649
610 def _unthunk(context, mapping, thing): 650 def _unthunk(context, mapping, thing):
611 """Evaluate a lazy byte string into value""" 651 """Evaluate a lazy byte string into value"""
612 if not isinstance(thing, types.GeneratorType): 652 if not isinstance(thing, types.GeneratorType):
613 return thing 653 return thing
614 return stringify(context, mapping, thing) 654 return stringify(context, mapping, thing)
653 if thing is None: 693 if thing is None:
654 # not a template keyword, takes as a boolean literal 694 # not a template keyword, takes as a boolean literal
655 thing = stringutil.parsebool(data) 695 thing = stringutil.parsebool(data)
656 else: 696 else:
657 thing = func(context, mapping, data) 697 thing = func(context, mapping, data)
658 if isinstance(thing, wrapped): 698 return makewrapped(context, mapping, thing).tobool(context, mapping)
659 thing = thing.tovalue(context, mapping)
660 if isinstance(thing, bool):
661 return thing
662 # other objects are evaluated as strings, which means 0 is True, but
663 # empty dict/list should be False as they are expected to be ''
664 return bool(stringify(context, mapping, thing))
665 699
666 def evaldate(context, mapping, arg, err=None): 700 def evaldate(context, mapping, arg, err=None):
667 """Evaluate given argument as a date tuple or a date string; returns 701 """Evaluate given argument as a date tuple or a date string; returns
668 a (unixtime, offset) tuple""" 702 a (unixtime, offset) tuple"""
669 thing = evalrawexp(context, mapping, arg) 703 thing = evalrawexp(context, mapping, arg)