[Buildbot-commits] buildbot/buildbot/changes mail.py,1.24,1.25

Brian Warner warner at users.sourceforge.net
Sat Jul 28 05:57:52 UTC 2007


Update of /cvsroot/buildbot/buildbot/buildbot/changes
In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv28244/buildbot/changes

Modified Files:
	mail.py 
Log Message:
[project @ changes/mail.py: refactor, giving each parser a separate subclass]

Original author: warner at lothar.com
Date: 2007-07-28 05:38:27+00:00

Index: mail.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/changes/mail.py,v
retrieving revision 1.24
retrieving revision 1.25
diff -u -d -r1.24 -r1.25
--- mail.py	23 Jan 2007 21:04:32 -0000	1.24
+++ mail.py	28 Jul 2007 05:57:50 -0000	1.25
@@ -12,197 +12,228 @@
 from buildbot.changes import changes
 from buildbot.changes.maildir import MaildirService
 
-def parseFreshCVSMail(self, fd, prefix=None, sep="/"):
-    """Parse mail sent by FreshCVS"""
-    # this uses rfc822.Message so it can run under python2.1 . In the future
-    # it will be updated to use python2.2's "email" module.
+class MaildirSource(MaildirService, util.ComparableMixin):
+    """This source will watch a maildir that is subscribed to a FreshCVS
+    change-announcement mailing list.
+    """
+    implements(IChangeSource)
 
-    m = Message(fd)
-    # FreshCVS sets From: to "user CVS <user>", but the <> part may be
-    # modified by the MTA (to include a local domain)
-    name, addr = m.getaddr("from")
-    if not name:
-        return None # no From means this message isn't from FreshCVS
-    cvs = name.find(" CVS")
-    if cvs == -1:
-        return None # this message isn't from FreshCVS
-    who = name[:cvs]
+    compare_attrs = ["basedir", "pollinterval"]
+    name = None
 
-    # we take the time of receipt as the time of checkin. Not correct, but it
-    # avoids the out-of-order-changes issue. See the comment in parseSyncmail
-    # about using the 'Date:' header
-    when = util.now()
+    def __init__(self, maildir, prefix=None):
+        MaildirService.__init__(self, maildir)
+        self.prefix = prefix
 
-    files = []
-    comments = ""
-    isdir = 0
-    lines = m.fp.readlines()
-    while lines:
-        line = lines.pop(0)
-        if line == "Modified files:\n":
-            break
-    while lines:
-        line = lines.pop(0)
-        if line == "\n":
-            break
-        line = line.rstrip("\n")
-        linebits = line.split(None, 1)
-        file = linebits[0]
-        if prefix:
-            # insist that the file start with the prefix: FreshCVS sends
-            # changes we don't care about too
-            bits = file.split(sep)
-            if bits[0] == prefix:
-                file = sep.join(bits[1:])
-            else:
+    def describe(self):
+        return "%s mailing list in maildir %s" % (self.name, self.basedir)
+
+    def messageReceived(self, filename):
+        path = os.path.join(self.basedir, "new", filename)
+        change = self.parse(open(path, "r"), self.prefix)
+        if change:
+            self.parent.addChange(change)
+        os.rename(os.path.join(self.basedir, "new", filename),
+                  os.path.join(self.basedir, "cur", filename))
+
+class FCMaildirSource(MaildirSource):
+    name = "FreshCVS"
+
+    def parse(self, fd, prefix=None, sep="/"):
+        """Parse mail sent by FreshCVS"""
+        # this uses rfc822.Message so it can run under python2.1 . In the
+        # future it will be updated to use python2.2's "email" module.
+
+        m = Message(fd)
+        # FreshCVS sets From: to "user CVS <user>", but the <> part may be
+        # modified by the MTA (to include a local domain)
+        name, addr = m.getaddr("from")
+        if not name:
+            return None # no From means this message isn't from FreshCVS
+        cvs = name.find(" CVS")
+        if cvs == -1:
+            return None # this message isn't from FreshCVS
+        who = name[:cvs]
+
+        # we take the time of receipt as the time of checkin. Not correct,
+        # but it avoids the out-of-order-changes issue. See the comment in
+        # parseSyncmail about using the 'Date:' header
+        when = util.now()
+
+        files = []
+        comments = ""
+        isdir = 0
+        lines = m.fp.readlines()
+        while lines:
+            line = lines.pop(0)
+            if line == "Modified files:\n":
                 break
-        if len(linebits) == 1:
-            isdir = 1
-        elif linebits[1] == "0 0":
-            isdir = 1
-        files.append(file)
-    while lines:
-        line = lines.pop(0)
-        if line == "Log message:\n":
-            break
-    # message is terminated by "ViewCVS links:" or "Index:..." (patch)
-    while lines:
-        line = lines.pop(0)
-        if line == "ViewCVS links:\n":
-            break
-        if line.find("Index: ") == 0:
-            break
-        comments += line
-    comments = comments.rstrip() + "\n"
+        while lines:
+            line = lines.pop(0)
+            if line == "\n":
+                break
+            line = line.rstrip("\n")
+            linebits = line.split(None, 1)
+            file = linebits[0]
+            if prefix:
+                # insist that the file start with the prefix: FreshCVS sends
+                # changes we don't care about too
+                bits = file.split(sep)
+                if bits[0] == prefix:
+                    file = sep.join(bits[1:])
+                else:
+                    break
+            if len(linebits) == 1:
+                isdir = 1
+            elif linebits[1] == "0 0":
+                isdir = 1
+            files.append(file)
+        while lines:
+            line = lines.pop(0)
+            if line == "Log message:\n":
+                break
+        # message is terminated by "ViewCVS links:" or "Index:..." (patch)
+        while lines:
+            line = lines.pop(0)
+            if line == "ViewCVS links:\n":
+                break
+            if line.find("Index: ") == 0:
+                break
+            comments += line
+        comments = comments.rstrip() + "\n"
 
-    if not files:
-        return None
-    
-    change = changes.Change(who, files, comments, isdir, when=when)
+        if not files:
+            return None
 
-    return change
+        change = changes.Change(who, files, comments, isdir, when=when)
 
-def parseSyncmail(self, fd, prefix=None, sep="/"):
-    """Parse messages sent by the 'syncmail' program, as suggested by the
-    sourceforge.net CVS Admin documentation. Syncmail is maintained at
-    syncmail.sf.net .
-    """
-    # pretty much the same as freshcvs mail, not surprising since CVS is the
-    # one creating most of the text
+        return change
 
-    m = Message(fd)
-    # The mail is sent from the person doing the checkin. Assume that the
-    # local username is enough to identify them (this assumes a one-server
-    # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS
-    # model)
-    name, addr = m.getaddr("from")
-    if not addr:
-        return None # no From means this message isn't from FreshCVS
-    at = addr.find("@")
-    if at == -1:
-        who = addr # might still be useful
-    else:
-        who = addr[:at]
+class SyncmailMaildirSource(MaildirSource):
+    name = "Syncmail"
 
-    # we take the time of receipt as the time of checkin. Not correct (it
-    # depends upon the email latency), but it avoids the out-of-order-changes
-    # issue. Also syncmail doesn't give us anything better to work with,
-    # unless you count pulling the v1-vs-v2 timestamp out of the diffs, which
-    # would be ugly. TODO: Pulling the 'Date:' header from the mail is a
-    # possibility, and email.Utils.parsedate_tz may be useful. It should be
-    # configurable, however, because there are a lot of broken clocks out
-    # there.
-    when = util.now()
+    def parse(self, fd, prefix=None, sep="/"):
+        """Parse messages sent by the 'syncmail' program, as suggested by the
+        sourceforge.net CVS Admin documentation. Syncmail is maintained at
+        syncmail.sf.net .
+        """
+        # pretty much the same as freshcvs mail, not surprising since CVS is
+        # the one creating most of the text
 
-    subject = m.getheader("subject")
-    # syncmail puts the repository-relative directory in the subject:
-    # mprefix + "%(dir)s %(file)s,%(oldversion)s,%(newversion)s", where
-    # 'mprefix' is something that could be added by a mailing list
-    # manager.
-    # this is the only reasonable way to determine the directory name
-    space = subject.find(" ")
-    if space != -1:
-        directory = subject[:space]
-    else:
-        directory = subject
-    
-    files = []
-    comments = ""
-    isdir = 0
-    branch = None
+        m = Message(fd)
+        # The mail is sent from the person doing the checkin. Assume that the
+        # local username is enough to identify them (this assumes a one-server
+        # cvs-over-rsh environment rather than the server-dirs-shared-over-NFS
+        # model)
+        name, addr = m.getaddr("from")
+        if not addr:
+            return None # no From means this message isn't from FreshCVS
+        at = addr.find("@")
+        if at == -1:
+            who = addr # might still be useful
+        else:
+            who = addr[:at]
 
-    lines = m.fp.readlines()
-    while lines:
-        line = lines.pop(0)
+        # we take the time of receipt as the time of checkin. Not correct (it
+        # depends upon the email latency), but it avoids the
+        # out-of-order-changes issue. Also syncmail doesn't give us anything
+        # better to work with, unless you count pulling the v1-vs-v2
+        # timestamp out of the diffs, which would be ugly. TODO: Pulling the
+        # 'Date:' header from the mail is a possibility, and
+        # email.Utils.parsedate_tz may be useful. It should be configurable,
+        # however, because there are a lot of broken clocks out there.
+        when = util.now()
 
-        if (line == "Modified Files:\n" or
-            line == "Added Files:\n" or
-            line == "Removed Files:\n"):
-            break
+        subject = m.getheader("subject")
+        # syncmail puts the repository-relative directory in the subject:
+        # mprefix + "%(dir)s %(file)s,%(oldversion)s,%(newversion)s", where
+        # 'mprefix' is something that could be added by a mailing list
+        # manager.
+        # this is the only reasonable way to determine the directory name
+        space = subject.find(" ")
+        if space != -1:
+            directory = subject[:space]
+        else:
+            directory = subject
 
-    while lines:
-        line = lines.pop(0)
-        if line == "\n":
-            break
-        if line == "Log Message:\n":
-            lines.insert(0, line)
-            break
-        line = line.lstrip()
-        line = line.rstrip()
-        # note: syncmail will send one email per directory involved in a
-        # commit, with multiple files if they were in the same directory.
-        # Unlike freshCVS, it makes no attempt to collect all related
-        # commits into a single message.
+        files = []
+        comments = ""
+        isdir = 0
+        branch = None
 
-        # note: syncmail will report a Tag underneath the ... Files: line
-        # e.g.:       Tag: BRANCH-DEVEL
+        lines = m.fp.readlines()
+        while lines:
+            line = lines.pop(0)
 
-        if line.startswith('Tag:'):
-            branch = line.split(' ')[-1].rstrip()
-            continue
+            if (line == "Modified Files:\n" or
+                line == "Added Files:\n" or
+                line == "Removed Files:\n"):
+                break
 
-        # note: it doesn't actually make sense to use portable functions
-        # like os.path.join and os.sep, because these filenames all use
-        # separator conventions established by the remote CVS server (which
-        # is probably running on unix), not the local buildmaster system.
-        thesefiles = line.split(" ")
-        for f in thesefiles:
-            f = sep.join([directory, f])
-            if prefix:
-                # insist that the file start with the prefix: we may get
-                # changes we don't care about too
-                bits = f.split(sep)
-                if bits[0] == prefix:
-                    f = sep.join(bits[1:])
-                else:
-                    break
-            # TODO: figure out how new directories are described, set .isdir
-            files.append(f)
+        while lines:
+            line = lines.pop(0)
+            if line == "\n":
+                break
+            if line == "Log Message:\n":
+                lines.insert(0, line)
+                break
+            line = line.lstrip()
+            line = line.rstrip()
+            # note: syncmail will send one email per directory involved in a
+            # commit, with multiple files if they were in the same directory.
+            # Unlike freshCVS, it makes no attempt to collect all related
+            # commits into a single message.
 
-    if not files:
-        return None
+            # note: syncmail will report a Tag underneath the ... Files: line
+            # e.g.:       Tag: BRANCH-DEVEL
 
-    while lines:
-        line = lines.pop(0)
-        if line == "Log Message:\n":
-            break
-    # message is terminated by "Index:..." (patch) or "--- NEW FILE.."
-    # or "--- filename DELETED ---". Sigh.
-    while lines:
-        line = lines.pop(0)
-        if line.find("Index: ") == 0:
-            break
-        if re.search(r"^--- NEW FILE", line):
-            break
-        if re.search(r" DELETED ---$", line):
-            break
-        comments += line
-    comments = comments.rstrip() + "\n"
-    
-    change = changes.Change(who, files, comments, isdir, when=when,
-                            branch=branch)
+            if line.startswith('Tag:'):
+                branch = line.split(' ')[-1].rstrip()
+                continue
 
-    return change
+            # note: it doesn't actually make sense to use portable functions
+            # like os.path.join and os.sep, because these filenames all use
+            # separator conventions established by the remote CVS server (which
+            # is probably running on unix), not the local buildmaster system.
+            thesefiles = line.split(" ")
+            for f in thesefiles:
+                f = sep.join([directory, f])
+                if prefix:
+                    # insist that the file start with the prefix: we may get
+                    # changes we don't care about too
+                    bits = f.split(sep)
+                    if bits[0] == prefix:
+                        f = sep.join(bits[1:])
+                    else:
+                        break
+                # TODO: figure out how new directories are described, set
+                # .isdir
+                files.append(f)
+
+        if not files:
+            return None
+
+        while lines:
+            line = lines.pop(0)
+            if line == "Log Message:\n":
+                break
+        # message is terminated by "Index:..." (patch) or "--- NEW FILE.."
+        # or "--- filename DELETED ---". Sigh.
+        while lines:
+            line = lines.pop(0)
+            if line.find("Index: ") == 0:
+                break
+            if re.search(r"^--- NEW FILE", line):
+                break
+            if re.search(r" DELETED ---$", line):
+                break
+            comments += line
+        comments = comments.rstrip() + "\n"
+
+        change = changes.Change(who, files, comments, isdir, when=when,
+                                branch=branch)
+
+        return change
 
 # Bonsai mail parser by Stephen Davis.
 #
@@ -231,106 +262,72 @@
 # actually make a new directory part of the build process. That's my story
 # and I'm sticking to it.
 
-def parseBonsaiMail(self, fd, prefix=None, sep="/"):
-    """Parse mail sent by the Bonsai cvs loginfo script."""
-
-    msg = Message(fd)
-
-    # we don't care who the email came from b/c the cvs user is in the msg
-    # text
-    
-    who = "unknown"
-    timestamp = None
-    files = []
-    lines = msg.fp.readlines()
-
-    # read the control lines (what/who/where/file/etc.)
-    while lines:
-        line = lines.pop(0)
-        if line == "LOGCOMMENT\n":
-            break;
-        line = line.rstrip("\n")
-        
-        # we'd like to do the following but it won't work if the number of
-        # items doesn't match so...
-        #   what, timestamp, user, repo, module, file = line.split( '|' )
-        items = line.split('|')
-        if len(items) < 6:
-            # not a valid line, assume this isn't a bonsai message
-            return None
-
-        try:
-            # just grab the bottom-most timestamp, they're probably all the
-            # same. TODO: I'm assuming this is relative to the epoch, but
-            # this needs testing.
-            timestamp = int(items[1])
-        except ValueError:
-            pass
-
-        user = items[2]
-        if user:
-            who = user
+class BonsaiMaildirSource(MaildirSource):
+    name = "Bonsai"
 
-        module = items[4]
-        file = items[5]
-        if module and file:
-            path = "%s/%s" % (module, file)
-            files.append(path)
-        sticky = items[7]
-        branch = items[8]
+    def parse(self, fd, prefix=None, sep="/"):
+        """Parse mail sent by the Bonsai cvs loginfo script."""
 
-    # if no files changed, return nothing
-    if not files:
-        return None
+        msg = Message(fd)
 
-    # read the comments
-    comments = ""
-    while lines:
-        line = lines.pop(0)
-        if line == ":ENDLOGCOMMENT\n":
-            break
-        comments += line
-    comments = comments.rstrip() + "\n"
+        # we don't care who the email came from b/c the cvs user is in the
+        # msg text
 
-    # return buildbot Change object
-    return changes.Change(who, files, comments, when=timestamp, branch=branch)
+        who = "unknown"
+        timestamp = None
+        files = []
+        lines = msg.fp.readlines()
 
+        # read the control lines (what/who/where/file/etc.)
+        while lines:
+            line = lines.pop(0)
+            if line == "LOGCOMMENT\n":
+                break;
+            line = line.rstrip("\n")
 
+            # we'd like to do the following but it won't work if the number of
+            # items doesn't match so...
+            #   what, timestamp, user, repo, module, file = line.split( '|' )
+            items = line.split('|')
+            if len(items) < 6:
+                # not a valid line, assume this isn't a bonsai message
+                return None
 
-class MaildirSource(MaildirService, util.ComparableMixin):
-    """This source will watch a maildir that is subscribed to a FreshCVS
-    change-announcement mailing list.
-    """
-    implements(IChangeSource)
+            try:
+                # just grab the bottom-most timestamp, they're probably all the
+                # same. TODO: I'm assuming this is relative to the epoch, but
+                # this needs testing.
+                timestamp = int(items[1])
+            except ValueError:
+                pass
 
-    compare_attrs = ["basedir", "pollinterval", "parser"]
-    parser = None
-    name = None
+            user = items[2]
+            if user:
+                who = user
 
-    def __init__(self, maildir, prefix=None, sep="/"):
-        MaildirService.__init__(self, maildir)
-        self.prefix = prefix
-        self.sep = sep
+            module = items[4]
+            file = items[5]
+            if module and file:
+                path = "%s/%s" % (module, file)
+                files.append(path)
+            sticky = items[7]
+            branch = items[8]
 
-    def describe(self):
-        return "%s mailing list in maildir %s" % (self.name, self.basedir)
+        # if no files changed, return nothing
+        if not files:
+            return None
 
-    def messageReceived(self, filename):
-        path = os.path.join(self.basedir, "new", filename)
-        change = self.parser(open(path, "r"), self.prefix, self.sep)
-        if change:
-            self.parent.addChange(change)
-        os.rename(os.path.join(self.basedir, "new", filename),
-                  os.path.join(self.basedir, "cur", filename))
+        # read the comments
+        comments = ""
+        while lines:
+            line = lines.pop(0)
+            if line == ":ENDLOGCOMMENT\n":
+                break
+            comments += line
+        comments = comments.rstrip() + "\n"
 
-class FCMaildirSource(MaildirSource):
-    parser = parseFreshCVSMail
-    name = "FreshCVS"
+        # return buildbot Change object
+        return changes.Change(who, files, comments, when=timestamp,
+                              branch=branch)
 
-class SyncmailMaildirSource(MaildirSource):
-    parser = parseSyncmail
-    name = "Syncmail"
 
-class BonsaiMaildirSource(MaildirSource):
-    parser = parseBonsaiMail
-    name = "Bonsai"





More information about the Commits mailing list