summaryrefslogtreecommitdiff
path: root/src/mailman/chains/base.py
blob: 9507d8fbc322ea84b3eb712f21b8f2789df5d812 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# Copyright (C) 2008-2017 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.

"""Base class for terminal chains."""

from mailman.config import config
from mailman.core.i18n import _
from mailman.interfaces.chain import (
    IChain, IChainIterator, IChainLink, IMutableChain, LinkAction)
from mailman.interfaces.rules import IRule
from mailman.utilities.modules import abstract_component
from public import public
from zope.interface import implementer


@public
def format_reasons(reasons):
    """Translate and format hold and rejection reasons.

    :param reasons: A list of reasons from the rules that hit.  Each reason is
        a string to be translated or a tuple consisting of a string with {}
        replacements and one or more replacement values.
    :returns: A list of the translated and formatted strings.
    """
    new_reasons = []
    for reason in reasons:
        if isinstance(reason, tuple):
            new_reasons.append(_(reason[0]).format(*reason[1:]))
        else:
            new_reasons.append(_(reason))
    return new_reasons


@public
@implementer(IChainLink)
class Link:
    """A chain link."""

    def __init__(self, rule, action=None, chain=None, function=None):
        self.rule = (rule
                     if IRule.providedBy(rule)
                     else config.rules[rule])
        self.action = (LinkAction.defer if action is None else action)
        self.chain = (chain
                      if chain is None or IChain.providedBy(chain)
                      else config.chains[chain])
        self.function = function

    def __repr__(self):
        message = '<Link "if {0.rule.name} then {0.action}"'
        if self.chain is None and self.function is not None:
            message += ' {0.function.__name__}()'
        elif self.chain is not None and self.function is None:
            message += ' {0.chain.name}'
        elif self.chain is None and self.function is None:
            pass
        else:
            message += ' {0.chain.name} {0.function.__name__}()'
        message += '>'
        return message.format(self)


@public
@abstract_component
@implementer(IChain, IChainIterator)
class TerminalChainBase:
    """A base chain that always matches and executes a method.

    The method is called '_process()' and must be provided by the subclass.
    """
    def _process(self, mlist, msg, msgdata):
        """Process the message for the given mailing list.

        This must be overridden by subclasses.

        :param mlist: The mailing list.
        :param msg: The message.
        :param msgdata: The message metadata.
        """
        raise NotImplementedError

    def get_links(self, mlist, msg, msgdata):
        """See `IChain`."""
        return iter(self)

    def __iter__(self):
        """See `IChainIterator`."""
        # First, yield a link that always runs the process method.
        yield Link('truth', LinkAction.run, function=self._process)
        # Now yield a rule that stops all processing.
        yield Link('truth', LinkAction.stop)


@public
@abstract_component
@implementer(IMutableChain)
class Chain:
    """Generic chain base class."""

    def __init__(self, name, description):
        assert name not in config.chains, (
            'Duplicate chain name: {}'.format(name))
        self.name = name
        self.description = description
        self._links = []

    def append_link(self, link):
        """See `IMutableChain`."""
        self._links.append(link)

    def flush(self):
        """See `IMutableChain`."""
        self._links = []

    def get_links(self, mlist, msg, msgdata):
        """See `IChain`."""
        return iter(ChainIterator(self))

    def get_iterator(self):
        """Return an iterator over the links."""
        # We do it this way in order to preserve a separation of interfaces,
        # and allows .get_links() to be overridden.
        yield from self._links


@public
@implementer(IChainIterator)
class ChainIterator:
    """Generic chain iterator."""

    def __init__(self, chain):
        self._chain = chain

    def __iter__(self):
        """See `IChainIterator`."""
        return self._chain.get_iterator()