changeset 37499:75c13343cf38

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().
author Yuya Nishihara <yuya@tcha.org>
date Sun, 18 Mar 2018 23:36:52 +0900
parents aacfca6f9767
children 8bb3899a0f47
files mercurial/templater.py mercurial/templateutil.py tests/test-command-template.t
diffstat 3 files changed, 51 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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
--- 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: <generator *> 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'