# HG changeset patch # User Yuya Nishihara # Date 1521599421 -32400 # Node ID 06d11cd905166ed5a8aa300abff4b5f5ef052c2b # Parent 12b6ee9e88f3016b294f2f2c141cbc5f8b4e6e67 templater: promote getmember() to an interface of wrapped types diff -r 12b6ee9e88f3 -r 06d11cd90516 mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Wed Mar 21 01:39:44 2018 +0900 +++ b/mercurial/hgweb/webutil.py Wed Mar 21 11:30:21 2018 +0900 @@ -713,6 +713,9 @@ def __copy__(self): return sessionvars(copy.copy(self._vars), self._start) + def getmember(self, context, mapping, key): + return self._vars.get(key) + def itermaps(self, context): separator = self._start for key, value in sorted(self._vars.iteritems()): diff -r 12b6ee9e88f3 -r 06d11cd90516 mercurial/templatefuncs.py --- a/mercurial/templatefuncs.py Wed Mar 21 01:39:44 2018 +0900 +++ b/mercurial/templatefuncs.py Wed Mar 21 11:30:21 2018 +0900 @@ -262,12 +262,13 @@ raise error.ParseError(_("get() expects two arguments")) dictarg = evalwrapped(context, mapping, args[0]) - if not util.safehasattr(dictarg, 'getmember'): + key = evalfuncarg(context, mapping, args[1]) + try: + return dictarg.getmember(context, mapping, key) + except error.ParseError as err: # i18n: "get" is a keyword - raise error.ParseError(_("get() expects a dict as first argument")) - - key = evalfuncarg(context, mapping, args[1]) - return dictarg.getmember(context, mapping, key) + hint = _("get() expects a dict as first argument") + raise error.ParseError(bytes(err), hint=hint) @templatefunc('if(expr, then[, else])') def if_(context, mapping, args): diff -r 12b6ee9e88f3 -r 06d11cd90516 mercurial/templateutil.py --- a/mercurial/templateutil.py Wed Mar 21 01:39:44 2018 +0900 +++ b/mercurial/templateutil.py Wed Mar 21 11:30:21 2018 +0900 @@ -38,6 +38,14 @@ __metaclass__ = abc.ABCMeta @abc.abstractmethod + def getmember(self, context, mapping, key): + """Return a member item for the specified key + + A returned object may be either a wrapped object or a pure value + depending on the self type. + """ + + @abc.abstractmethod def itermaps(self, context): """Yield each template mapping""" @@ -72,6 +80,10 @@ def __init__(self, value): self._value = value + def getmember(self, context, mapping, key): + raise error.ParseError(_('%r is not a dictionary') + % pycompat.bytestr(self._value)) + def itermaps(self, context): raise error.ParseError(_('%r is not iterable of mappings') % pycompat.bytestr(self._value)) @@ -91,6 +103,9 @@ def __init__(self, value): self._value = value + def getmember(self, context, mapping, key): + raise error.ParseError(_('%r is not a dictionary') % self._value) + def itermaps(self, context): raise error.ParseError(_('%r is not iterable of mappings') % self._value) @@ -196,6 +211,10 @@ def tomap(self): return self._makemap(self._key) + def getmember(self, context, mapping, key): + w = makewrapped(context, mapping, self._value) + return w.getmember(context, mapping, key) + def itermaps(self, context): yield self.tomap() @@ -231,6 +250,9 @@ self._tmpl = tmpl self._defaultsep = sep + def getmember(self, context, mapping, key): + raise error.ParseError(_('not a dictionary')) + def join(self, context, mapping, sep): mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) if self._name: @@ -294,6 +316,9 @@ def _gen(self, context): return self._make(context, *self._args) + def getmember(self, context, mapping, key): + raise error.ParseError(_('not a dictionary')) + def itermaps(self, context): raise error.ParseError(_('list of strings is not mappable')) @@ -678,15 +703,13 @@ lm = context.overlaymap(mapping, d.tomap()) return runsymbol(context, lm, memb) try: - if util.safehasattr(d, 'getmember'): - return d.getmember(context, mapping, memb) - raise error.ParseError - except error.ParseError: + return d.getmember(context, mapping, memb) + except error.ParseError as err: sym = findsymbolicname(darg) - if sym: - raise error.ParseError(_("keyword '%s' has no member") % sym) - else: - raise error.ParseError(_("%r has no member") % pycompat.bytestr(d)) + if not sym: + raise + hint = _("keyword '%s' does not support member operation") % sym + raise error.ParseError(bytes(err), hint=hint) def runnegate(context, mapping, data): data = evalinteger(context, mapping, data, diff -r 12b6ee9e88f3 -r 06d11cd90516 tests/test-command-template.t --- a/tests/test-command-template.t Wed Mar 21 01:39:44 2018 +0900 +++ b/tests/test-command-template.t Wed Mar 21 11:30:21 2018 +0900 @@ -3345,10 +3345,11 @@ default $ hg log -R latesttag -l1 -T '{author.invalid}\n' - hg: parse error: keyword 'author' has no member + hg: parse error: 'test' is not a dictionary + (keyword 'author' does not support member operation) [255] $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n' - hg: parse error: 'a' has no member + hg: parse error: 'a' is not a dictionary [255] Test the sub function of templating for expansion: @@ -3851,7 +3852,8 @@ $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n' default $ hg log -r 0 --template '{get(files, "should_fail")}\n' - hg: parse error: get() expects a dict as first argument + hg: parse error: not a dictionary + (get() expects a dict as first argument) [255] Test json filter applied to hybrid object: