changeset 37274:7d3bc1d4e871

templater: pass (context, mapping) down to unwraphybrid() See the subsequent patches for why. I initially thought it would be wrong to pass a mapping to flatten() and stringify() since these functions may be applied to a tree of generators, where each node should be bound to the mapping when it was evaluated. But, actually that isn't a problem. If an intermediate node has to override a mapping dict, it can do on unwraphybrid() and yield "unwrapped" generator of byte strings: "{f(g(v))}" # literal template example. ^^^^ # g() want to override a mapping, so it returns a wrapped # object 'G{V}' with partial mapping 'lm' attached. ^^^^^^^ # f() stringifies 'G{V}', starting from a mapping 'm'. # when unwrapping 'G{}', it updates 'm' with 'lm', and # passes it to 'V'. This structure is important for the formatter (and the hgweb) to build a static template keyword, which can't access a mapping dict until evaluation phase.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 17 Mar 2018 20:09:05 +0900
parents 0194dac77c93
children 83e1bbd48991
files mercurial/templatefuncs.py mercurial/templater.py mercurial/templateutil.py tests/test-template-engine.t
diffstat 4 files changed, 29 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/templatefuncs.py	Mon Apr 02 16:18:33 2018 -0700
+++ b/mercurial/templatefuncs.py	Sat Mar 17 20:09:05 2018 +0900
@@ -283,7 +283,8 @@
     keytype = getattr(haystack, 'keytype', None)
     try:
         needle = evalrawexp(context, mapping, args[0])
-        needle = templateutil.unwrapastype(needle, keytype or bytes)
+        needle = templateutil.unwrapastype(context, mapping, needle,
+                                           keytype or bytes)
         found = (needle in haystack)
     except error.ParseError:
         found = False
--- a/mercurial/templater.py	Mon Apr 02 16:18:33 2018 -0700
+++ b/mercurial/templater.py	Sat Mar 17 20:09:05 2018 +0900
@@ -679,7 +679,7 @@
         if extramapping:
             extramapping.update(mapping)
             mapping = extramapping
-        return templateutil.flatten(func(self, mapping, data))
+        return templateutil.flatten(self, mapping, func(self, mapping, data))
 
 engines = {'default': engine}
 
--- a/mercurial/templateutil.py	Mon Apr 02 16:18:33 2018 -0700
+++ b/mercurial/templateutil.py	Sat Mar 17 20:09:05 2018 +0900
@@ -120,7 +120,7 @@
         prefmt = pycompat.bytestr
     return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
 
-def unwraphybrid(thing):
+def unwraphybrid(context, mapping, thing):
     """Return an object which can be stringified possibly by using a legacy
     template"""
     gen = getattr(thing, 'gen', None)
@@ -241,9 +241,9 @@
     if context.preload(endname):
         yield context.process(endname, mapping)
 
-def flatten(thing):
+def flatten(context, mapping, thing):
     """Yield a single stream from a possibly nested set of iterators"""
-    thing = unwraphybrid(thing)
+    thing = unwraphybrid(context, mapping, thing)
     if isinstance(thing, bytes):
         yield thing
     elif isinstance(thing, str):
@@ -257,7 +257,7 @@
         yield pycompat.bytestr(thing)
     else:
         for i in thing:
-            i = unwraphybrid(i)
+            i = unwraphybrid(context, mapping, i)
             if isinstance(i, bytes):
                 yield i
             elif i is None:
@@ -265,14 +265,14 @@
             elif not util.safehasattr(i, '__iter__'):
                 yield pycompat.bytestr(i)
             else:
-                for j in flatten(i):
+                for j in flatten(context, mapping, i):
                     yield j
 
-def stringify(thing):
+def stringify(context, mapping, thing):
     """Turn values into bytes by converting into text and concatenating them"""
     if isinstance(thing, bytes):
         return thing  # retain localstr to be round-tripped
-    return b''.join(flatten(thing))
+    return b''.join(flatten(context, mapping, thing))
 
 def findsymbolicname(arg):
     """Find symbolic name for the given compiled expression; returns None
@@ -294,17 +294,17 @@
 
 def evalfuncarg(context, mapping, arg):
     """Evaluate given argument as value type"""
-    return _unwrapvalue(evalrawexp(context, mapping, arg))
+    return _unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
 
 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
 # is fixed. we can't do that right now because join() has to take a generator
 # of byte strings as it is, not a lazy byte string.
-def _unwrapvalue(thing):
+def _unwrapvalue(context, mapping, thing):
     thing = unwrapvalue(thing)
     # evalrawexp() may return string, generator of strings or arbitrary object
     # such as date tuple, but filter does not want generator.
     if isinstance(thing, types.GeneratorType):
-        thing = stringify(thing)
+        thing = stringify(context, mapping, thing)
     return thing
 
 def evalboolean(context, mapping, arg):
@@ -322,15 +322,16 @@
         return thing
     # other objects are evaluated as strings, which means 0 is True, but
     # empty dict/list should be False as they are expected to be ''
-    return bool(stringify(thing))
+    return bool(stringify(context, mapping, thing))
 
 def evaldate(context, mapping, arg, err=None):
     """Evaluate given argument as a date tuple or a date string; returns
     a (unixtime, offset) tuple"""
-    return unwrapdate(evalrawexp(context, mapping, arg), err)
+    thing = evalrawexp(context, mapping, arg)
+    return unwrapdate(context, mapping, thing, err)
 
-def unwrapdate(thing, err=None):
-    thing = _unwrapvalue(thing)
+def unwrapdate(context, mapping, thing, err=None):
+    thing = _unwrapvalue(context, mapping, thing)
     try:
         return dateutil.parsedate(thing)
     except AttributeError:
@@ -341,17 +342,18 @@
         raise error.ParseError(err)
 
 def evalinteger(context, mapping, arg, err=None):
-    return unwrapinteger(evalrawexp(context, mapping, arg), err)
+    thing = evalrawexp(context, mapping, arg)
+    return unwrapinteger(context, mapping, thing, err)
 
-def unwrapinteger(thing, err=None):
-    thing = _unwrapvalue(thing)
+def unwrapinteger(context, mapping, thing, err=None):
+    thing = _unwrapvalue(context, mapping, thing)
     try:
         return int(thing)
     except (TypeError, ValueError):
         raise error.ParseError(err or _('not an integer'))
 
 def evalstring(context, mapping, arg):
-    return stringify(evalrawexp(context, mapping, arg))
+    return stringify(context, mapping, evalrawexp(context, mapping, arg))
 
 def evalstringliteral(context, mapping, arg):
     """Evaluate given argument as string template, but returns symbol name
@@ -361,7 +363,7 @@
         thing = func(context, mapping, data, default=data)
     else:
         thing = func(context, mapping, data)
-    return stringify(thing)
+    return stringify(context, mapping, thing)
 
 _unwrapfuncbytype = {
     None: _unwrapvalue,
@@ -370,13 +372,13 @@
     int: unwrapinteger,
 }
 
-def unwrapastype(thing, typ):
+def unwrapastype(context, mapping, thing, typ):
     """Move the inner value object out of the wrapper and coerce its type"""
     try:
         f = _unwrapfuncbytype[typ]
     except KeyError:
         raise error.ProgrammingError('invalid type specified: %r' % typ)
-    return f(thing)
+    return f(context, mapping, thing)
 
 def runinteger(context, mapping, data):
     return int(data)
@@ -425,8 +427,9 @@
 def runfilter(context, mapping, data):
     arg, filt = data
     thing = evalrawexp(context, mapping, arg)
+    intype = getattr(filt, '_intype', None)
     try:
-        thing = unwrapastype(thing, getattr(filt, '_intype', None))
+        thing = unwrapastype(context, mapping, thing, intype)
         return filt(thing)
     except error.ParseError as e:
         raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
--- a/tests/test-template-engine.t	Mon Apr 02 16:18:33 2018 -0700
+++ b/tests/test-template-engine.t	Sat Mar 17 20:09:05 2018 +0900
@@ -24,7 +24,7 @@
   >                 v = v(**pycompat.strkwargs(props))
   >             elif callable(v):
   >                 v = v(self, props)
-  >             v = templateutil.stringify(v)
+  >             v = templateutil.stringify(self, props, v)
   >             tmpl = tmpl.replace(b'{{%s}}' % k, v)
   >         yield tmpl
   >