]> git.ozlabs.org Git - patchwork/blobdiff - apps/patchwork/bin/parsemail.py
[parser] Don't remove --- update lines
[patchwork] / apps / patchwork / bin / parsemail.py
index d0993acfc2f641a790c08f35917cbb669111351b..b0f1497656ab8ac4170c93f34975454b4f9b0fad 100755 (executable)
@@ -25,10 +25,15 @@ import datetime
 import time
 import operator
 from email import message_from_file
-from email.header import Header
-from email.utils import parsedate_tz, mktime_tz
-
-from patchparser import parse_patch
+try:
+    from email.header import Header
+    from email.utils import parsedate_tz, mktime_tz
+except ImportError:
+    # Python 2.4 compatibility
+    from email.Header import Header
+    from email.Utils import parsedate_tz, mktime_tz
+
+from patchwork.parser import parse_patch
 from patchwork.models import Patch, Project, Person, Comment
 
 list_id_headers = ['List-ID', 'X-Mailing-List']
@@ -98,7 +103,6 @@ def find_author(mail):
 def mail_date(mail):
     t = parsedate_tz(mail.get('Date', ''))
     if not t:
-        print "using now()"
         return datetime.datetime.utcnow()
     return datetime.datetime.utcfromtimestamp(mktime_tz(t))
 
@@ -144,14 +148,14 @@ def find_content(project, mail):
 
     if patchbuf:
         mail_headers(mail)
-        patch = Patch(name = clean_subject(mail.get('Subject')),
-                content = patchbuf, date = mail_date(mail),
-                headers = mail_headers(mail))
+       name = clean_subject(mail.get('Subject'), [project.linkname])
+        patch = Patch(name = name, content = patchbuf,
+                    date = mail_date(mail), headers = mail_headers(mail))
 
     if commentbuf:
         if patch:
-           cpatch = patch
-       else:
+            cpatch = patch
+        else:
             cpatch = find_patch_for_comment(mail)
             if not cpatch:
                 return (None, None)
@@ -194,18 +198,109 @@ def find_patch_for_comment(mail):
 
     return None
 
+split_re = re.compile('[,\s]+')
+
+def split_prefixes(prefix):
+    """ Turn a prefix string into a list of prefix tokens
+
+    >>> split_prefixes('PATCH')
+    ['PATCH']
+    >>> split_prefixes('PATCH,RFC')
+    ['PATCH', 'RFC']
+    >>> split_prefixes('')
+    []
+    >>> split_prefixes('PATCH,')
+    ['PATCH']
+    >>> split_prefixes('PATCH ')
+    ['PATCH']
+    >>> split_prefixes('PATCH,RFC')
+    ['PATCH', 'RFC']
+    >>> split_prefixes('PATCH 1/2')
+    ['PATCH', '1/2']
+    """
+    matches = split_re.split(prefix)
+    return [ s for s in matches if s != '' ]
+
 re_re = re.compile('^(re|fwd?)[:\s]\s*', re.I)
-prefix_re = re.compile('^\[.*\]\s*')
+prefix_re = re.compile('^\[([^\]]*)\]\s*(.*)$')
 whitespace_re = re.compile('\s+')
 
-def clean_subject(subject):
+def clean_subject(subject, drop_prefixes = None):
+    """ Clean a Subject: header from an incoming patch.
+
+    Removes Re: and Fwd: strings, as well as [PATCH]-style prefixes. By
+    default, only [PATCH] is removed, and we keep any other bracketed data
+    in the subject. If drop_prefixes is provided, remove those too,
+    comparing case-insensitively.
+
+    >>> clean_subject('meep')
+    'meep'
+    >>> clean_subject('Re: meep')
+    'meep'
+    >>> clean_subject('[PATCH] meep')
+    'meep'
+    >>> clean_subject('[PATCH] meep \\n meep')
+    'meep meep'
+    >>> clean_subject('[PATCH RFC] meep')
+    '[RFC] meep'
+    >>> clean_subject('[PATCH,RFC] meep')
+    '[RFC] meep'
+    >>> clean_subject('[PATCH,1/2] meep')
+    '[1/2] meep'
+    >>> clean_subject('[PATCH RFC 1/2] meep')
+    '[RFC,1/2] meep'
+    >>> clean_subject('[PATCH] [RFC] meep')
+    '[RFC] meep'
+    >>> clean_subject('[PATCH] [RFC,1/2] meep')
+    '[RFC,1/2] meep'
+    >>> clean_subject('[PATCH] [RFC] [1/2] meep')
+    '[RFC,1/2] meep'
+    >>> clean_subject('[PATCH] rewrite [a-z] regexes')
+    'rewrite [a-z] regexes'
+    >>> clean_subject('[PATCH] [RFC] rewrite [a-z] regexes')
+    '[RFC] rewrite [a-z] regexes'
+    >>> clean_subject('[foo] [bar] meep', ['foo'])
+    '[bar] meep'
+    >>> clean_subject('[FOO] [bar] meep', ['foo'])
+    '[bar] meep'
+    """
+
+    if drop_prefixes is None:
+        drop_prefixes = []
+    else:
+        drop_prefixes = [ s.lower() for s in drop_prefixes ]
+
+    drop_prefixes.append('patch')
+
+    # remove Re:, Fwd:, etc
     subject = re_re.sub(' ', subject)
-    subject = prefix_re.sub('', subject)
+
+    # normalise whitespace
+    subject = whitespace_re.sub(' ', subject)
+
+    prefixes = []
+
+    match = prefix_re.match(subject)
+
+    while match:
+        prefix_str = match.group(1)
+        prefixes += [ p for p in split_prefixes(prefix_str) \
+                        if p.lower() not in drop_prefixes]
+
+        subject = match.group(2)
+        match = prefix_re.match(subject)
+
     subject = whitespace_re.sub(' ', subject)
-    return subject.strip()
 
-sig_re = re.compile('^(-{2,3} ?|_+)\n.*', re.S | re.M)
+    subject = subject.strip()
+    if prefixes:
+        subject = '[%s] %s' % (','.join(prefixes), subject)
+
+    return subject
+
+sig_re = re.compile('^(-- |_+)\n.*', re.S | re.M)
 def clean_content(str):
+    """ Try to remove signature (-- ) and list footer (_____) cruft """
     str = sig_re.sub('', str)
     return str.strip()
 
@@ -248,7 +343,7 @@ def main(args):
         try:
             patch.save()
         except Exception, ex:
-            print ex.message
+            print str(ex)
 
     if comment:
         if save_required:
@@ -262,7 +357,7 @@ def main(args):
         try:
             comment.save()
         except Exception, ex:
-            print ex.message
+            print str(ex)
 
     return 0