Show More
@@ -717,6 +717,12 b' class sessionvars(templateutil.wrapped):' | |||||
717 | key = templateutil.unwrapvalue(context, mapping, key) |
|
717 | key = templateutil.unwrapvalue(context, mapping, key) | |
718 | return self._vars.get(key) |
|
718 | return self._vars.get(key) | |
719 |
|
719 | |||
|
720 | def getmin(self, context, mapping): | |||
|
721 | raise error.ParseError(_('not comparable')) | |||
|
722 | ||||
|
723 | def getmax(self, context, mapping): | |||
|
724 | raise error.ParseError(_('not comparable')) | |||
|
725 | ||||
720 | def itermaps(self, context): |
|
726 | def itermaps(self, context): | |
721 | separator = self._start |
|
727 | separator = self._start | |
722 | for key, value in sorted(self._vars.iteritems()): |
|
728 | for key, value in sorted(self._vars.iteritems()): |
@@ -20,7 +20,6 b' from . import (' | |||||
20 | error, |
|
20 | error, | |
21 | minirst, |
|
21 | minirst, | |
22 | obsutil, |
|
22 | obsutil, | |
23 | pycompat, |
|
|||
24 | registrar, |
|
23 | registrar, | |
25 | revset as revsetmod, |
|
24 | revset as revsetmod, | |
26 | revsetlang, |
|
25 | revsetlang, | |
@@ -404,13 +403,13 b' def max_(context, mapping, args, **kwarg' | |||||
404 | # i18n: "max" is a keyword |
|
403 | # i18n: "max" is a keyword | |
405 | raise error.ParseError(_("max expects one argument")) |
|
404 | raise error.ParseError(_("max expects one argument")) | |
406 |
|
405 | |||
407 |
iterable = eval |
|
406 | iterable = evalwrapped(context, mapping, args[0]) | |
408 | try: |
|
407 | try: | |
409 | x = max(pycompat.maybebytestr(iterable)) |
|
408 | return iterable.getmax(context, mapping) | |
410 |
except |
|
409 | except error.ParseError as err: | |
411 | # i18n: "max" is a keyword |
|
410 | # i18n: "max" is a keyword | |
412 |
|
|
411 | hint = _("max first argument should be an iterable") | |
413 | return templateutil.wraphybridvalue(iterable, x, x) |
|
412 | raise error.ParseError(bytes(err), hint=hint) | |
414 |
|
413 | |||
415 | @templatefunc('min(iterable)') |
|
414 | @templatefunc('min(iterable)') | |
416 | def min_(context, mapping, args, **kwargs): |
|
415 | def min_(context, mapping, args, **kwargs): | |
@@ -419,13 +418,13 b' def min_(context, mapping, args, **kwarg' | |||||
419 | # i18n: "min" is a keyword |
|
418 | # i18n: "min" is a keyword | |
420 | raise error.ParseError(_("min expects one argument")) |
|
419 | raise error.ParseError(_("min expects one argument")) | |
421 |
|
420 | |||
422 |
iterable = eval |
|
421 | iterable = evalwrapped(context, mapping, args[0]) | |
423 | try: |
|
422 | try: | |
424 | x = min(pycompat.maybebytestr(iterable)) |
|
423 | return iterable.getmin(context, mapping) | |
425 |
except |
|
424 | except error.ParseError as err: | |
426 | # i18n: "min" is a keyword |
|
425 | # i18n: "min" is a keyword | |
427 |
|
|
426 | hint = _("min first argument should be an iterable") | |
428 | return templateutil.wraphybridvalue(iterable, x, x) |
|
427 | raise error.ParseError(bytes(err), hint=hint) | |
429 |
|
428 | |||
430 | @templatefunc('mod(a, b)') |
|
429 | @templatefunc('mod(a, b)') | |
431 | def mod(context, mapping, args): |
|
430 | def mod(context, mapping, args): |
@@ -47,6 +47,16 b' class wrapped(object):' | |||||
47 | """ |
|
47 | """ | |
48 |
|
48 | |||
49 | @abc.abstractmethod |
|
49 | @abc.abstractmethod | |
|
50 | def getmin(self, context, mapping): | |||
|
51 | """Return the smallest item, which may be either a wrapped or a pure | |||
|
52 | value depending on the self type""" | |||
|
53 | ||||
|
54 | @abc.abstractmethod | |||
|
55 | def getmax(self, context, mapping): | |||
|
56 | """Return the largest item, which may be either a wrapped or a pure | |||
|
57 | value depending on the self type""" | |||
|
58 | ||||
|
59 | @abc.abstractmethod | |||
50 | def itermaps(self, context): |
|
60 | def itermaps(self, context): | |
51 | """Yield each template mapping""" |
|
61 | """Yield each template mapping""" | |
52 |
|
62 | |||
@@ -85,6 +95,17 b' class wrappedbytes(wrapped):' | |||||
85 | raise error.ParseError(_('%r is not a dictionary') |
|
95 | raise error.ParseError(_('%r is not a dictionary') | |
86 | % pycompat.bytestr(self._value)) |
|
96 | % pycompat.bytestr(self._value)) | |
87 |
|
97 | |||
|
98 | def getmin(self, context, mapping): | |||
|
99 | return self._getby(context, mapping, min) | |||
|
100 | ||||
|
101 | def getmax(self, context, mapping): | |||
|
102 | return self._getby(context, mapping, max) | |||
|
103 | ||||
|
104 | def _getby(self, context, mapping, func): | |||
|
105 | if not self._value: | |||
|
106 | raise error.ParseError(_('empty string')) | |||
|
107 | return func(pycompat.iterbytestr(self._value)) | |||
|
108 | ||||
88 | def itermaps(self, context): |
|
109 | def itermaps(self, context): | |
89 | raise error.ParseError(_('%r is not iterable of mappings') |
|
110 | raise error.ParseError(_('%r is not iterable of mappings') | |
90 | % pycompat.bytestr(self._value)) |
|
111 | % pycompat.bytestr(self._value)) | |
@@ -107,6 +128,12 b' class wrappedvalue(wrapped):' | |||||
107 | def getmember(self, context, mapping, key): |
|
128 | def getmember(self, context, mapping, key): | |
108 | raise error.ParseError(_('%r is not a dictionary') % self._value) |
|
129 | raise error.ParseError(_('%r is not a dictionary') % self._value) | |
109 |
|
130 | |||
|
131 | def getmin(self, context, mapping): | |||
|
132 | raise error.ParseError(_("%r is not iterable") % self._value) | |||
|
133 | ||||
|
134 | def getmax(self, context, mapping): | |||
|
135 | raise error.ParseError(_("%r is not iterable") % self._value) | |||
|
136 | ||||
110 | def itermaps(self, context): |
|
137 | def itermaps(self, context): | |
111 | raise error.ParseError(_('%r is not iterable of mappings') |
|
138 | raise error.ParseError(_('%r is not iterable of mappings') | |
112 | % self._value) |
|
139 | % self._value) | |
@@ -151,6 +178,18 b' class hybrid(wrapped):' | |||||
151 | key = unwrapastype(context, mapping, key, self.keytype) |
|
178 | key = unwrapastype(context, mapping, key, self.keytype) | |
152 | return self._wrapvalue(key, self._values.get(key)) |
|
179 | return self._wrapvalue(key, self._values.get(key)) | |
153 |
|
180 | |||
|
181 | def getmin(self, context, mapping): | |||
|
182 | return self._getby(context, mapping, min) | |||
|
183 | ||||
|
184 | def getmax(self, context, mapping): | |||
|
185 | return self._getby(context, mapping, max) | |||
|
186 | ||||
|
187 | def _getby(self, context, mapping, func): | |||
|
188 | if not self._values: | |||
|
189 | raise error.ParseError(_('empty sequence')) | |||
|
190 | val = func(self._values) | |||
|
191 | return self._wrapvalue(val, val) | |||
|
192 | ||||
154 | def _wrapvalue(self, key, val): |
|
193 | def _wrapvalue(self, key, val): | |
155 | if val is None: |
|
194 | if val is None: | |
156 | return |
|
195 | return | |
@@ -217,6 +256,14 b' class mappable(wrapped):' | |||||
217 | w = makewrapped(context, mapping, self._value) |
|
256 | w = makewrapped(context, mapping, self._value) | |
218 | return w.getmember(context, mapping, key) |
|
257 | return w.getmember(context, mapping, key) | |
219 |
|
258 | |||
|
259 | def getmin(self, context, mapping): | |||
|
260 | w = makewrapped(context, mapping, self._value) | |||
|
261 | return w.getmin(context, mapping) | |||
|
262 | ||||
|
263 | def getmax(self, context, mapping): | |||
|
264 | w = makewrapped(context, mapping, self._value) | |||
|
265 | return w.getmax(context, mapping) | |||
|
266 | ||||
220 | def itermaps(self, context): |
|
267 | def itermaps(self, context): | |
221 | yield self.tomap() |
|
268 | yield self.tomap() | |
222 |
|
269 | |||
@@ -255,6 +302,12 b' class _mappingsequence(wrapped):' | |||||
255 | def getmember(self, context, mapping, key): |
|
302 | def getmember(self, context, mapping, key): | |
256 | raise error.ParseError(_('not a dictionary')) |
|
303 | raise error.ParseError(_('not a dictionary')) | |
257 |
|
304 | |||
|
305 | def getmin(self, context, mapping): | |||
|
306 | raise error.ParseError(_('not comparable')) | |||
|
307 | ||||
|
308 | def getmax(self, context, mapping): | |||
|
309 | raise error.ParseError(_('not comparable')) | |||
|
310 | ||||
258 | def join(self, context, mapping, sep): |
|
311 | def join(self, context, mapping, sep): | |
259 | mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) |
|
312 | mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) | |
260 | if self._name: |
|
313 | if self._name: | |
@@ -321,6 +374,18 b' class mappedgenerator(wrapped):' | |||||
321 | def getmember(self, context, mapping, key): |
|
374 | def getmember(self, context, mapping, key): | |
322 | raise error.ParseError(_('not a dictionary')) |
|
375 | raise error.ParseError(_('not a dictionary')) | |
323 |
|
376 | |||
|
377 | def getmin(self, context, mapping): | |||
|
378 | return self._getby(context, mapping, min) | |||
|
379 | ||||
|
380 | def getmax(self, context, mapping): | |||
|
381 | return self._getby(context, mapping, max) | |||
|
382 | ||||
|
383 | def _getby(self, context, mapping, func): | |||
|
384 | xs = self.tovalue(context, mapping) | |||
|
385 | if not xs: | |||
|
386 | raise error.ParseError(_('empty sequence')) | |||
|
387 | return func(xs) | |||
|
388 | ||||
324 | def itermaps(self, context): |
|
389 | def itermaps(self, context): | |
325 | raise error.ParseError(_('list of strings is not mappable')) |
|
390 | raise error.ParseError(_('list of strings is not mappable')) | |
326 |
|
391 |
@@ -3274,6 +3274,51 b' Test min/max over map operation:' | |||||
3274 | $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n' |
|
3274 | $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n' | |
3275 | t3 |
|
3275 | t3 | |
3276 |
|
3276 | |||
|
3277 | Test min/max of strings: | |||
|
3278 | ||||
|
3279 | $ hg log -R latesttag -l1 -T '{min(desc)}\n' | |||
|
3280 | 3 | |||
|
3281 | $ hg log -R latesttag -l1 -T '{max(desc)}\n' | |||
|
3282 | t | |||
|
3283 | ||||
|
3284 | Test min/max of non-iterable: | |||
|
3285 | ||||
|
3286 | $ hg debugtemplate '{min(1)}' | |||
|
3287 | hg: parse error: 1 is not iterable | |||
|
3288 | (min first argument should be an iterable) | |||
|
3289 | [255] | |||
|
3290 | $ hg debugtemplate '{max(2)}' | |||
|
3291 | hg: parse error: 2 is not iterable | |||
|
3292 | (max first argument should be an iterable) | |||
|
3293 | [255] | |||
|
3294 | ||||
|
3295 | Test min/max of empty sequence: | |||
|
3296 | ||||
|
3297 | $ hg debugtemplate '{min("")}' | |||
|
3298 | hg: parse error: empty string | |||
|
3299 | (min first argument should be an iterable) | |||
|
3300 | [255] | |||
|
3301 | $ hg debugtemplate '{max("")}' | |||
|
3302 | hg: parse error: empty string | |||
|
3303 | (max first argument should be an iterable) | |||
|
3304 | [255] | |||
|
3305 | $ hg debugtemplate '{min(dict())}' | |||
|
3306 | hg: parse error: empty sequence | |||
|
3307 | (min first argument should be an iterable) | |||
|
3308 | [255] | |||
|
3309 | $ hg debugtemplate '{max(dict())}' | |||
|
3310 | hg: parse error: empty sequence | |||
|
3311 | (max first argument should be an iterable) | |||
|
3312 | [255] | |||
|
3313 | $ hg debugtemplate '{min(dict() % "")}' | |||
|
3314 | hg: parse error: empty sequence | |||
|
3315 | (min first argument should be an iterable) | |||
|
3316 | [255] | |||
|
3317 | $ hg debugtemplate '{max(dict() % "")}' | |||
|
3318 | hg: parse error: empty sequence | |||
|
3319 | (max first argument should be an iterable) | |||
|
3320 | [255] | |||
|
3321 | ||||
3277 | Test min/max of if() result |
|
3322 | Test min/max of if() result | |
3278 |
|
3323 | |||
3279 | $ cd latesttag |
|
3324 | $ cd latesttag |
General Comments 0
You need to be logged in to leave comments.
Login now