[Python-modules-commits] [python-sievelib] 02/09: New upstream version 1.1.0

Michael Fladischer fladi at moszumanska.debian.org
Tue Jan 9 07:14:33 UTC 2018


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch debian/master
in repository python-sievelib.

commit a79b0c5eaa885b62db93be71eec760a46625e334
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Tue Jan 9 08:03:31 2018 +0100

    New upstream version 1.1.0
---
 PKG-INFO                       |  3 ++-
 README.rst                     |  1 +
 setup.cfg                      |  1 -
 setup.py                       |  3 ++-
 sievelib.egg-info/PKG-INFO     |  3 ++-
 sievelib/commands.py           | 55 +++++++++++++++++++++++++++++++++++++-----
 sievelib/factory.py            | 16 +++++++++++-
 sievelib/parser.py             | 17 +++++++------
 sievelib/tests/test_factory.py | 50 ++++++++++++++++++++++++++------------
 sievelib/tests/test_parser.py  | 32 ++++++++++++++++++++++++
 10 files changed, 147 insertions(+), 34 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index cadc3f4..08d4832 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sievelib
-Version: 1.0.0
+Version: 1.1.0
 Summary: Client-side SIEVE library
 Home-page: https://github.com/tonioo/sievelib
 Author: Antoine Nguyen
@@ -44,6 +44,7 @@ Description: sievelib
         
         The following extensions are also supported:
         
+        * Copying Without Side Effects (`RFC 3894 <https://tools.ietf.org/html/rfc3894>`_)
         * Date and Index (`RFC 5260 <https://tools.ietf.org/html/rfc5260>`_)
         * Vacation (`RFC 5230 <http://tools.ietf.org/html/rfc5230>`_)
         
diff --git a/README.rst b/README.rst
index 4583eab..34a9de4 100644
--- a/README.rst
+++ b/README.rst
@@ -36,6 +36,7 @@ supported.
 
 The following extensions are also supported:
 
+* Copying Without Side Effects (`RFC 3894 <https://tools.ietf.org/html/rfc3894>`_)
 * Date and Index (`RFC 5260 <https://tools.ietf.org/html/rfc5260>`_)
 * Vacation (`RFC 5230 <http://tools.ietf.org/html/rfc5230>`_)
 
diff --git a/setup.cfg b/setup.cfg
index 861a9f5..8bfd5a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,4 @@
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff --git a/setup.py b/setup.py
index 79c3703..e2def9d 100644
--- a/setup.py
+++ b/setup.py
@@ -50,11 +50,12 @@ def parse_dependency_links(*filenames):
 def read(fname):
     return open(os.path.join(ROOT, fname)).read()
 
+
 setup(
     name="sievelib",
     packages=find_packages(),
     include_package_data=True,
-    version="1.0.0",
+    version="1.1.0",
     description="Client-side SIEVE library",
     author="Antoine Nguyen",
     author_email="tonio at ngyn.org",
diff --git a/sievelib.egg-info/PKG-INFO b/sievelib.egg-info/PKG-INFO
index cadc3f4..08d4832 100644
--- a/sievelib.egg-info/PKG-INFO
+++ b/sievelib.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: sievelib
-Version: 1.0.0
+Version: 1.1.0
 Summary: Client-side SIEVE library
 Home-page: https://github.com/tonioo/sievelib
 Author: Antoine Nguyen
@@ -44,6 +44,7 @@ Description: sievelib
         
         The following extensions are also supported:
         
+        * Copying Without Side Effects (`RFC 3894 <https://tools.ietf.org/html/rfc3894>`_)
         * Date and Index (`RFC 5260 <https://tools.ietf.org/html/rfc5260>`_)
         * Vacation (`RFC 5230 <http://tools.ietf.org/html/rfc5230>`_)
         
diff --git a/sievelib/commands.py b/sievelib/commands.py
index 4b482ba..494a432 100644
--- a/sievelib/commands.py
+++ b/sievelib/commands.py
@@ -29,8 +29,14 @@ import sys
 from future.utils import python_2_unicode_compatible
 
 
+class CommandError(Exception):
+    """Base command exception class."""
+
+    pass
+
+
 @python_2_unicode_compatible
-class UnknownCommand(Exception):
+class UnknownCommand(CommandError):
     """Specific exception raised when an unknown command is encountered"""
 
     def __init__(self, name):
@@ -41,7 +47,7 @@ class UnknownCommand(Exception):
 
 
 @python_2_unicode_compatible
-class BadArgument(Exception):
+class BadArgument(CommandError):
     """Specific exception raised when a bad argument is encountered"""
 
     def __init__(self, command, seen, expected):
@@ -55,7 +61,7 @@ class BadArgument(Exception):
 
 
 @python_2_unicode_compatible
-class BadValue(Exception):
+class BadValue(CommandError):
     """Specific exception raised when a bad argument value is encountered"""
 
     def __init__(self, argument, value):
@@ -67,6 +73,16 @@ class BadValue(Exception):
                % (self.value, self.argument)
 
 
+ at python_2_unicode_compatible
+class ExtensionNotLoaded(CommandError):
+    """Raised when an extension is not loaded."""
+
+    def __init__(self, name):
+        self.name = name
+
+    def __str__(self):
+        return "extension '{}' not loaded".format(self.name)
+
 # Statement elements (see RFC, section 8.3)
 # They are used in different commands.
 comparator = {"name": "comparator",
@@ -150,7 +166,11 @@ class Command(object):
                                 target.write(", ")
                         target.write(")")
                     else:
-                        target.write("[" + ((", ".join(['"%s"' % v.strip('"') for v in value]))) + "]")
+                        target.write(
+                            "[{}]".format(", ".join(
+                                ['"%s"' % v.strip('"') for v in value])
+                            )
+                        )
                     continue
                 if isinstance(value, Command):
                     value.tosieve(indentlevel, target=target)
@@ -225,7 +245,8 @@ class Command(object):
                         for t in value:
                             t.dump(indentlevel, target)
                     else:
-                        self.__print("[" + (",".join(value)) + "]", indentlevel, target=target)
+                        self.__print("[" + (",".join(value)) + "]",
+                                     indentlevel, target=target)
                     continue
                 if isinstance(value, Command):
                     value.dump(indentlevel, target)
@@ -288,7 +309,7 @@ class Command(object):
             return True
         return value.lower() in arg["values"]
 
-    def check_next_arg(self, atype, avalue, add=True):
+    def check_next_arg(self, atype, avalue, add=True, check_extension=True):
         """Argument validity checking
 
         This method is usually used by the parser to check if detected
@@ -312,6 +333,8 @@ class Command(object):
         :param atype: the argument's type
         :param avalue: the argument's value
         :param add: indicates if this argument should be recorded on success
+        :param check_extension: raise ExtensionNotLoaded if extension not
+                                loaded
         :return: True on success, False otherwise
         """
         if not self.has_arguments():
@@ -353,6 +376,12 @@ class Command(object):
                 break
 
             if atype in curarg["type"]:
+                ext = curarg.get("extension")
+                condition = (
+                    check_extension and ext and
+                    ext not in RequireCommand.loaded_extensions)
+                if condition:
+                    raise ExtensionNotLoaded(ext)
                 if self.__is_valid_value_for_arg(curarg, avalue):
                     if "extra_arg" in curarg:
                         self.curarg = curarg
@@ -368,6 +397,10 @@ class Command(object):
                               self.args_definition[pos]["type"])
         return True
 
+    def __contains__(self, name):
+        """Check if argument is provided with command."""
+        return name in self.arguments
+
     def __getitem__(self, name):
         """Shorcut to access a command argument
 
@@ -460,6 +493,11 @@ class ActionCommand(Command):
 class FileintoCommand(ActionCommand):
     is_extension = True
     args_definition = [
+        {"name": "copy",
+         "type": ["tag"],
+         "values": [":copy"],
+         "required": False,
+         "extension": "copy"},
         {"name": "mailbox",
          "type": ["string"],
          "required": True}
@@ -468,6 +506,11 @@ class FileintoCommand(ActionCommand):
 
 class RedirectCommand(ActionCommand):
     args_definition = [
+        {"name": "copy",
+         "type": ["tag"],
+         "values": [":copy"],
+         "required": False,
+         "extension": "copy"},
         {"name": "address",
          "type": ["string"],
          "required": True}
diff --git a/sievelib/factory.py b/sievelib/factory.py
index 840e990..37721ba 100644
--- a/sievelib/factory.py
+++ b/sievelib/factory.py
@@ -97,6 +97,14 @@ class FiltersSet(object):
         if name not in self.requires:
             self.requires += [name]
 
+    def check_if_arg_is_extension(self, arg):
+        """Include extension if arg requires one."""
+        args_using_extensions = {
+            ":copy": "copy"
+        }
+        if arg in args_using_extensions:
+            self.require(args_using_extensions[arg])
+
     def __gen_require_command(self):
         """Internal method to create a RequireCommand based on requirements
 
@@ -200,7 +208,13 @@ class FiltersSet(object):
             if action.is_extension:
                 self.require(actdef[0])
             for arg in actdef[1:]:
-                action.check_next_arg("string", self.__quote_if_necessary(arg))
+                self.check_if_arg_is_extension(arg)
+                if arg.startswith(":"):
+                    atype = "tag"
+                else:
+                    atype = "string"
+                    arg = self.__quote_if_necessary(arg)
+                action.check_next_arg(atype, arg, check_extension=False)
             ifcontrol.addchild(action)
         return ifcontrol
 
diff --git a/sievelib/parser.py b/sievelib/parser.py
index 137a9f9..1c5f86f 100755
--- a/sievelib/parser.py
+++ b/sievelib/parser.py
@@ -17,8 +17,7 @@ from future.utils import python_2_unicode_compatible, text_type
 import six
 
 from sievelib.commands import (
-    get_command_instance, UnknownCommand, BadArgument, BadValue
-)
+    get_command_instance, CommandError, RequireCommand)
 
 
 @python_2_unicode_compatible
@@ -136,6 +135,7 @@ class Parser(object):
         self.__curstringlist = None
         self.__expected = None
         self.__opened_blocks = 0
+        RequireCommand.loaded_extensions = []
 
     def __set_expected(self, *args, **kwargs):
         """Set the next expected token.
@@ -326,15 +326,17 @@ class Parser(object):
 
             if ttype != "identifier":
                 return False
-            command = get_command_instance(tvalue.decode("ascii"), self.__curcommand)
+            command = get_command_instance(
+                tvalue.decode("ascii"), self.__curcommand)
             if command.get_type() == "test":
-                raise ParseError("%s may not appear as a first command" % command.name)
+                raise ParseError(
+                    "%s may not appear as a first command" % command.name)
             if command.get_type() == "control" and command.accept_children \
-                and command.has_arguments():
+               and command.has_arguments():
                 self.__set_expected("identifier")
             if self.__curcommand is not None:
                 if not self.__curcommand.addchild(command):
-                    raise ParseError("%s unexpected after a %s" % \
+                    raise ParseError("%s unexpected after a %s" %
                                      (tvalue, self.__curcommand.name))
             self.__curcommand = command
             self.__cstate = self.__arguments
@@ -405,7 +407,7 @@ class Parser(object):
                 raise ParseError("end of script reached while %s expected" %
                                  "|".join(self.__expected))
 
-        except (ParseError, UnknownCommand, BadArgument, BadValue) as e:
+        except (ParseError, CommandError) as e:
             self.error = "line %d: %s" % (self.lexer.curlineno(), str(e))
             return False
         return True
@@ -420,7 +422,6 @@ class Parser(object):
         """
         with open(name, "rb") as fp:
             return self.parse(fp.read())
-        
 
     def dump(self, target=sys.stdout):
         """Dump the parsing tree.
diff --git a/sievelib/tests/test_factory.py b/sievelib/tests/test_factory.py
index 061fa14..dfa0ff9 100644
--- a/sievelib/tests/test_factory.py
+++ b/sievelib/tests/test_factory.py
@@ -16,23 +16,41 @@ class FactoryTestCase(unittest.TestCase):
         self.fs.addfilter(
             "rule1",
             [('Sender', ":is", 'toto at toto.com'), ],
-            [("fileinto", 'Toto'), ])
+            [("fileinto", ":copy", "Toto"), ])
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.fs.tosieve(output)
-        self.assertEqual(output.getvalue(), """require ["fileinto"];
+        self.assertEqual(output.getvalue(), """require ["fileinto", "copy"];
 
 # Filter: rule1
 if anyof (header :is "Sender" "toto at toto.com") {
-    fileinto "Toto";
+    fileinto :copy "Toto";
+}
+""")
+        output.close()
+
+    def test_use_action_with_tag(self):
+        output = six.StringIO()
+        self.fs.addfilter(
+            "rule1",
+            [('Sender', ":is", 'toto at toto.com'), ],
+            [("redirect", ":copy", "toto at titi.com"), ])
+        self.assertIsNot(self.fs.getfilter("rule1"), None)
+        self.fs.tosieve(output)
+        self.assertEqual(output.getvalue(), """require ["copy"];
+
+# Filter: rule1
+if anyof (header :is "Sender" "toto at toto.com") {
+    redirect :copy "toto at titi.com";
 }
 """)
         output.close()
 
     def test_add_header_filter_with_not(self):
         output = six.StringIO()
-        self.fs.addfilter("rule1",
-                          [('Sender', ":notcontains", 'toto at toto.com'),],
-                          [("fileinto", 'Toto'),])
+        self.fs.addfilter(
+            "rule1",
+            [('Sender', ":notcontains", 'toto at toto.com')],
+            [("fileinto", 'Toto')])
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.fs.tosieve(output)
         self.assertEqual(output.getvalue(), """require ["fileinto"];
@@ -47,8 +65,9 @@ if anyof (not header :contains "Sender" "toto at toto.com") {
         output = six.StringIO()
         self.fs.addfilter(
             "rule1",
-            [('exists', "list-help", "list-unsubscribe", "list-subscribe", "list-owner")],
-            [("fileinto", 'Toto'),]
+            [('exists', "list-help", "list-unsubscribe",
+              "list-subscribe", "list-owner")],
+            [("fileinto", 'Toto')]
         )
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.fs.tosieve(output)
@@ -64,8 +83,9 @@ if anyof (exists ["list-help","list-unsubscribe","list-subscribe","list-owner"])
         output = six.StringIO()
         self.fs.addfilter(
             "rule1",
-            [('notexists', "list-help", "list-unsubscribe", "list-subscribe", "list-owner")],
-            [("fileinto", 'Toto'),]
+            [('notexists', "list-help", "list-unsubscribe",
+              "list-subscribe", "list-owner")],
+            [("fileinto", 'Toto')]
         )
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.fs.tosieve(output)
@@ -82,7 +102,7 @@ if anyof (not exists ["list-help","list-unsubscribe","list-subscribe","list-owne
         self.fs.addfilter(
             "rule1",
             [('size', ":over", "100k")],
-            [("fileinto", 'Totoéé'),]
+            [("fileinto", 'Totoéé')]
         )
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.fs.tosieve(output)
@@ -96,8 +116,8 @@ if anyof (size :over 100k) {
 
     def test_remove_filter(self):
         self.fs.addfilter("rule1",
-                          [('Sender', ":is", 'toto at toto.com'),],
-                          [("fileinto", 'Toto'),])
+                          [('Sender', ":is", 'toto at toto.com')],
+                          [("fileinto", 'Toto')])
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.assertEqual(self.fs.removefilter("rule1"), True)
         self.assertIs(self.fs.getfilter("rule1"), None)
@@ -107,8 +127,8 @@ if anyof (size :over 100k) {
         FIXME: Extra spaces are written between if and anyof, why?!
         """
         self.fs.addfilter("rule1",
-                          [('Sender', ":is", 'toto at toto.com'),],
-                          [("fileinto", 'Toto'),])
+                          [('Sender', ":is", 'toto at toto.com')],
+                          [("fileinto", 'Toto')])
         self.assertIsNot(self.fs.getfilter("rule1"), None)
         self.assertEqual(self.fs.disablefilter("rule1"), True)
         output = six.StringIO()
diff --git a/sievelib/tests/test_parser.py b/sievelib/tests/test_parser.py
index de268cf..1d79ca4 100644
--- a/sievelib/tests/test_parser.py
+++ b/sievelib/tests/test_parser.py
@@ -694,5 +694,37 @@ if allof (
 """)
 
 
+class CopyWithoutSideEffectsTestCase(SieveTest):
+    """RFC3894 test cases."""
+
+    def test_redirect_with_copy(self):
+        self.compilation_ko(b"""
+if header :contains "subject" "test" {
+    redirect :copy "dev at null.com";
+}
+""")
+
+        self.compilation_ok(b"""require "copy";
+if header :contains "subject" "test" {
+    redirect :copy "dev at null.com";
+}
+""")
+
+    def test_fileinto_with_copy(self):
+        self.compilation_ko(b"""require "fileinto";
+if header :contains "subject" "test" {
+    fileinto :copy "Spam";
+}
+""")
+        self.assertEqual(
+            self.parser.error, "line 3: extension 'copy' not loaded")
+
+        self.compilation_ok(b"""require ["fileinto", "copy"];
+if header :contains "subject" "test" {
+    fileinto :copy "Spam";
+}
+""")
+
+
 if __name__ == "__main__":
     unittest.main()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-sievelib.git



More information about the Python-modules-commits mailing list