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.
--- 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