Show More
@@ -727,6 +727,10 b' class sessionvars(templateutil.wrapped):' | |||||
727 | def getmax(self, context, mapping): |
|
727 | def getmax(self, context, mapping): | |
728 | raise error.ParseError(_('not comparable')) |
|
728 | raise error.ParseError(_('not comparable')) | |
729 |
|
729 | |||
|
730 | def filter(self, context, mapping, select): | |||
|
731 | # implement if necessary | |||
|
732 | raise error.ParseError(_('not filterable')) | |||
|
733 | ||||
730 | def itermaps(self, context): |
|
734 | def itermaps(self, context): | |
731 | separator = self._start |
|
735 | separator = self._start | |
732 | for key, value in sorted(self._vars.iteritems()): |
|
736 | for key, value in sorted(self._vars.iteritems()): |
@@ -166,6 +166,17 b' def fill(context, mapping, args):' | |||||
166 |
|
166 | |||
167 | return templatefilters.fill(text, width, initindent, hangindent) |
|
167 | return templatefilters.fill(text, width, initindent, hangindent) | |
168 |
|
168 | |||
|
169 | @templatefunc('filter(iterable)') | |||
|
170 | def filter_(context, mapping, args): | |||
|
171 | """Remove empty elements from a list or a dict.""" | |||
|
172 | if len(args) != 1: | |||
|
173 | # i18n: "filter" is a keyword | |||
|
174 | raise error.ParseError(_("filter expects one argument")) | |||
|
175 | iterable = evalwrapped(context, mapping, args[0]) | |||
|
176 | def select(w): | |||
|
177 | return w.tobool(context, mapping) | |||
|
178 | return iterable.filter(context, mapping, select) | |||
|
179 | ||||
169 | @templatefunc('formatnode(node)', requires={'ui'}) |
|
180 | @templatefunc('formatnode(node)', requires={'ui'}) | |
170 | def formatnode(context, mapping, args): |
|
181 | def formatnode(context, mapping, args): | |
171 | """Obtain the preferred form of a changeset hash. (DEPRECATED)""" |
|
182 | """Obtain the preferred form of a changeset hash. (DEPRECATED)""" |
@@ -63,6 +63,14 b' class wrapped(object):' | |||||
63 | value depending on the self type""" |
|
63 | value depending on the self type""" | |
64 |
|
64 | |||
65 | @abc.abstractmethod |
|
65 | @abc.abstractmethod | |
|
66 | def filter(self, context, mapping, select): | |||
|
67 | """Return new container of the same type which includes only the | |||
|
68 | selected elements | |||
|
69 | ||||
|
70 | select() takes each item as a wrapped object and returns True/False. | |||
|
71 | """ | |||
|
72 | ||||
|
73 | @abc.abstractmethod | |||
66 | def itermaps(self, context): |
|
74 | def itermaps(self, context): | |
67 | """Yield each template mapping""" |
|
75 | """Yield each template mapping""" | |
68 |
|
76 | |||
@@ -130,6 +138,10 b' class wrappedbytes(wrapped):' | |||||
130 | raise error.ParseError(_('empty string')) |
|
138 | raise error.ParseError(_('empty string')) | |
131 | return func(pycompat.iterbytestr(self._value)) |
|
139 | return func(pycompat.iterbytestr(self._value)) | |
132 |
|
140 | |||
|
141 | def filter(self, context, mapping, select): | |||
|
142 | raise error.ParseError(_('%r is not filterable') | |||
|
143 | % pycompat.bytestr(self._value)) | |||
|
144 | ||||
133 | def itermaps(self, context): |
|
145 | def itermaps(self, context): | |
134 | raise error.ParseError(_('%r is not iterable of mappings') |
|
146 | raise error.ParseError(_('%r is not iterable of mappings') | |
135 | % pycompat.bytestr(self._value)) |
|
147 | % pycompat.bytestr(self._value)) | |
@@ -164,6 +176,9 b' class wrappedvalue(wrapped):' | |||||
164 | def getmax(self, context, mapping): |
|
176 | def getmax(self, context, mapping): | |
165 | raise error.ParseError(_("%r is not iterable") % self._value) |
|
177 | raise error.ParseError(_("%r is not iterable") % self._value) | |
166 |
|
178 | |||
|
179 | def filter(self, context, mapping, select): | |||
|
180 | raise error.ParseError(_("%r is not iterable") % self._value) | |||
|
181 | ||||
167 | def itermaps(self, context): |
|
182 | def itermaps(self, context): | |
168 | raise error.ParseError(_('%r is not iterable of mappings') |
|
183 | raise error.ParseError(_('%r is not iterable of mappings') | |
169 | % self._value) |
|
184 | % self._value) | |
@@ -208,6 +223,9 b' class date(mappable, wrapped):' | |||||
208 | def getmax(self, context, mapping): |
|
223 | def getmax(self, context, mapping): | |
209 | raise error.ParseError(_('date is not iterable')) |
|
224 | raise error.ParseError(_('date is not iterable')) | |
210 |
|
225 | |||
|
226 | def filter(self, context, mapping, select): | |||
|
227 | raise error.ParseError(_('date is not iterable')) | |||
|
228 | ||||
211 | def join(self, context, mapping, sep): |
|
229 | def join(self, context, mapping, sep): | |
212 | raise error.ParseError(_("date is not iterable")) |
|
230 | raise error.ParseError(_("date is not iterable")) | |
213 |
|
231 | |||
@@ -273,6 +291,14 b' class hybrid(wrapped):' | |||||
273 | return val |
|
291 | return val | |
274 | return hybriditem(None, key, val, self._makemap) |
|
292 | return hybriditem(None, key, val, self._makemap) | |
275 |
|
293 | |||
|
294 | def filter(self, context, mapping, select): | |||
|
295 | if util.safehasattr(self._values, 'get'): | |||
|
296 | values = {k: v for k, v in self._values.iteritems() | |||
|
297 | if select(self._wrapvalue(k, v))} | |||
|
298 | else: | |||
|
299 | values = [v for v in self._values if select(self._wrapvalue(v, v))] | |||
|
300 | return hybrid(None, values, self._makemap, self._joinfmt, self._keytype) | |||
|
301 | ||||
276 | def itermaps(self, context): |
|
302 | def itermaps(self, context): | |
277 | makemap = self._makemap |
|
303 | makemap = self._makemap | |
278 | for x in self._values: |
|
304 | for x in self._values: | |
@@ -336,6 +362,10 b' class hybriditem(mappable, wrapped):' | |||||
336 | w = makewrapped(context, mapping, self._value) |
|
362 | w = makewrapped(context, mapping, self._value) | |
337 | return w.getmax(context, mapping) |
|
363 | return w.getmax(context, mapping) | |
338 |
|
364 | |||
|
365 | def filter(self, context, mapping, select): | |||
|
366 | w = makewrapped(context, mapping, self._value) | |||
|
367 | return w.filter(context, mapping, select) | |||
|
368 | ||||
339 | def join(self, context, mapping, sep): |
|
369 | def join(self, context, mapping, sep): | |
340 | w = makewrapped(context, mapping, self._value) |
|
370 | w = makewrapped(context, mapping, self._value) | |
341 | return w.join(context, mapping, sep) |
|
371 | return w.join(context, mapping, sep) | |
@@ -384,6 +414,9 b' class _mappingsequence(wrapped):' | |||||
384 | def getmax(self, context, mapping): |
|
414 | def getmax(self, context, mapping): | |
385 | raise error.ParseError(_('not comparable')) |
|
415 | raise error.ParseError(_('not comparable')) | |
386 |
|
416 | |||
|
417 | def filter(self, context, mapping, select): | |||
|
418 | raise error.ParseError(_('not filterable without template')) | |||
|
419 | ||||
387 | def join(self, context, mapping, sep): |
|
420 | def join(self, context, mapping, sep): | |
388 | mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) |
|
421 | mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) | |
389 | if self._name: |
|
422 | if self._name: | |
@@ -472,6 +505,17 b' class mappedgenerator(wrapped):' | |||||
472 | raise error.ParseError(_('empty sequence')) |
|
505 | raise error.ParseError(_('empty sequence')) | |
473 | return func(xs) |
|
506 | return func(xs) | |
474 |
|
507 | |||
|
508 | @staticmethod | |||
|
509 | def _filteredgen(context, mapping, make, args, select): | |||
|
510 | for x in make(context, *args): | |||
|
511 | s = stringify(context, mapping, x) | |||
|
512 | if select(wrappedbytes(s)): | |||
|
513 | yield s | |||
|
514 | ||||
|
515 | def filter(self, context, mapping, select): | |||
|
516 | args = (mapping, self._make, self._args, select) | |||
|
517 | return mappedgenerator(self._filteredgen, args) | |||
|
518 | ||||
475 | def itermaps(self, context): |
|
519 | def itermaps(self, context): | |
476 | raise error.ParseError(_('list of strings is not mappable')) |
|
520 | raise error.ParseError(_('list of strings is not mappable')) | |
477 |
|
521 |
@@ -435,6 +435,48 b' latesttag() function:' | |||||
435 |
|
435 | |||
436 | $ cd .. |
|
436 | $ cd .. | |
437 |
|
437 | |||
|
438 | Test filter() empty values: | |||
|
439 | ||||
|
440 | $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}' | |||
|
441 | other 1 | |||
|
442 | other 2 | |||
|
443 | other 3 | |||
|
444 | $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}' | |||
|
445 | 0 | |||
|
446 | ||||
|
447 | 0 should not be falsy | |||
|
448 | ||||
|
449 | $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n' | |||
|
450 | 0 1 2 | |||
|
451 | ||||
|
452 | Test filter() shouldn't crash: | |||
|
453 | ||||
|
454 | $ hg log -R a -r 0 -T '{filter(extras)}\n' | |||
|
455 | branch=default | |||
|
456 | $ hg log -R a -r 0 -T '{filter(files)}\n' | |||
|
457 | a | |||
|
458 | ||||
|
459 | Test filter() unsupported arguments: | |||
|
460 | ||||
|
461 | $ hg log -R a -r 0 -T '{filter()}\n' | |||
|
462 | hg: parse error: filter expects one argument | |||
|
463 | [255] | |||
|
464 | $ hg log -R a -r 0 -T '{filter(date)}\n' | |||
|
465 | hg: parse error: date is not iterable | |||
|
466 | [255] | |||
|
467 | $ hg log -R a -r 0 -T '{filter(rev)}\n' | |||
|
468 | hg: parse error: 0 is not iterable | |||
|
469 | [255] | |||
|
470 | $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n' | |||
|
471 | hg: parse error: 'line 1' is not filterable | |||
|
472 | [255] | |||
|
473 | $ hg log -R a -r 0 -T '{filter(manifest)}\n' | |||
|
474 | hg: parse error: '0:a0c8bcbbb45c' is not filterable | |||
|
475 | [255] | |||
|
476 | $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n' | |||
|
477 | hg: parse error: not filterable without template | |||
|
478 | [255] | |||
|
479 | ||||
438 |
|
|
480 | Test manifest/get() can be join()-ed as string, though it's silly: | |
439 |
|
481 | |||
440 | $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n' |
|
482 | $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n' |
General Comments 0
You need to be logged in to leave comments.
Login now