1
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
8
'EmailCommandCollection',
13
from lazr.lifecycle.event import ObjectModifiedEvent
14
from lazr.lifecycle.interfaces import (
18
from lazr.lifecycle.snapshot import Snapshot
19
from zope.interface import providedBy
21
from lp.services.mail.helpers import get_error_message
22
from lp.services.mail.interfaces import EmailProcessingError
25
def normalize_arguments(string_args):
26
"""Normalizes the string arguments.
28
The string_args argument is simply the argument string whitespace
29
splitted. Sometimes arguments may be quoted, though, so that they can
30
contain space characters. For example "This is a long string".
32
This function loops through all the argument and joins the quoted strings
33
into a single arguments.
35
>>> normalize_arguments(['"This', 'is', 'a', 'long', 'string."'])
36
['This is a long string.']
38
>>> normalize_arguments(
39
... ['"First', 'string"', '"Second', 'string"', 'foo'])
40
['First string', 'Second string', 'foo']
44
for item in string_args:
45
if item.startswith('"'):
47
result.append(item[1:])
48
elif quoted_string and item.endswith('"'):
49
result[-1] += ' ' + item[:-1]
52
result[-1] += ' ' + item
60
"""Represents a command.
62
Both name the values in the args list are strings.
64
_numberOfArguments = None
66
def __init__(self, name, string_args):
68
self.string_args = normalize_arguments(string_args)
70
def _ensureNumberOfArguments(self):
71
"""Check that the number of arguments is correct.
73
Raise an EmailProcessingError
75
if self._numberOfArguments is not None:
76
num_arguments_got = len(self.string_args)
77
if self._numberOfArguments != num_arguments_got:
78
raise EmailProcessingError(
80
'num-arguments-mismatch.txt',
81
command_name=self.name,
82
num_arguments_expected=self._numberOfArguments,
83
num_arguments_got=num_arguments_got))
85
def convertArguments(self, context):
86
"""Converts the string argument to Python objects.
88
Returns a dict with names as keys, and the Python objects as
91
raise NotImplementedError
94
"""See IEmailCommand."""
95
return ' '.join([self.name] + self.string_args)
98
class EditEmailCommand(EmailCommand):
99
"""Helper class for commands that edits the context.
101
It makes sure that the correct events are notified.
104
def execute(self, context, current_event):
105
"""See IEmailCommand."""
106
self._ensureNumberOfArguments()
107
args = self.convertArguments(context)
109
edited_fields = set()
110
if IObjectModifiedEvent.providedBy(current_event):
111
context_snapshot = current_event.object_before_modification
112
edited_fields.update(current_event.edited_fields)
114
context_snapshot = Snapshot(
115
context, providing=providedBy(context))
118
for attr_name, attr_value in args.items():
119
if getattr(context, attr_name) != attr_value:
120
self.setAttributeValue(context, attr_name, attr_value)
122
if edited and not IObjectCreatedEvent.providedBy(current_event):
123
edited_fields.update(args.keys())
124
current_event = ObjectModifiedEvent(
125
context, context_snapshot, list(edited_fields))
127
return context, current_event
129
def setAttributeValue(self, context, attr_name, attr_value):
130
"""See IEmailCommand."""
131
setattr(context, attr_name, attr_value)
134
class NoSuchCommand(KeyError):
135
"""A command with the given name couldn't be found."""
138
class EmailCommandCollection:
139
"""A collection of email commands."""
143
"""Returns all the command names."""
144
return klass._commands.keys()
147
def get(klass, name, string_args):
148
"""Returns a command object with the given name and arguments.
150
If a command with the given name can't be found, a NoSuchCommand
153
command_class = klass._commands.get(name)
154
if command_class is None:
155
raise NoSuchCommand(name)
156
return command_class(name, string_args)