Mercurial > public > mercurial-scm > hg
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) |