Skip to content

Commit 502a5a0

Browse files
author
Richard Jones
committed
mailgw subject parsing has configurable levels of strictness
nosy messages may be sent individually to all recipients
1 parent 545b72f commit 502a5a0

File tree

7 files changed

+469
-111
lines changed

7 files changed

+469
-111
lines changed

CHANGES.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
This file contains the changes to the Roundup system over time. The entries
22
are given with the most recent entry first.
33

4-
????-??-?? 0.9.0
4+
2005-10-07 0.9.0b1
55
Feature:
66
- added "imapServer.py" script (sf patch 934567)
77
- added date selection popup windows (thanks Marcus Priesch)
88
- added Xapian indexer; replaces standard indexers if Xapian is available
9+
- mailgw subject parsing has configurable levels of strictness
10+
- nosy messages may be sent individually to all recipients
911

1012

11-
2005-??-?? 0.8.5
13+
2005-10-07 0.8.5
1214
Feature:
1315
- Argentinian Spanish translation by Ramiro Morales
1416

doc/customizing.txt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Customising Roundup
33
===================
44

5-
:Version: $Revision: 1.184 $
5+
:Version: $Revision: 1.185 $
66

77
.. This document borrows from the ZopeBook section on ZPT. The original is at:
88
http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -255,6 +255,31 @@ Section **mailgw**
255255
Default class to use in the mailgw if one isn't supplied in email subjects.
256256
To disable, leave the value blank.
257257

258+
subject_prefix_parsing -- ``strict``
259+
Controls the parsing of the [prefix] on subject lines in incoming emails.
260+
``strict`` will return an error to the sender if the [prefix] is not
261+
recognised. ``loose`` will attempt to parse the [prefix] but just
262+
pass it through as part of the issue title if not recognised. ``none``
263+
will always pass any [prefix] through as part of the issue title.
264+
265+
subject_suffix_parsing -- ``strict``
266+
Controls the parsing of the [suffix] on subject lines in incoming emails.
267+
``strict`` will return an error to the sender if the [suffix] is not
268+
recognised. ``loose`` will attempt to parse the [suffix] but just
269+
pass it through as part of the issue title if not recognised. ``none``
270+
will always pass any [suffix] through as part of the issue title.
271+
272+
subject_suffix_delimiters -- ``[]``
273+
Defines the brackets used for delimiting the commands suffix in a subject
274+
line.
275+
276+
subject_content_match -- ``always``
277+
Controls matching of the incoming email subject line against issue titles
278+
in the case where there is no designator [prefix]. ``never`` turns off
279+
matching. ``creation + interval`` or ``activity + interval`` will match
280+
an issue for the interval after the issue's creation or last activity.
281+
The interval is a standard Roundup interval.
282+
258283
Section **nosy**
259284
Nosy messages sending
260285

@@ -280,6 +305,11 @@ Section **nosy**
280305
on followups too. If ``no``, they're never added to the nosy.
281306
Allowed values: ``yes``, ``no``, ``new``
282307

308+
email_sending -- ``single``
309+
Controls the email sending from the nosy reactor. If ``multiple`` then
310+
a separate email is sent to each recipient. If ``single`` then a single
311+
email is sent with each recipient as a CC address.
312+
283313
You may generate a new default config file using the ``roundup-admin
284314
genconfig`` command.
285315

doc/installation.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ An RDBMS
5656
Xapian full-text indexer
5757
The Xapian_ full-text indexer is also supported and will be used by
5858
default if it is available. This is strongly recommended if you are
59-
anticipating a large number of issues (> 1000).
59+
anticipating a large number of issues (> 5000).
6060

6161
You may install Xapian at any time, even after a tracker has been
6262
installed and used. You will need to run the "roundup-admin reindex"
@@ -246,6 +246,10 @@ create your tracker on.
246246
Choosing Your Backend
247247
---------------------
248248

249+
.. note::
250+
251+
The tsearch2 backend is experimental and should not be selected.
252+
249253
The actual storage of Roundup tracker information is handled by backends.
250254
There's several to choose from, each with benefits and limitations:
251255

roundup/configuration.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Roundup Issue Tracker configuration support
22
#
3-
# $Id: configuration.py,v 1.26 2005-06-24 05:22:03 richard Exp $
3+
# $Id: configuration.py,v 1.27 2005-10-07 04:42:13 richard Exp $
44
#
55
__docformat__ = "restructuredtext"
66

@@ -569,6 +569,33 @@ class NullableFilePathOption(NullableOption, FilePathOption):
569569
"Default locale name for the tracker mail gateway.\n"
570570
"If this option is not set, mail gateway will use\n"
571571
"the language of the tracker instance."),
572+
(Option, "subject_prefix_parsing", "strict",
573+
"Controls the parsing of the [prefix] on subject\n"
574+
"lines in incoming emails. \"strict\" will return an\n"
575+
"error to the sender if the [prefix] is not recognised.\n"
576+
"\"loose\" will attempt to parse the [prefix] but just\n"
577+
"pass it through as part of the issue title if not\n"
578+
"recognised. \"none\" will always pass any [prefix]\n"
579+
"through as part of the issue title."),
580+
(Option, "subject_suffix_parsing", "strict",
581+
"Controls the parsing of the [suffix] on subject\n"
582+
"lines in incoming emails. \"strict\" will return an\n"
583+
"error to the sender if the [suffix] is not recognised.\n"
584+
"\"loose\" will attempt to parse the [suffix] but just\n"
585+
"pass it through as part of the issue title if not\n"
586+
"recognised. \"none\" will always pass any [suffix]\n"
587+
"through as part of the issue title."),
588+
(Option, "subject_suffix_delimiters", "[]",
589+
"Defines the brackets used for delimiting the commands\n"
590+
"suffix in a subject line."),
591+
(Option, "subject_content_match", "always",
592+
"Controls matching of the incoming email subject line\n"
593+
"against issue titles in the case where there is no\n"
594+
"designator [prefix]. \"never\" turns off matching.\n"
595+
"\"creation + interval\" or \"activity + interval\"\n"
596+
"will match an issue for the interval after the issue's\n"
597+
"creation or last activity. The interval is a standard\n"
598+
"Roundup interval."),
572599
), "Roundup Mail Gateway options"),
573600
("nosy", (
574601
(RunDetectorOption, "messages_to_author", "no",
@@ -592,6 +619,11 @@ class NullableFilePathOption(NullableOption, FilePathOption):
592619
"If 'yes', then the recipients will be added on followups too.\n"
593620
"If 'no', they're never added to the nosy.\n",
594621
["ADD_RECIPIENTS_TO_NOSY"]),
622+
(Option, "email_sending", "single",
623+
"Controls the email sending from the nosy reactor. If\n"
624+
"\"multiple\" then a separate email is sent to each\n"
625+
"recipient. If \"single\" then a single email is sent with\n"
626+
"each recipient as a CC address."),
595627
), "Nosy messages sending"),
596628
)
597629

roundup/mailgw.py

Lines changed: 99 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class node. Any parts of other types are each stored in separate files
7272
an exception, the original message is bounced back to the sender with the
7373
explanatory message given in the exception.
7474
75-
$Id: mailgw.py,v 1.167 2005-09-28 05:48:23 richard Exp $
75+
$Id: mailgw.py,v 1.168 2005-10-07 04:42:13 richard Exp $
7676
"""
7777
__docformat__ = 'restructuredtext'
7878

@@ -297,19 +297,6 @@ def as_attachment(self):
297297

298298
class MailGW:
299299

300-
# Matches subjects like:
301-
# Re: "[issue1234] title of issue [status=resolved]"
302-
subject_re = re.compile(r'''
303-
(?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*\s* # Re:
304-
(?P<quote>")? # Leading "
305-
(\[(?P<classname>[^\d\s]+) # [issue..
306-
(?P<nodeid>\d+)? # ..1234]
307-
\])?\s*
308-
(?P<title>[^[]+)? # issue title
309-
"? # Trailing "
310-
(\[(?P<args>.+?)\])? # [prop=value]
311-
''', re.IGNORECASE|re.VERBOSE)
312-
313300
def __init__(self, instance, db, arguments=()):
314301
self.instance = instance
315302
self.db = db
@@ -611,6 +598,23 @@ def handle_message(self, message):
611598
# make local variable for easier access
612599
config = self.instance.config
613600

601+
# determine the sender's address
602+
from_list = message.getaddrlist('resent-from')
603+
if not from_list:
604+
from_list = message.getaddrlist('from')
605+
606+
# check for registration OTK
607+
# or fallback on the default class
608+
otk_re = re.compile('-- key (?P<otk>[a-zA-Z0-9]{32})')
609+
otk = otk_re.search(subject)
610+
if otk:
611+
self.db.confirm_registration(otk.group('otk'))
612+
subject = 'Your registration to %s is complete' % \
613+
config['TRACKER_NAME']
614+
sendto = [from_list[0][1]]
615+
self.mailer.standard_message(sendto, subject, '')
616+
return
617+
614618
# XXX Don't enable. This doesn't work yet.
615619
# "[^A-z.]tracker\+(?P<classname>[^\d\s]+)(?P<nodeid>\d+)\@some.dom.ain[^A-z.]"
616620
# handle delivery to addresses like:[email protected]
@@ -627,37 +631,45 @@ def handle_message(self, message):
627631
# nodeid = issue.group('nodeid')
628632
# break
629633

630-
# determine the sender's address
631-
from_list = message.getaddrlist('resent-from')
632-
if not from_list:
633-
from_list = message.getaddrlist('from')
634+
# Matches subjects like:
635+
# Re: "[issue1234] title of issue [status=resolved]"
636+
open, close = config['MAILGW_SUBJECT_SUFFIX_DELIMITERS']
637+
delim_open = re.escape(open)
638+
delim_close = re.escape(close)
639+
subject_re = re.compile(r'''
640+
(?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*\s* # Re:
641+
(?P<quote>")? # Leading "
642+
(\[(?P<classname>[^\d\s]+) # [issue..
643+
(?P<nodeid>\d+)? # ..1234]
644+
\])?\s*
645+
(?P<title>[^%s]+)? # issue title
646+
"? # Trailing "
647+
(?P<argswhole>%s(?P<args>.+?)%s)? # [prop=value]
648+
'''%(delim_open, delim_open, delim_close),
649+
re.IGNORECASE|re.VERBOSE)
650+
651+
# figure subject line parsing modes
652+
pfxmode = config['MAILGW_SUBJECT_PREFIX_PARSING']
653+
sfxmode = config['MAILGW_SUBJECT_SUFFIX_PARSING']
634654

635655
# check for well-formed subject line
636-
m = self.subject_re.match(subject)
656+
m = subject_re.match(subject)
637657
if m:
638658
# get the classname
639-
classname = m.group('classname')
659+
if pfxmode == 'none':
660+
classname = None
661+
else:
662+
classname = m.group('classname')
640663
if classname is None:
641-
# no classname, check if this a registration confirmation email
642-
# or fallback on the default class
643-
otk_re = re.compile('-- key (?P<otk>[a-zA-Z0-9]{32})')
644-
otk = otk_re.search(m.group('title'))
645-
if otk:
646-
self.db.confirm_registration(otk.group('otk'))
647-
subject = 'Your registration to %s is complete' % \
648-
config['TRACKER_NAME']
649-
sendto = [from_list[0][1]]
650-
self.mailer.standard_message(sendto, subject, '')
651-
return
652-
elif self.default_class:
664+
if self.default_class:
653665
classname = self.default_class
654666
else:
655667
classname = config['MAILGW_DEFAULT_CLASS']
656668
if not classname:
657669
# fail
658670
m = None
659671

660-
if not m:
672+
if not m and pfxmode == 'strict':
661673
raise MailUsageError, """
662674
The message you sent to roundup did not contain a properly formed subject
663675
line. The subject must contain a class name or designator to indicate the
@@ -672,10 +684,22 @@ def handle_message(self, message):
672684
Subject was: '%s'
673685
"""%subject
674686

675-
# get the class
676-
try:
677-
cl = self.db.getclass(classname)
678-
except KeyError:
687+
# try to get the class specified - if "loose" then fall back on the
688+
# default
689+
attempts = [classname]
690+
if pfxmode == 'loose':
691+
if self.default_class:
692+
attempts.append(self.default_class)
693+
else:
694+
attempts.append(config['MAILGW_DEFAULT_CLASS'])
695+
cl = None
696+
for trycl in attempts:
697+
try:
698+
cl = self.db.getclass(classname)
699+
break
700+
except KeyError:
701+
pass
702+
if not cl:
679703
raise MailUsageError, '''
680704
The class name you identified in the subject line ("%s") does not exist in the
681705
database.
@@ -685,7 +709,10 @@ def handle_message(self, message):
685709
'''%(classname, ', '.join(self.db.getclasses()), subject)
686710

687711
# get the optional nodeid
688-
nodeid = m.group('nodeid')
712+
if pfxmode == 'none':
713+
nodeid = None
714+
else:
715+
nodeid = m.group('nodeid')
689716

690717
# title is optional too
691718
title = m.group('title')
@@ -703,7 +730,7 @@ def handle_message(self, message):
703730
if nodeid is None and not title:
704731
raise MailUsageError, '''
705732
I cannot match your message to a node in the database - you need to either
706-
supply a full node identifier (with number, eg "[issue123]" or keep the
733+
supply a full designator (with number, eg "[issue123]" or keep the
707734
previous subject title intact so I can match that.
708735
709736
Subject was: "%s"
@@ -713,20 +740,36 @@ def handle_message(self, message):
713740
# maybe someone's responded to the initial mail that created an
714741
# entry. Try to find the matching nodes with the same title, and
715742
# use the _last_ one matched (since that'll _usually_ be the most
716-
# recent...)
717-
if nodeid is None and m.group('refwd'):
743+
# recent...). The subject_content_match config may specify an
744+
# additional restriction based on the matched node's creation or
745+
# activity.
746+
tmatch_mode = config['MAILGW_SUBJECT_CONTENT_MATCH']
747+
if tmatch_mode != 'never' and nodeid is None and m.group('refwd'):
718748
l = cl.stringFind(title=title)
719-
if l:
720-
nodeid = l[-1]
749+
limit = None
750+
if (tmatch_mode.startswith('creation') or
751+
tmatch_mode.startswith('activity')):
752+
limit, interval = tmatch_mode.split(' ', 1)
753+
threshold = date.Date('.') - date.Interval(interval)
754+
for id in l:
755+
if limit:
756+
if threshold < cl.get(id, limit):
757+
nodeid = id
758+
else:
759+
nodeid = id
721760

722761
# if a nodeid was specified, make sure it's valid
723762
if nodeid is not None and not cl.hasnode(nodeid):
724-
raise MailUsageError, '''
763+
if pfxmode == 'strict':
764+
raise MailUsageError, '''
725765
The node specified by the designator in the subject of your message ("%s")
726766
does not exist.
727767
728768
Subject was: "%s"
729769
'''%(nodeid, subject)
770+
else:
771+
title = subject
772+
nodeid = None
730773

731774
# Handle the arguments specified by the email gateway command line.
732775
# We do this by looping over the list of self.arguments looking for
@@ -844,17 +887,24 @@ def handle_message(self, message):
844887
properties = cl.getprops()
845888
props = {}
846889
args = m.group('args')
890+
argswhole = m.group('argswhole')
847891
if args:
848-
errors, props = setPropArrayFromString(self, cl, args, nodeid)
849-
# handle any errors parsing the argument list
850-
if errors:
851-
errors = '\n- '.join(map(str, errors))
852-
raise MailUsageError, '''
892+
if sfxmode == 'none':
893+
title += ' ' + argswhole
894+
else:
895+
errors, props = setPropArrayFromString(self, cl, args, nodeid)
896+
# handle any errors parsing the argument list
897+
if errors:
898+
if sfxmode == 'strict':
899+
errors = '\n- '.join(map(str, errors))
900+
raise MailUsageError, '''
853901
There were problems handling your subject line argument list:
854902
- %s
855903
856904
Subject was: "%s"
857905
'''%(errors, subject)
906+
else:
907+
title += ' ' + argswhole
858908

859909

860910
# set the issue title to the subject

0 commit comments

Comments
 (0)