# HG changeset patch # User Yuya Nishihara # Date 1521284945 -32400 # Node ID 7d3bc1d4e8717d4c974f839b23cb11b98cbfbb70 # Parent 0194dac77c93912e3073466c440f4f175fc93bc0 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. diff -r 0194dac77c93 -r 7d3bc1d4e871 mercurial/templatefuncs.py --- 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 diff -r 0194dac77c93 -r 7d3bc1d4e871 mercurial/templater.py --- 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} diff -r 0194dac77c93 -r 7d3bc1d4e871 mercurial/templateutil.py --- 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)) diff -r 0194dac77c93 -r 7d3bc1d4e871 tests/test-template-engine.t --- 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 >