Patchwork [1,of,8,V2] templater: pass (context, mapping) down to unwraphybrid()

mail settings
Submitter Yuya Nishihara
Date April 3, 2018, 3:40 p.m.
Message ID <164e39f1fc56cebdaa44.1522770003@mimosa>
Download mbox | patch
Permalink /patch/30162/
State Accepted
Headers show


Yuya Nishihara - April 3, 2018, 3:40 p.m.
# HG changeset patch
# User Yuya Nishihara <>
# Date 1521284945 -32400
#      Sat Mar 17 20:09:05 2018 +0900
# Node ID 164e39f1fc56cebdaa441458dfa6436939c97267
# Parent  c118bb7c938ae92ffdfbab5ea977f1c029c87599
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


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -283,7 +283,8 @@  def ifcontains(context, mapping, args):
     keytype = getattr(haystack, 'keytype', None)
         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
diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -679,7 +679,7 @@  class engine(object):
         if extramapping:
             mapping = extramapping
-        return templateutil.flatten(func(self, mapping, data))
+        return templateutil.flatten(self, mapping, func(self, mapping, data))
 engines = {'default': engine}
diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -120,7 +120,7 @@  def hybridlist(data, name, fmt=None, gen
         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
     gen = getattr(thing, 'gen', None)
@@ -241,9 +241,9 @@  def _showcompatlist(context, mapping, na
     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 @@  def flatten(thing):
         yield pycompat.bytestr(thing)
         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 @@  def flatten(thing):
             elif not util.safehasattr(i, '__iter__'):
                 yield pycompat.bytestr(i)
-                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 evalrawexp(context, mapping, arg):
 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 @@  def evalboolean(context, mapping, arg):
         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)
         return dateutil.parsedate(thing)
     except AttributeError:
@@ -341,17 +342,18 @@  def unwrapdate(thing, err=None):
         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)
         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 @@  def evalstringliteral(context, mapping, 
         thing = func(context, mapping, data, default=data)
         thing = func(context, mapping, data)
-    return stringify(thing)
+    return stringify(context, mapping, thing)
 _unwrapfuncbytype = {
     None: _unwrapvalue,
@@ -370,13 +372,13 @@  def evalstringliteral(context, mapping, 
     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"""
         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 runtemplate(context, mapping, templa
 def runfilter(context, mapping, data):
     arg, filt = data
     thing = evalrawexp(context, mapping, arg)
+    intype = getattr(filt, '_intype', None)
-        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))
diff --git a/tests/test-template-engine.t b/tests/test-template-engine.t
--- a/tests/test-template-engine.t
+++ b/tests/test-template-engine.t
@@ -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