Skip to content

Commit 49bca8c

Browse files
Merge branch 'develop'
2 parents 53dc873 + 3656229 commit 49bca8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+325
-242
lines changed

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# Sifter3 - Sieve email filter (RFC 5228)
22

3-
Sifter3 is a Python 3 implementation of the Sieve email filter language (RFC 5228) and is based on the Python 2 version from <https://github.com/garyp/sifter>
3+
Sifter3 is a Python 3 implementation of the Sieve email filter language (RFC 5228)
44

55
![Python package](https://github.com/manfred-kaiser/sifter3/workflows/Python%20package/badge.svg)
6-
[![Documentation Status](https://readthedocs.org/projects/sifter3/badge/?version=master)](https://sifter3.readthedocs.io/de/master/?badge=master)
6+
[![Documentation Status](https://readthedocs.org/projects/sifter3/badge/?version=latest)](https://sifter3.readthedocs.io/en/latest/?badge=latest)
77
[![CodeFactor](https://www.codefactor.io/repository/github/manfred-kaiser/sifter3/badge)](https://www.codefactor.io/repository/github/manfred-kaiser/sifter3)
88
[![Github version](https://img.shields.io/github/v/release/manfred-kaiser/sifter3?label=github&logo=github)](https://github.com/manfred-kaiser/sifter3/releases)
99
[![PyPI version](https://img.shields.io/pypi/v/sifter3.svg?logo=pypi&logoColor=FFE873)](https://pypi.org/project/sifter3/)
@@ -18,13 +18,17 @@ FEATURES
1818

1919
- Supports all of the base Sieve spec from RFC 5228, except for
2020
features still listed under TODO below
21+
- multiline strings (since version 0.2.2)
22+
- bracketed comments (since version 0.2.4)
2123
- Extensions supported:
2224
- regex (draft-ietf-sieve-regex-01)
2325
- body (RFC 5173)
2426
- variables (RFC 5229)
2527
- enotify (RFC 5435, particularly the mailto method RFC 5436)
2628
- imap4flags (RFC 5232: setflag, addflag, removeflag; not supported: hasflags, :flags)
27-
- compatible with the python 2 version from https://github.com/garyp/sifter
29+
- reject and ereject (RFC 5429)
30+
- ihave (RFC 5463)
31+
2832

2933
INSTALL
3034
=======
@@ -45,6 +49,15 @@ email message. Each action is a tuple consisting of the action name and
4549
action-specific arguments. It is up to the caller to manipulate the
4650
message and message store based on the actions returned.
4751

52+
COMMAND LINE
53+
============
54+
55+
The output of the command line tool can be parsed as json.
56+
57+
$ sifter tests/evaluation_1.rules tests/evaluation_1.msg
58+
[['redirect', '[email protected]']]
59+
60+
4861
WARNINGS
4962
========
5063

@@ -61,8 +74,6 @@ TODO
6174
filtering
6275
- Base spec features not yet implemented:
6376
- encoded characters (section 2.4.2.4)
64-
- multi-line strings (section 2.4.2)
65-
- bracketed comments (section 2.3)
6677
- message uniqueness (section 2.10.3)
6778
- envelope test (section 5.4)
6879
- handle message loops (section 10)
@@ -84,6 +95,5 @@ TODO
8495
- environment (RFC 5183)
8596
- date and index (RFC 5260)
8697
- editheader (RFC 5293)
87-
- ihave (RFC 5463)
8898
- mailbox metadata (RFC 5490)
8999
- xmpp notifications (RFC 5437)

docs/index.rst

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ FEATURES
99

1010
- Supports all of the base Sieve spec from RFC 5228, except for
1111
features still listed under TODO below
12+
13+
- multiline strings (since version 0.2.2)
14+
- bracketed comments (since version 0.2.4)
15+
1216
- Extensions supported:
1317

1418
- regex (draft-ietf-sieve-regex-01)
1519
- body (RFC 5173)
1620
- variables (RFC 5229)
1721
- enotify (RFC 5435, particularly the mailto method RFC 5436)
1822
- imap4flags (RFC 5232: setflag, addflag, removeflag; not supported: hasflags, :flags)
19-
20-
- compatible with the Python 2 version from https://github.com/garyp/sifter
23+
- reject and ereject (RFC 5429) (since version 0.2.4)
24+
- ihave (RFC 5463) (since version 0.2.5)
2125

2226
INSTALL
2327
-------
@@ -45,6 +49,17 @@ consisting of the action name and action-specific arguments. It is up to
4549
the caller to manipulate the message and message store based on the
4650
actions returned.
4751

52+
COMMAND LINE
53+
------------
54+
55+
The output of the command line tool can be parsed as json.
56+
57+
.. code-block:: bash
58+
59+
$ sifter tests/evaluation_1.rules tests/evaluation_1.msg
60+
[['redirect', '[email protected]']]
61+
62+
4863
WARNINGS
4964
--------
5065

@@ -60,10 +75,11 @@ TODO
6075
- An example adaptor that provides Unix LDA behavior using sieve for
6176
filtering
6277
- Base spec features not yet implemented:
78+
6379
- encoded characters (section 2.4.2.4)
64-
- multi-line strings (section 2.4.2)
65-
- bracketed comments (section 2.3)
6680
- message uniqueness (section 2.10.3)
6781
- envelope test (section 5.4)
6882
- handle message loops (section 10)
6983
- limit abuse of redirect action (section
84+
- address test should limit allowed headers to those that contain
85+
addresses (section 5.1)

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name="sifter3",
13-
version="0.2.1",
13+
version="0.2.6",
1414
author="Manfred Kaiser, Gary Peck",
1515
1616
url="https://sifter3.readthedocs.io/en/latest/",
@@ -60,6 +60,8 @@
6060
'keep = sifter.commands.keep:CommandKeep',
6161
'notify = sifter.commands.notify:CommandNotify',
6262
'redirect = sifter.commands.redirect:CommandRedirect',
63+
'reject = sifter.commands.reject:CommandReject',
64+
'ereject = sifter.commands.reject:CommandEReject',
6365
'require = sifter.commands.require:CommandRequire',
6466
'set = sifter.commands.variables:CommandSet',
6567
'stop = sifter.commands.stop:CommandStop',
@@ -76,6 +78,7 @@
7678
'true = sifter.tests.true:TestTrue',
7779
'valid_notify_method = sifter.tests.notify:TestValidNotifyMethod',
7880
'notify_method_capability = sifter.tests.notify:TestValidNotifyMethod',
81+
'ihave = sifter.tests.ihave:TestIHave',
7982
# sifter comparators
8083
'ascii_casemap = sifter.comparators.ascii_casemap:ComparatorASCIICasemap',
8184
'ascii_casemap_noi = sifter.comparators.ascii_casemap:ComparatorASCIICasemapnoi',

sifter/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import logging
55
import sys
6+
import json
67

78
import sifter.parser
89

@@ -24,4 +25,4 @@ def main() -> None:
2425
rules = sifter.parser.parse_file(open(args.rulefile))
2526
msg = email.message_from_file(open(args.messagefile))
2627
msg_actions = rules.evaluate(msg)
27-
print(msg_actions)
28+
print(json.dumps(msg_actions, indent=4))

sifter/commands/discard.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
from email.message import Message
2-
from typing import (
3-
Optional
4-
)
52

6-
from sifter.grammar.actions import Actions
73
from sifter.grammar.state import EvaluationState
84
from sifter.grammar.command import Command
95

106

117
# section 4.4
128
class CommandDiscard(Command):
139

14-
RULE_IDENTIFIER = 'DISCARD'
10+
HANDLER_ID = 'DISCARD'
1511

16-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
12+
def evaluate(self, message: Message, state: EvaluationState) -> None:
1713
state.actions.cancel_implicit_keep()
18-
return None

sifter/commands/fileinto.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
from email.message import Message
2-
from typing import (
3-
Optional
4-
)
52

63
from sifter.grammar.command import Command
74
from sifter.grammar.string import expand_variables
85
from sifter.validators.stringlist import StringList
96
from sifter.grammar.state import EvaluationState
10-
from sifter.grammar.actions import Actions
117

128

139
# section 4.1
1410
class CommandFileInto(Command):
1511

16-
RULE_IDENTIFIER = 'FILEINTO'
12+
HANDLER_ID = 'FILEINTO'
13+
EXTENSION_NAME = 'fileinto'
1714
POSITIONAL_ARGS = [StringList(length=1)]
1815

19-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
16+
def evaluate(self, message: Message, state: EvaluationState) -> None:
2017
state.check_required_extension('fileinto', 'FILEINTO')
2118

2219
file_dest = self.positional_args[0]
2320
file_dest = list(map(lambda s: expand_variables(s, state), file_dest)) # type: ignore
2421

2522
state.actions.append('fileinto', file_dest)
2623
state.actions.cancel_implicit_keep()
27-
return None

sifter/commands/if_cmd.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions
2525

2626
class CommandIf(CommandIfBase):
2727

28-
RULE_IDENTIFIER = 'IF'
28+
HANDLER_ID = 'IF'
2929

3030

3131
class CommandElsIf(CommandIfBase):
3232

33-
RULE_IDENTIFIER = 'ELSIF'
33+
HANDLER_ID = 'ELSIF'
3434

3535
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
3636
if state.last_if:
@@ -40,7 +40,7 @@ def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions
4040

4141
class CommandElse(Command):
4242

43-
RULE_IDENTIFIER = 'ELSE'
43+
HANDLER_ID = 'ELSE'
4444
TESTS_MIN = 0
4545
HAS_BLOCKS = False
4646

sifter/commands/imap4flags.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from email.message import Message
2-
from typing import Optional
32

43
from sifter.grammar.command import Command
54
from sifter.validators.stringlist import StringList
65
from sifter.grammar.string import expand_variables
76
from sifter.grammar.state import EvaluationState
8-
from sifter.grammar.actions import Actions
97

108
# This implements the RFC5232 imap4flags extension
119
# commands: addflag, removeflag, setflag
@@ -15,38 +13,38 @@
1513

1614
class CommandSetFlag(Command):
1715

18-
RULE_IDENTIFIER = 'SETFLAG'
16+
HANDLER_ID = 'SETFLAG'
17+
EXTENSION_NAME = 'imap4flags'
1918
POSITIONAL_ARGS = [StringList()]
2019

21-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
20+
def evaluate(self, message: Message, state: EvaluationState) -> None:
2221
state.check_required_extension('imap4flags', 'imapflags')
2322
flag_list = self.positional_args[0]
2423
flag_list = list(map(lambda s: expand_variables(s, state), flag_list)) # type: ignore
2524
state.actions.append('setflag', flag_list)
26-
return None
2725

2826

2927
class CommandRemoveFlag(Command):
3028

31-
RULE_IDENTIFIER = 'REMOVEFLAG'
29+
HANDLER_ID = 'REMOVEFLAG'
30+
EXTENSION_NAME = 'imap4flags'
3231
POSITIONAL_ARGS = [StringList()]
3332

34-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
33+
def evaluate(self, message: Message, state: EvaluationState) -> None:
3534
state.check_required_extension('imap4flags', 'imapflags')
3635
flag_list = self.positional_args[0]
3736
flag_list = list(map(lambda s: expand_variables(s, state), flag_list)) # type: ignore
3837
state.actions.append('removeflag', flag_list)
39-
return None
4038

4139

4240
class CommandAddFlag(Command):
4341

44-
RULE_IDENTIFIER = 'ADDFLAG'
42+
HANDLER_ID = 'ADDFLAG'
43+
EXTENSION_NAME = 'imap4flags'
4544
POSITIONAL_ARGS = [StringList()]
4645

47-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
46+
def evaluate(self, message: Message, state: EvaluationState) -> None:
4847
state.check_required_extension('imap4flags', 'imapflags')
4948
flag_list = self.positional_args[0]
5049
flag_list = list(map(lambda s: expand_variables(s, state), flag_list)) # type: ignore
5150
state.actions.append('addflag', flag_list)
52-
return None

sifter/commands/keep.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
from email.message import Message
2-
from typing import (
3-
Optional
4-
)
52

63
from sifter.grammar.command import Command
74
from sifter.grammar.state import EvaluationState
8-
from sifter.grammar.actions import Actions
95

106

117
# section 4.3
128
class CommandKeep(Command):
139

14-
RULE_IDENTIFIER = 'KEEP'
10+
HANDLER_ID = 'KEEP'
1511
HAS_BLOCKS = False
1612

17-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
13+
def evaluate(self, message: Message, state: EvaluationState) -> None:
1814
state.actions.append('keep')
1915
state.actions.cancel_implicit_keep()
20-
return None

sifter/commands/notify.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import re
22
from email.message import Message
3-
from typing import Optional
43

54
from sifter.grammar.command import Command
65
from sifter.grammar.string import expand_variables
@@ -9,13 +8,13 @@
98
from sifter.grammar.rule import RuleSyntaxError
109
from sifter.extensions import ExtensionRegistry
1110
from sifter.grammar.state import EvaluationState
12-
from sifter.grammar.actions import Actions
1311

1412

1513
# RFC 5435
1614
class CommandNotify(Command):
1715

18-
RULE_IDENTIFIER = 'NOTIFY'
16+
HANDLER_ID = 'NOTIFY'
17+
EXTENSION_NAME = 'enotify'
1918
TAGGED_ARGS = {
2019
'from': Tag('FROM', (StringList(1),)),
2120
'importance': Tag('IMPORTANCE', (StringList(1),)),
@@ -26,7 +25,7 @@ class CommandNotify(Command):
2625
StringList(length=1)
2726
]
2827

29-
def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions]:
28+
def evaluate(self, message: Message, state: EvaluationState) -> None:
3029
notify_from = notify_importance = self.notify_message = None
3130
notify_options = [] # type: ignore
3231
if 'from' in self.tagged_args:
@@ -63,5 +62,3 @@ def evaluate(self, message: Message, state: EvaluationState) -> Optional[Actions
6362
'notify',
6463
(notify_method, notify_from, notify_importance, notify_options, notify_message) # type: ignore
6564
)
66-
67-
return None

0 commit comments

Comments
 (0)