# HG changeset patch # User Yuya Nishihara # Date 2017-09-19 14:13:46 # Node ID ee0d74083a22b4381b6a8bf1623e5a2bbdce58d2 # Parent 7259f0ddfc0f18138420e7c9c7e4145a25016d7b templater: store revisions as ints so min/max won't compare them as strings Because a template value has no explicit type (like ancient PHP), ifcontains() has to coerce the type of the needle. Before, it was always converted to a string, which meant any container type should be a list/dict of strings. This no longer works since we've introduced min/max functions. In order to work around the untyped nature of templater, this patch adds a type specifier to hybrid dict/list. It isn't named as "valuetype" since the _hybrid class can also wrap a dict. diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py --- a/mercurial/templatekw.py +++ b/mercurial/templatekw.py @@ -37,12 +37,13 @@ class _hybrid(object): - "{files|json}" """ - def __init__(self, gen, values, makemap, joinfmt): + def __init__(self, gen, values, makemap, joinfmt, keytype=None): if gen is not None: self.gen = gen # generator or function returning generator self._values = values self._makemap = makemap self.joinfmt = joinfmt + self.keytype = keytype # hint for 'x in y' where type(x) is unresolved def gen(self): """Default generator to stringify this as {join(self, ' ')}""" for i, x in enumerate(self._values): @@ -788,15 +789,14 @@ def showparents(**args): repo = args['repo'] ctx = args['ctx'] pctxs = scmutil.meaningfulparents(repo, ctx) - # ifcontains() needs a list of str - prevs = ["%d" % p.rev() for p in pctxs] + prevs = [p.rev() for p in pctxs] parents = [[('rev', p.rev()), ('node', p.hex()), ('phase', p.phasestr())] for p in pctxs] f = _showlist('parent', parents, args) - return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}}, - lambda x: scmutil.formatchangeid(repo[int(x)])) + return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}}, + lambda x: scmutil.formatchangeid(repo[x]), keytype=int) @templatekeyword('phase') def showphase(repo, ctx, templ, **args): @@ -818,12 +818,10 @@ def showrevslist(name, revs, **args): be evaluated""" args = pycompat.byteskwargs(args) repo = args['ctx'].repo() - # ifcontains() needs a list of str - revs = ["%d" % r for r in revs] - f = _showlist(name, revs, args) + f = _showlist(name, ['%d' % r for r in revs], args) return _hybrid(f, revs, - lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}}, - pycompat.identity) + lambda x: {name: x, 'ctx': repo[x], 'revcache': {}}, + pycompat.identity, keytype=int) @templatekeyword('subrepos') def showsubrepos(**args): diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -333,12 +333,12 @@ def evalboolean(context, mapping, arg): # empty dict/list should be False as they are expected to be '' return bool(stringify(thing)) -def evalinteger(context, mapping, arg, err): +def evalinteger(context, mapping, arg, err=None): v = evalfuncarg(context, mapping, arg) try: return int(v) except (TypeError, ValueError): - raise error.ParseError(err) + raise error.ParseError(err or _('not an integer')) def evalstring(context, mapping, arg): return stringify(evalrawexp(context, mapping, arg)) @@ -353,6 +353,20 @@ def evalstringliteral(context, mapping, thing = func(context, mapping, data) return stringify(thing) +_evalfuncbytype = { + bool: evalboolean, + bytes: evalstring, + int: evalinteger, +} + +def evalastype(context, mapping, arg, typ): + """Evaluate given argument and coerce its type""" + try: + f = _evalfuncbytype[typ] + except KeyError: + raise error.ProgrammingError('invalid type specified: %r' % typ) + return f(context, mapping, arg) + def runinteger(context, mapping, data): return int(data) @@ -782,8 +796,9 @@ def ifcontains(context, mapping, args): # i18n: "ifcontains" is a keyword raise error.ParseError(_("ifcontains expects three or four arguments")) - needle = evalstring(context, mapping, args[0]) haystack = evalfuncarg(context, mapping, args[1]) + needle = evalastype(context, mapping, args[0], + getattr(haystack, 'keytype', None) or bytes) if needle in haystack: yield evalrawexp(context, mapping, args[2]) diff --git a/tests/test-command-template.t b/tests/test-command-template.t --- a/tests/test-command-template.t +++ b/tests/test-command-template.t @@ -3147,6 +3147,13 @@ Test manifest/get() can be join()-ed as $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n' default +Test min/max of integers + + $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n' + 9 + $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n' + 10 + Test dot operator precedence: $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'