Mercurial > hg
changeset 38265:41ae9b3cbfb9
templater: abstract min/max away
I'm not certain how many get*() functions I'll add to the wrapped types,
but getmin() and getmax() will allow us to optimize a revset wrapper.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Mon, 19 Mar 2018 00:16:12 +0900 |
parents | fbb2eddea4d2 |
children | 80f423a14c90 |
files | mercurial/hgweb/webutil.py mercurial/templatefuncs.py mercurial/templateutil.py tests/test-command-template.t |
diffstat | 4 files changed, 126 insertions(+), 11 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/hgweb/webutil.py Sun Jun 10 12:24:53 2018 +0900 +++ b/mercurial/hgweb/webutil.py Mon Mar 19 00:16:12 2018 +0900 @@ -717,6 +717,12 @@ key = templateutil.unwrapvalue(context, mapping, key) return self._vars.get(key) + def getmin(self, context, mapping): + raise error.ParseError(_('not comparable')) + + def getmax(self, context, mapping): + raise error.ParseError(_('not comparable')) + def itermaps(self, context): separator = self._start for key, value in sorted(self._vars.iteritems()):
--- a/mercurial/templatefuncs.py Sun Jun 10 12:24:53 2018 +0900 +++ b/mercurial/templatefuncs.py Mon Mar 19 00:16:12 2018 +0900 @@ -20,7 +20,6 @@ error, minirst, obsutil, - pycompat, registrar, revset as revsetmod, revsetlang, @@ -404,13 +403,13 @@ # i18n: "max" is a keyword raise error.ParseError(_("max expects one argument")) - iterable = evalfuncarg(context, mapping, args[0]) + iterable = evalwrapped(context, mapping, args[0]) try: - x = max(pycompat.maybebytestr(iterable)) - except (TypeError, ValueError): + return iterable.getmax(context, mapping) + except error.ParseError as err: # i18n: "max" is a keyword - raise error.ParseError(_("max first argument should be an iterable")) - return templateutil.wraphybridvalue(iterable, x, x) + hint = _("max first argument should be an iterable") + raise error.ParseError(bytes(err), hint=hint) @templatefunc('min(iterable)') def min_(context, mapping, args, **kwargs): @@ -419,13 +418,13 @@ # i18n: "min" is a keyword raise error.ParseError(_("min expects one argument")) - iterable = evalfuncarg(context, mapping, args[0]) + iterable = evalwrapped(context, mapping, args[0]) try: - x = min(pycompat.maybebytestr(iterable)) - except (TypeError, ValueError): + return iterable.getmin(context, mapping) + except error.ParseError as err: # i18n: "min" is a keyword - raise error.ParseError(_("min first argument should be an iterable")) - return templateutil.wraphybridvalue(iterable, x, x) + hint = _("min first argument should be an iterable") + raise error.ParseError(bytes(err), hint=hint) @templatefunc('mod(a, b)') def mod(context, mapping, args):
--- a/mercurial/templateutil.py Sun Jun 10 12:24:53 2018 +0900 +++ b/mercurial/templateutil.py Mon Mar 19 00:16:12 2018 +0900 @@ -47,6 +47,16 @@ """ @abc.abstractmethod + def getmin(self, context, mapping): + """Return the smallest item, which may be either a wrapped or a pure + value depending on the self type""" + + @abc.abstractmethod + def getmax(self, context, mapping): + """Return the largest item, which may be either a wrapped or a pure + value depending on the self type""" + + @abc.abstractmethod def itermaps(self, context): """Yield each template mapping""" @@ -85,6 +95,17 @@ raise error.ParseError(_('%r is not a dictionary') % pycompat.bytestr(self._value)) + def getmin(self, context, mapping): + return self._getby(context, mapping, min) + + def getmax(self, context, mapping): + return self._getby(context, mapping, max) + + def _getby(self, context, mapping, func): + if not self._value: + raise error.ParseError(_('empty string')) + return func(pycompat.iterbytestr(self._value)) + def itermaps(self, context): raise error.ParseError(_('%r is not iterable of mappings') % pycompat.bytestr(self._value)) @@ -107,6 +128,12 @@ def getmember(self, context, mapping, key): raise error.ParseError(_('%r is not a dictionary') % self._value) + def getmin(self, context, mapping): + raise error.ParseError(_("%r is not iterable") % self._value) + + def getmax(self, context, mapping): + raise error.ParseError(_("%r is not iterable") % self._value) + def itermaps(self, context): raise error.ParseError(_('%r is not iterable of mappings') % self._value) @@ -151,6 +178,18 @@ key = unwrapastype(context, mapping, key, self.keytype) return self._wrapvalue(key, self._values.get(key)) + def getmin(self, context, mapping): + return self._getby(context, mapping, min) + + def getmax(self, context, mapping): + return self._getby(context, mapping, max) + + def _getby(self, context, mapping, func): + if not self._values: + raise error.ParseError(_('empty sequence')) + val = func(self._values) + return self._wrapvalue(val, val) + def _wrapvalue(self, key, val): if val is None: return @@ -217,6 +256,14 @@ w = makewrapped(context, mapping, self._value) return w.getmember(context, mapping, key) + def getmin(self, context, mapping): + w = makewrapped(context, mapping, self._value) + return w.getmin(context, mapping) + + def getmax(self, context, mapping): + w = makewrapped(context, mapping, self._value) + return w.getmax(context, mapping) + def itermaps(self, context): yield self.tomap() @@ -255,6 +302,12 @@ def getmember(self, context, mapping, key): raise error.ParseError(_('not a dictionary')) + def getmin(self, context, mapping): + raise error.ParseError(_('not comparable')) + + def getmax(self, context, mapping): + raise error.ParseError(_('not comparable')) + def join(self, context, mapping, sep): mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) if self._name: @@ -321,6 +374,18 @@ def getmember(self, context, mapping, key): raise error.ParseError(_('not a dictionary')) + def getmin(self, context, mapping): + return self._getby(context, mapping, min) + + def getmax(self, context, mapping): + return self._getby(context, mapping, max) + + def _getby(self, context, mapping, func): + xs = self.tovalue(context, mapping) + if not xs: + raise error.ParseError(_('empty sequence')) + return func(xs) + def itermaps(self, context): raise error.ParseError(_('list of strings is not mappable'))
--- a/tests/test-command-template.t Sun Jun 10 12:24:53 2018 +0900 +++ b/tests/test-command-template.t Mon Mar 19 00:16:12 2018 +0900 @@ -3274,6 +3274,51 @@ $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n' t3 +Test min/max of strings: + + $ hg log -R latesttag -l1 -T '{min(desc)}\n' + 3 + $ hg log -R latesttag -l1 -T '{max(desc)}\n' + t + +Test min/max of non-iterable: + + $ hg debugtemplate '{min(1)}' + hg: parse error: 1 is not iterable + (min first argument should be an iterable) + [255] + $ hg debugtemplate '{max(2)}' + hg: parse error: 2 is not iterable + (max first argument should be an iterable) + [255] + +Test min/max of empty sequence: + + $ hg debugtemplate '{min("")}' + hg: parse error: empty string + (min first argument should be an iterable) + [255] + $ hg debugtemplate '{max("")}' + hg: parse error: empty string + (max first argument should be an iterable) + [255] + $ hg debugtemplate '{min(dict())}' + hg: parse error: empty sequence + (min first argument should be an iterable) + [255] + $ hg debugtemplate '{max(dict())}' + hg: parse error: empty sequence + (max first argument should be an iterable) + [255] + $ hg debugtemplate '{min(dict() % "")}' + hg: parse error: empty sequence + (min first argument should be an iterable) + [255] + $ hg debugtemplate '{max(dict() % "")}' + hg: parse error: empty sequence + (max first argument should be an iterable) + [255] + Test min/max of if() result $ cd latesttag