# HG changeset patch # User Yuya Nishihara # Date 1521383812 -32400 # Node ID 75c13343cf381f0396f2c75350f319ac5bf00c03 # Parent aacfca6f97675779c4f75614a737ce4aca3c13b0 templater: wrap result of '%' operation so it never looks like a thunk This fixes min/max()/json() of map result. Before, it was taken as a lazy byte string and stringified by evalfuncarg(). diff -r aacfca6f9767 -r 75c13343cf38 mercurial/templater.py --- a/mercurial/templater.py Thu Jan 18 12:54:01 2018 +0100 +++ b/mercurial/templater.py Sun Mar 18 23:36:52 2018 +0900 @@ -48,6 +48,10 @@ mappinggenerator, mappinglist represents mappings (i.e. a list of dicts), which may have default output format. + +mappedgenerator + a lazily-evaluated list of byte strings, which is e.g. a result of % + operation. """ from __future__ import absolute_import, print_function diff -r aacfca6f9767 -r 75c13343cf38 mercurial/templateutil.py --- a/mercurial/templateutil.py Thu Jan 18 12:54:01 2018 +0100 +++ b/mercurial/templateutil.py Sun Mar 18 23:36:52 2018 +0900 @@ -227,6 +227,33 @@ def itermaps(self, context): return iter(self._mappings) +class mappedgenerator(wrapped): + """Wrapper for generator of strings which acts as a list + + The function ``make(context, *args)`` should return a generator of + byte strings, or a generator of (possibly nested) generators of byte + strings (i.e. a generator for a list of byte strings.) + """ + + def __init__(self, make, args=()): + self._make = make + self._args = args + + def _gen(self, context): + return self._make(context, *self._args) + + def itermaps(self, context): + raise error.ParseError(_('list of strings is not mappable')) + + def join(self, context, mapping, sep): + return joinitems(self._gen(context), sep) + + def show(self, context, mapping): + return self.join(context, mapping, '') + + def tovalue(self, context, mapping): + return [stringify(context, mapping, x) for x in self._gen(context)] + def hybriddict(data, key='key', value='value', fmt=None, gen=None): """Wrap data to support both dict-like and string-like operations""" prefmt = pycompat.identity @@ -589,18 +616,23 @@ lm['index'] = i yield lm +def _applymap(context, mapping, diter, targ): + for lm in _iteroverlaymaps(context, mapping, diter): + yield evalrawexp(context, lm, targ) + def runmap(context, mapping, data): darg, targ = data d = evalrawexp(context, mapping, darg) # TODO: a generator should be rejected because it is a thunk of lazy # string, but we can't because hgweb abuses generator as a keyword # that returns a list of dicts. + # TODO: drop _checkeditermaps() and pass 'd' to mappedgenerator so it + # can be restarted. if isinstance(d, wrapped): diter = d.itermaps(context) else: diter = _checkeditermaps(darg, d) - for lm in _iteroverlaymaps(context, mapping, diter): - yield evalrawexp(context, lm, targ) + return mappedgenerator(_applymap, args=(mapping, diter, targ)) def runmember(context, mapping, data): darg, memb = data diff -r aacfca6f9767 -r 75c13343cf38 tests/test-command-template.t --- a/tests/test-command-template.t Thu Jan 18 12:54:01 2018 +0100 +++ b/tests/test-command-template.t Sun Mar 18 23:36:52 2018 +0900 @@ -3217,7 +3217,7 @@ hg: parse error: None is not iterable of mappings [255] $ hg log -R latesttag -r tip -T '{extras % "{key}\n" % "{key}\n"}' - hg: parse error: is not iterable of mappings (glob) + hg: parse error: list of strings is not mappable [255] Test new-style inline templating of non-list/dict type: @@ -3255,6 +3255,13 @@ $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n' 10 +Test min/max over map operation: + + $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n' + at3 + $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n' + t3 + Test min/max of if() result $ cd latesttag @@ -3842,6 +3849,11 @@ $ hg log -r0 -T '{extras|json}\n' {"branch": "default"} +Test json filter applied to map result: + + $ hg log -r0 -T '{json(extras % "{key}")}\n' + ["branch"] + Test localdate(date, tz) function: $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'