Patchwork [3,of,6] templater: abstract ifcontains() over wrapped types

login
register
mail settings
Submitter Yuya Nishihara
Date June 8, 2018, 2:51 p.m.
Message ID <7837f4a52b85cc034c7b.1528469509@mimosa>
Download mbox | patch
Permalink /patch/32031/
State Accepted
Headers show

Comments

Yuya Nishihara - June 8, 2018, 2:51 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1521386600 -32400
#      Mon Mar 19 00:23:20 2018 +0900
# Node ID 7837f4a52b85cc034c7b3b9e79e53941552c27e6
# Parent  ba79741bbadac2e014eec83b6c7b5a6805febc5d
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.

Patch

diff --git a/mercurial/hgweb/webutil.py b/mercurial/hgweb/webutil.py
--- a/mercurial/hgweb/webutil.py
+++ b/mercurial/hgweb/webutil.py
@@ -713,6 +713,10 @@  class sessionvars(templateutil.wrapped):
     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)
diff --git a/mercurial/templatefuncs.py b/mercurial/templatefuncs.py
--- a/mercurial/templatefuncs.py
+++ b/mercurial/templatefuncs.py
@@ -291,13 +291,10 @@  def ifcontains(context, mapping, args):
         # 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
 
diff --git a/mercurial/templateutil.py b/mercurial/templateutil.py
--- a/mercurial/templateutil.py
+++ b/mercurial/templateutil.py
@@ -38,6 +38,13 @@  class wrapped(object):
     __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 @@  class wrappedbytes(wrapped):
     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 @@  class wrappedvalue(wrapped):
     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 @@  class hybrid(wrapped):
         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 @@  class mappable(wrapped):
     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 @@  class _mappingsequence(wrapped):
         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 @@  class mappedgenerator(wrapped):
         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)
 
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -4166,6 +4166,15 @@  Test ifcontains function
   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'