Patchwork [3,of,4] templater: extend filter() to accept template expression for emptiness test

login
register
mail settings
Submitter Yuya Nishihara
Date June 23, 2018, 9:51 a.m.
Message ID <f2d42f1933ce7e28647f.1529747500@mimosa>
Download mbox | patch
Permalink /patch/32390/
State New
Headers show

Comments

Yuya Nishihara - June 23, 2018, 9:51 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1528985414 -32400
#      Thu Jun 14 23:10:14 2018 +0900
# Node ID f2d42f1933ce7e28647f2b834c86de3369fbbe00
# Parent  f14965009644ab63cbe1990081493e8d490a30eb
templater: extend filter() to accept template expression for emptiness test

This utilizes the pass-by-name nature of template arguments.

Patch

diff --git a/mercurial/templatefuncs.py b/mercurial/templatefuncs.py
--- a/mercurial/templatefuncs.py
+++ b/mercurial/templatefuncs.py
@@ -166,15 +166,23 @@  def fill(context, mapping, args):
 
     return templatefilters.fill(text, width, initindent, hangindent)
 
-@templatefunc('filter(iterable)')
+@templatefunc('filter(iterable[, expr])')
 def filter_(context, mapping, args):
-    """Remove empty elements from a list or a dict."""
-    if len(args) != 1:
+    """Remove empty elements from a list or a dict. If expr specified, it's
+    applied to each element to test emptiness."""
+    if not (1 <= len(args) <= 2):
         # i18n: "filter" is a keyword
-        raise error.ParseError(_("filter expects one argument"))
+        raise error.ParseError(_("filter expects one or two arguments"))
     iterable = evalwrapped(context, mapping, args[0])
-    def select(w):
-        return w.tobool(context, mapping)
+    if len(args) == 1:
+        def select(w):
+            return w.tobool(context, mapping)
+    else:
+        def select(w):
+            if not isinstance(w, templateutil.mappable):
+                raise error.ParseError(_("not filterable by expression"))
+            lm = context.overlaymap(mapping, w.tomap(context))
+            return evalboolean(context, lm, args[1])
     return iterable.filter(context, mapping, select)
 
 @templatefunc('formatnode(node)', requires={'ui'})
diff --git a/mercurial/templateutil.py b/mercurial/templateutil.py
--- a/mercurial/templateutil.py
+++ b/mercurial/templateutil.py
@@ -415,6 +415,7 @@  class _mappingsequence(wrapped):
         raise error.ParseError(_('not comparable'))
 
     def filter(self, context, mapping, select):
+        # implement if necessary; we'll need a wrapped type for a mapping dict
         raise error.ParseError(_('not filterable without template'))
 
     def join(self, context, mapping, sep):
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
@@ -449,6 +449,13 @@  Test filter() empty values:
   $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
   0 1 2
 
+Test filter() by expression:
+
+  $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
+  other 1
+  $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
+  b=1
+
 Test filter() shouldn't crash:
 
   $ hg log -R a -r 0 -T '{filter(extras)}\n'
@@ -459,7 +466,7 @@  Test filter() shouldn't crash:
 Test filter() unsupported arguments:
 
   $ hg log -R a -r 0 -T '{filter()}\n'
-  hg: parse error: filter expects one argument
+  hg: parse error: filter expects one or two arguments
   [255]
   $ hg log -R a -r 0 -T '{filter(date)}\n'
   hg: parse error: date is not iterable
@@ -476,6 +483,9 @@  Test filter() unsupported arguments:
   $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
   hg: parse error: not filterable without template
   [255]
+  $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
+  hg: parse error: not filterable by expression
+  [255]
 
 Test manifest/get() can be join()-ed as string, though it's silly: