templater: abstract ifcontains() over wrapped types
authorYuya Nishihara <yuya@tcha.org>
Mon, 19 Mar 2018 00:23:20 +0900
changeset 38267 fb874fc1d9b4
parent 38266 80f423a14c90
child 38268 49ef1539b84e
templater: abstract ifcontains() over wrapped types This allows us to make .keytype private. There's a minor BC that a hybrid dict/list of keytype=None now strictly checks the type of the needle. For example, {ifcontains(rev, files)} no longer matches a file named "1" at the rev=1. I made this change for consistency with the get(dict, key) function. We can restore the old behavior by making keytype=bytes the default if desired.
mercurial/hgweb/webutil.py
mercurial/templatefuncs.py
mercurial/templateutil.py
tests/test-command-template.t
--- a/mercurial/hgweb/webutil.py	Wed Mar 21 12:06:18 2018 +0900
+++ b/mercurial/hgweb/webutil.py	Mon Mar 19 00:23:20 2018 +0900
@@ -713,6 +713,10 @@
     def __copy__(self):
         return sessionvars(copy.copy(self._vars), self._start)
 
+    def contains(self, context, mapping, item):
+        item = templateutil.unwrapvalue(context, mapping, item)
+        return item in self._vars
+
     def getmember(self, context, mapping, key):
         key = templateutil.unwrapvalue(context, mapping, key)
         return self._vars.get(key)
--- a/mercurial/templatefuncs.py	Wed Mar 21 12:06:18 2018 +0900
+++ b/mercurial/templatefuncs.py	Mon Mar 19 00:23:20 2018 +0900
@@ -291,13 +291,10 @@
         # i18n: "ifcontains" is a keyword
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
-    haystack = evalfuncarg(context, mapping, args[1])
-    keytype = getattr(haystack, 'keytype', None)
+    haystack = evalwrapped(context, mapping, args[1])
     try:
         needle = evalrawexp(context, mapping, args[0])
-        needle = templateutil.unwrapastype(context, mapping, needle,
-                                           keytype or bytes)
-        found = (needle in haystack)
+        found = haystack.contains(context, mapping, needle)
     except error.ParseError:
         found = False
 
--- a/mercurial/templateutil.py	Wed Mar 21 12:06:18 2018 +0900
+++ b/mercurial/templateutil.py	Mon Mar 19 00:23:20 2018 +0900
@@ -38,6 +38,13 @@
     __metaclass__ = abc.ABCMeta
 
     @abc.abstractmethod
+    def contains(self, context, mapping, item):
+        """Test if the specified item is in self
+
+        The item argument may be a wrapped object.
+        """
+
+    @abc.abstractmethod
     def getmember(self, context, mapping, key):
         """Return a member item for the specified key
 
@@ -91,6 +98,10 @@
     def __init__(self, value):
         self._value = value
 
+    def contains(self, context, mapping, item):
+        item = stringify(context, mapping, item)
+        return item in self._value
+
     def getmember(self, context, mapping, key):
         raise error.ParseError(_('%r is not a dictionary')
                                % pycompat.bytestr(self._value))
@@ -125,6 +136,9 @@
     def __init__(self, value):
         self._value = value
 
+    def contains(self, context, mapping, item):
+        raise error.ParseError(_("%r is not iterable") % self._value)
+
     def getmember(self, context, mapping, key):
         raise error.ParseError(_('%r is not a dictionary') % self._value)
 
@@ -171,6 +185,10 @@
         self._joinfmt = joinfmt
         self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
 
+    def contains(self, context, mapping, item):
+        item = unwrapastype(context, mapping, item, self.keytype)
+        return item in self._values
+
     def getmember(self, context, mapping, key):
         # TODO: maybe split hybrid list/dict types?
         if not util.safehasattr(self._values, 'get'):
@@ -255,6 +273,10 @@
     def tomap(self):
         return self._makemap(self._key)
 
+    def contains(self, context, mapping, item):
+        w = makewrapped(context, mapping, self._value)
+        return w.contains(context, mapping, item)
+
     def getmember(self, context, mapping, key):
         w = makewrapped(context, mapping, self._value)
         return w.getmember(context, mapping, key)
@@ -302,6 +324,9 @@
         self._tmpl = tmpl
         self._defaultsep = sep
 
+    def contains(self, context, mapping, item):
+        raise error.ParseError(_('not comparable'))
+
     def getmember(self, context, mapping, key):
         raise error.ParseError(_('not a dictionary'))
 
@@ -371,6 +396,10 @@
         self._make = make
         self._args = args
 
+    def contains(self, context, mapping, item):
+        item = stringify(context, mapping, item)
+        return item in self.tovalue(context, mapping)
+
     def _gen(self, context):
         return self._make(context, *self._args)
 
--- a/tests/test-command-template.t	Wed Mar 21 12:06:18 2018 +0900
+++ b/tests/test-command-template.t	Mon Mar 19 00:23:20 2018 +0900
@@ -4166,6 +4166,15 @@
   1
   0
 
+  $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
+  t
+  $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
+  t
+  $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
+  f
+  $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
+  t
+
 Test revset function
 
   $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'