Repository: tonioo/sievelib

Issue:
Factory: add support for body and envelope tests
Those tests are useful and should be handled by the factory module.

Discussion:

Patch:
diff --git a/sievelib/commands.py b/sievelib/commands.py
index 6b3c1d6..7f6df2e 100644
--- a/sievelib/commands.py
+++ b/sievelib/commands.py
@@ -182,7 +182,7 @@ class Command(object):
 
                 if "string" in arg["type"]:
                     target.write(value)
-                    if not value.startswith('"'):
+                    if not value.startswith('"') and not value.startswith("["):
                         target.write("\n")
                 else:
                     target.write(value)
@@ -739,6 +739,20 @@ class EnvelopeCommand(TestCommand):
          "required": True}
     ]
 
+    def args_as_tuple(self):
+        """Return arguments as a list."""
+        result = ("envelope", self.arguments["match-type"])
+        if self.arguments["header-list"].startswith("["):
+            result += (tools.to_list(self.arguments["header-list"]),)
+        else:
+            result += ([self.arguments["header-list"].strip('"')],)
+        if self.arguments["key-list"].startswith("["):
+            result = result + (
+                tools.to_list(self.arguments["key-list"]),)
+        else:
+            result = result + ([self.arguments["key-list"].strip('"')],)
+        return result
+
 
 class ExistsCommand(TestCommand):
     args_definition = [
@@ -749,9 +763,8 @@ class ExistsCommand(TestCommand):
 
     def args_as_tuple(self):
         value = self.arguments["header-names"]
-        if ',' not in value:
-            return ('exists', value)
-
+        if not value.startswith("["):
+            return ('exists', value.strip('"'))
         return ("exists", ) + tuple(tools.to_list(value))
 
 
@@ -783,7 +796,6 @@ class HeaderCommand(TestCommand):
             result = (self.arguments["header-names"].strip('"'),)
         result = result + (self.arguments["match-type"],)
         if "," in self.arguments["key-list"]:
-            keylist = self.arguments["key-list"][1:-1]
             result = result + tuple(
                 tools.to_list(self.arguments["key-list"], unquote=False))
         else:
@@ -811,6 +823,18 @@ class BodyCommand(TestCommand):
     ]
     extension = "body"
 
+    def args_as_tuple(self):
+        """Return arguments as a list."""
+        result = ("body", )
+        result = result + (
+            self.arguments["body-transform"], self.arguments["match-type"])
+        if self.arguments["key-list"].startswith("["):
+            result = result + tuple(
+                tools.to_list(self.arguments["key-list"]))
+        else:
+            result = result + (self.arguments["key-list"].strip('"'),)
+        return result
+
 
 class NotCommand(TestCommand):
     accept_children = True
@@ -860,10 +884,11 @@ class HasflagCommand(TestCommand):
         """Deal with optional stringlist before a required one."""
         condition = (
             "variable-list" in self.arguments and
-            not "list-of-flags" in self.arguments
+            "list-of-flags" not in self.arguments
         )
         if condition:
-            self.arguments["list-of-flags"] = self.arguments.pop("variable-list")
+            self.arguments["list-of-flags"] = (
+                self.arguments.pop("variable-list"))
             self.rargs_cnt = 1
 
 
diff --git a/sievelib/factory.py b/sievelib/factory.py
index 4fa1743..caf2661 100644
--- a/sievelib/factory.py
+++ b/sievelib/factory.py
@@ -185,6 +185,26 @@ class FiltersSet(object):
                     "stringlist",
                     "[%s]" % (",".join('"%s"' % val for val in c[1:]))
                 )
+            elif cname == "envelope":
+                cmd = commands.get_command_instance("envelope", ifcontrol)
+                cmd.check_next_arg("tag", c[1])
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[{}]".format(",".join('"{}"'.format(val) for val in c[2]))
+                )
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[{}]".format(",".join('"{}"'.format(val) for val in c[3]))
+                )
+            elif cname == "body":
+                cmd = commands.get_command_instance("body", ifcontrol, False)
+                self.require(cmd.extension)
+                cmd.check_next_arg("tag", c[1])
+                cmd.check_next_arg("tag", c[2])
+                cmd.check_next_arg(
+                    "stringlist",
+                    "[%s]" % (",".join('"%s"' % val for val in c[3:]))
+                )
             else:
                 if c[1].startswith(':not'):
                     cmd = self.__build_condition(
@@ -308,7 +328,8 @@ class FiltersSet(object):
         if not flt:
             return None
         for node in flt.walk():
-            if isinstance(node, (commands.AllofCommand, commands.AnyofCommand)):
+            if isinstance(node, (commands.AllofCommand,
+                                 commands.AnyofCommand)):
                 return node.__class__.__name__.lower().replace("command", "")
         return None
 
@@ -324,7 +345,9 @@ class FiltersSet(object):
                 negate = True
             elif isinstance(node, (commands.HeaderCommand,
                                    commands.SizeCommand,
-                                   commands.ExistsCommand)):
+                                   commands.ExistsCommand,
+                                   commands.BodyCommand,
+                                   commands.EnvelopeCommand)):
                 args = node.args_as_tuple()
                 if negate:
                     if node.name == "header":
diff --git a/sievelib/managesieve.py b/sievelib/managesieve.py
index 365f098..60307b2 100644
--- a/sievelib/managesieve.py
+++ b/sievelib/managesieve.py
@@ -702,8 +702,8 @@ class Client(object):
         if "VERSION" not in self.__capabilities:
             raise NotImplementedError(
                 "server does not support CHECKSCRIPT command")
-        content = tools.to_bytes(content)
-        content = tools.to_bytes("{%d+}" % len(content)) + CRLF + content
+        content = tools.to_bytes(
+            u"{%d+}%s%s" % (len(content), str(CRLF), content))
         code, data = self.__send_command("CHECKSCRIPT", [content])
         if code == "OK":
             return True


Test patch:
diff --git a/sievelib/tests/test_factory.py b/sievelib/tests/test_factory.py
index 4ac273c..a8fb390 100644
--- a/sievelib/tests/test_factory.py
+++ b/sievelib/tests/test_factory.py
@@ -4,9 +4,7 @@ from __future__ import unicode_literals
 import unittest
 import six
 
-from sievelib import commands
 from sievelib.factory import FiltersSet
-from sievelib.parser import Parser
 
 
 class FactoryTestCase(unittest.TestCase):
@@ -52,6 +50,24 @@ class FactoryTestCase(unittest.TestCase):
         conditions = self.fs.get_filter_conditions("ruleA")
         self.assertEqual(orig_conditions, conditions)
 
+        orig_conditions = [("envelope", ":is", ["From"], ["hello"])]
+        self.fs.addfilter(
+            "ruleB",
+            orig_conditions,
+            [("fileinto", "INBOX")]
+        )
+        conditions = self.fs.get_filter_conditions("ruleB")
+        self.assertEqual(orig_conditions, conditions)
+
+        orig_conditions = [("body", ":raw", ":contains", "matteo")]
+        self.fs.addfilter(
+            "ruleC",
+            orig_conditions,
+            [("fileinto", "INBOX")]
+        )
+        conditions = self.fs.get_filter_conditions("ruleC")
+        self.assertEqual(orig_conditions, conditions)
+
     def test_get_filter_matchtype(self):
         """Test get_filter_matchtype method."""
         self.fs.addfilter(
@@ -222,6 +238,36 @@ if anyof (header :is "Sender" "toto@toto.com") {
 }
 """)
 
+    def test_add_body_filter(self):
+        """Add a body filter."""
+        self.fs.addfilter(
+            "test",
+            [("body", ":raw", ":contains", "matteo")],
+            [("fileinto", "Toto")]
+        )
+        self.assertEqual("{}".format(self.fs), """require ["body", "fileinto"];
+
+# Filter: test
+if anyof (body :contains :raw ["matteo"]) {
+    fileinto "Toto";
+}
+""")
+
+    def test_add_envelope_filter(self):
+        """Add a envelope filter."""
+        self.fs.addfilter(
+            "test",
+            [("envelope", ":is", ["From"], ["hello"])],
+            [("fileinto", "INBOX")]
+        )
+        self.assertEqual("{}".format(self.fs), """require ["fileinto"];
+
+# Filter: test
+if anyof (envelope :is ["From"] ["hello"]) {
+    fileinto "INBOX";
+}
+""")
+
 
 if __name__ == "__main__":
     unittest.main()