[PATCH 2 of 4] templater: introduce filter() function to remove empty items from list
Yuya Nishihara
yuya at tcha.org
Sat Jun 23 05:51:39 EDT 2018
# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1528983206 -32400
# Thu Jun 14 22:33:26 2018 +0900
# Node ID f14965009644ab63cbe1990081493e8d490a30eb
# Parent 720fc7b592ef6dd5dc11a7ec48a715ee39c1006a
templater: introduce filter() function to remove empty items from list
The primary use case is to filter out "tip" from a list of tags.
diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -727,6 +727,10 @@ class sessionvars(templateutil.wrapped):
def getmax(self, context, mapping):
raise error.ParseError(_('not comparable'))
+ def filter(self, context, mapping, select):
+ # implement if necessary
+ raise error.ParseError(_('not filterable'))
+
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
@@ -166,6 +166,17 @@ def fill(context, mapping, args):
return templatefilters.fill(text, width, initindent, hangindent)
+ at templatefunc('filter(iterable)')
+def filter_(context, mapping, args):
+ """Remove empty elements from a list or a dict."""
+ if len(args) != 1:
+ # i18n: "filter" is a keyword
+ raise error.ParseError(_("filter expects one argument"))
+ iterable = evalwrapped(context, mapping, args[0])
+ def select(w):
+ return w.tobool(context, mapping)
+ return iterable.filter(context, mapping, select)
+
@templatefunc('formatnode(node)', requires={'ui'})
def formatnode(context, mapping, args):
"""Obtain the preferred form of a changeset hash. (DEPRECATED)"""
diff --git a/mercurial/templateutil.py b/mercurial/templateutil.py
--- a/mercurial/templateutil.py
+++ b/mercurial/templateutil.py
@@ -63,6 +63,14 @@ class wrapped(object):
value depending on the self type"""
@abc.abstractmethod
+ def filter(self, context, mapping, select):
+ """Return new container of the same type which includes only the
+ selected elements
+
+ select() takes each item as a wrapped object and returns True/False.
+ """
+
+ @abc.abstractmethod
def itermaps(self, context):
"""Yield each template mapping"""
@@ -130,6 +138,10 @@ class wrappedbytes(wrapped):
raise error.ParseError(_('empty string'))
return func(pycompat.iterbytestr(self._value))
+ def filter(self, context, mapping, select):
+ raise error.ParseError(_('%r is not filterable')
+ % pycompat.bytestr(self._value))
+
def itermaps(self, context):
raise error.ParseError(_('%r is not iterable of mappings')
% pycompat.bytestr(self._value))
@@ -164,6 +176,9 @@ class wrappedvalue(wrapped):
def getmax(self, context, mapping):
raise error.ParseError(_("%r is not iterable") % self._value)
+ def filter(self, context, mapping, select):
+ raise error.ParseError(_("%r is not iterable") % self._value)
+
def itermaps(self, context):
raise error.ParseError(_('%r is not iterable of mappings')
% self._value)
@@ -208,6 +223,9 @@ class date(mappable, wrapped):
def getmax(self, context, mapping):
raise error.ParseError(_('date is not iterable'))
+ def filter(self, context, mapping, select):
+ raise error.ParseError(_('date is not iterable'))
+
def join(self, context, mapping, sep):
raise error.ParseError(_("date is not iterable"))
@@ -273,6 +291,14 @@ class hybrid(wrapped):
return val
return hybriditem(None, key, val, self._makemap)
+ def filter(self, context, mapping, select):
+ if util.safehasattr(self._values, 'get'):
+ values = {k: v for k, v in self._values.iteritems()
+ if select(self._wrapvalue(k, v))}
+ else:
+ values = [v for v in self._values if select(self._wrapvalue(v, v))]
+ return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
+
def itermaps(self, context):
makemap = self._makemap
for x in self._values:
@@ -336,6 +362,10 @@ class hybriditem(mappable, wrapped):
w = makewrapped(context, mapping, self._value)
return w.getmax(context, mapping)
+ def filter(self, context, mapping, select):
+ w = makewrapped(context, mapping, self._value)
+ return w.filter(context, mapping, select)
+
def join(self, context, mapping, sep):
w = makewrapped(context, mapping, self._value)
return w.join(context, mapping, sep)
@@ -384,6 +414,9 @@ class _mappingsequence(wrapped):
def getmax(self, context, mapping):
raise error.ParseError(_('not comparable'))
+ def filter(self, context, mapping, select):
+ raise error.ParseError(_('not filterable without template'))
+
def join(self, context, mapping, sep):
mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
if self._name:
@@ -472,6 +505,17 @@ class mappedgenerator(wrapped):
raise error.ParseError(_('empty sequence'))
return func(xs)
+ @staticmethod
+ def _filteredgen(context, mapping, make, args, select):
+ for x in make(context, *args):
+ s = stringify(context, mapping, x)
+ if select(wrappedbytes(s)):
+ yield s
+
+ def filter(self, context, mapping, select):
+ args = (mapping, self._make, self._args, select)
+ return mappedgenerator(self._filteredgen, args)
+
def itermaps(self, context):
raise error.ParseError(_('list of strings is not mappable'))
diff --git a/tests/test-template-functions.t b/tests/test-template-functions.t
--- a/tests/test-template-functions.t
+++ b/tests/test-template-functions.t
@@ -435,6 +435,48 @@ latesttag() function:
$ cd ..
+Test filter() empty values:
+
+ $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
+ other 1
+ other 2
+ other 3
+ $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
+ 0
+
+ 0 should not be falsy
+
+ $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
+ 0 1 2
+
+Test filter() shouldn't crash:
+
+ $ hg log -R a -r 0 -T '{filter(extras)}\n'
+ branch=default
+ $ hg log -R a -r 0 -T '{filter(files)}\n'
+ a
+
+Test filter() unsupported arguments:
+
+ $ hg log -R a -r 0 -T '{filter()}\n'
+ hg: parse error: filter expects one argument
+ [255]
+ $ hg log -R a -r 0 -T '{filter(date)}\n'
+ hg: parse error: date is not iterable
+ [255]
+ $ hg log -R a -r 0 -T '{filter(rev)}\n'
+ hg: parse error: 0 is not iterable
+ [255]
+ $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
+ hg: parse error: 'line 1' is not filterable
+ [255]
+ $ hg log -R a -r 0 -T '{filter(manifest)}\n'
+ hg: parse error: '0:a0c8bcbbb45c' is not filterable
+ [255]
+ $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
+ hg: parse error: not filterable without template
+ [255]
+
Test manifest/get() can be join()-ed as string, though it's silly:
$ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
More information about the Mercurial-devel
mailing list