# HG changeset patch # User Yuya Nishihara # Date 2018-03-21 02:30:21 # Node ID 06d11cd905166ed5a8aa300abff4b5f5ef052c2b # Parent 12b6ee9e88f3016b294f2f2c141cbc5f8b4e6e67 templater: promote getmember() to an interface of wrapped types diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py +++ b/mercurial/hgweb/webutil.py @@ -713,6 +713,9 @@ class sessionvars(templateutil.wrapped): 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 --git a/mercurial/templatefuncs.py b/mercurial/templatefuncs.py --- a/mercurial/templatefuncs.py +++ b/mercurial/templatefuncs.py @@ -262,12 +262,13 @@ def get(context, mapping, args): 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 --git a/mercurial/templateutil.py b/mercurial/templateutil.py --- a/mercurial/templateutil.py +++ b/mercurial/templateutil.py @@ -38,6 +38,14 @@ class wrapped(object): __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 @@ class wrappedbytes(wrapped): 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 @@ class wrappedvalue(wrapped): 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 @@ class mappable(wrapped): 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 @@ class _mappingsequence(wrapped): 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 @@ class mappedgenerator(wrapped): 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 @@ def runmember(context, mapping, data): 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 --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 @@ -3345,10 +3345,11 @@ Test evaluation of dot operator: 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 @@ Test get function: $ 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: