aboutsummaryrefslogtreecommitdiffstats
path: root/interfaces/email
diff options
context:
space:
mode:
Diffstat (limited to 'interfaces/email')
-rwxr-xr-xinterfaces/email/catmutt59
-rw-r--r--interfaces/email/interactive/README105
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail541
l---------interfaces/email/interactive/becommands1
-rw-r--r--interfaces/email/interactive/examples/email_bugs37
-rw-r--r--interfaces/email/interactive/send_pgp_mime.py2
6 files changed, 348 insertions, 397 deletions
diff --git a/interfaces/email/catmutt b/interfaces/email/catmutt
deleted file mode 100755
index 601f14f..0000000
--- a/interfaces/email/catmutt
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/sh
-
-# catmutt - wrap mutt allowing mboxes read from stdin.
-#
-# Copyright (C) 1998-1999 Moritz Barsnick <barsnick (at) gmx (dot) net>,
-# 2009 William Trevor King <wking (at) drexel (dot) edu>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# version 2 as published by the Free Software Foundation.
-#
-# This program 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 this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-#
-# developed from grepm-0.6
-# http://www.barsnick.net/sw/grepm.html
-
-PROGNAME=`basename "$0"`
-export TMPDIR="${TMPDIR-/tmp}" # used by mktemp
-umask 077
-
-if [ $# -gt 0 ] && [ "$1" = "--help" ]; then
- echo 1>&2 "Usage: ${PROGNAME} [--help] mutt-arguments"
- echo 1>&2 ""
- echo 1>&2 "Read a mailbox file from stdin and opens it with mutt."
- echo 1>&2 "For example: cat somefile.mbox | ${PROGNAME}"
- exit 0
-fi
-
-# Note: the -t/-p options to mktemp are deprecated for mktemp (GNU
-# coreutils) 7.1 in favor of --tmpdir but the --tmpdir option does not
-# exist yet for my 6.10-3ubuntu2 coreutils
-TMPFILE=`mktemp -t catmutt.XXXXXX` || exit 1
-
-trap "rm -f ${TMPFILE}; exit 1" 1 2 3 13 15
-
-cat > "${TMPFILE}" || exit 1
-
-# Now that we've read in the mailbox file, reopen stdin for mutt/user
-# interaction. When in a pipe we're not technically in a tty, so use
-# a little hack from "greno" at
-# http://www.linuxforums.org/forum/linux-programming-scripting/98607-bash-stdin-problem.html
-tty="/dev/`ps -p$$ --no-heading | awk '{print $2}'`"
-exec < ${tty}
-
-if [ `wc -c "${TMPFILE}" | awk '{print $1}'` -gt 0 ]; then
- echo 1>&2 "Calling mutt on temporary mailbox file (${TMPFILE})."
- mutt -R -f "${TMPFILE}" "$@"
-else
- echo 1>&2 "Empty mailbox input."
-fi
-
-rm -f "${TMPFILE}" && echo 1>&2 "Deleted temporary mailbox file (${TMPFILE})."
diff --git a/interfaces/email/interactive/README b/interfaces/email/interactive/README
index 79ef9a9..48bccdd 100644
--- a/interfaces/email/interactive/README
+++ b/interfaces/email/interactive/README
@@ -1,16 +1,19 @@
+***************
+Email Interface
+***************
+
Overview
========
The interactive email interface to Bugs Everywhere (BE) attempts to
-provide a Debian-bug-tracking-system-style interface to a BE
+provide a `Debian-bug-tracking-system-style`_ interface to a BE
repository. Users can mail in bug reports, comments, or control
requests, which will be committed to the served repository.
Developers can then pull the changes they approve of from the served
repository into their other repositories and push updates back onto
the served repository.
-For details about the Debian bug tracking system that inspired this
-interface, see http://www.debian.org/Bugs .
+.. _Debian-bug-tracking-system-style: http://www.debian.org/Bugs
Architecture
============
@@ -18,27 +21,34 @@ Architecture
In order to reduce setup costs, the entire interface can piggyback on
an existing email address, although from a security standpoint it's
probably best to create a dedicated user. Incoming email is filtered
-by procmail, with matching emails being piped into be-handle-mail for
-execution.
-
-Once be-handle-mail receives the email, the parsing method is selected
-according to the subject tag that procmail used grab the email in the
-first place. There are three parsing styles:
- Style Subject
- creating bugs [be-bug:submit] new bug summary
- commenting on bugs [be-bug:<bug-id>] commit message
- control [be-bug] commit message
-These are analogous to submit@bugs.debian.org, nnn@bugs.debian.org,
-and control@bugs.debian.org respectively.
+by procmail, with matching emails being piped into ``be-handle-mail``
+for execution.
+
+Once ``be-handle-mail`` receives the email, the parsing method is
+selected according to the subject tag that procmail used grab the
+email in the first place. There are four parsing styles:
+
+ +--------------------+----------------------------------+
+ | Style | Subject |
+ +====================+==================================+
+ | creating bugs | [be-bug:submit] new bug summary |
+ +--------------------+----------------------------------+
+ | commenting on bugs | [be-bug:<bug-id>] commit message |
+ +--------------------+----------------------------------+
+ | control | [be-bug] commit message |
+ +--------------------+----------------------------------+
+
+These are analogous to ``submit@bugs.debian.org``,
+``nnn@bugs.debian.org``, and ``control@bugs.debian.org`` respectively.
Creating bugs
=============
This interface creates a bug whose summary is given by the email's
post-tag subject. The body of the email must begin with a
-pseudo-header containing at least the "Version" field. Anything after
-the pseudo-header and before a line starting with '--' is, if present,
-attached as the bug's first comment.
+pseudo-header containing at least the ``Version`` field. Anything after
+the pseudo-header and before a line starting with ``--`` is, if present,
+attached as the bug's first comment.::
From jdoe@example.com Fri Apr 18 12:00:00 2008
From: John Doe <jdoe@example.com>
@@ -51,22 +61,22 @@ attached as the bug's first comment.
Severity: minor
Someone should write up a series of test emails to send into
- be-handle mail so we can test changes quickly without having to
+ be-handle-mail so we can test changes quickly without having to
use procmail.
--
Goofy tagline not included.
-Available pseudo-headers are Version, Reporter, Assign, Depend,
-Severity, Status, Tag, and Target.
+Available pseudo-headers are ``Version``, ``Reporter``, ``Assign``,
+``Depend``, ``Severity``, ``Status``, ``Tag``, and ``Target``.
Commenting on bugs
==================
This interface appends a comment to the bug specified in the subject
tag. The the first non-multipart body is attached with the
-appropriate content-type. In the case of "text/plain" contents,
-anything following a line starting with '--' is stripped.
+appropriate content-type. In the case of ``text/plain`` contents,
+anything following a line starting with ``--`` is stripped.::
From jdoe@example.com Fri Apr 18 12:00:00 2008
From: John Doe <jdoe@example.com>
@@ -85,11 +95,11 @@ Controlling bugs
================
This interface consists of a list of allowed be commands, with one
-command per line. Blank lines and lines beginning with '#' are
-ignored, as well anything following a line starting with '--'. All
+command per line. Blank lines and lines beginning with ``#`` are
+ignored, as well anything following a line starting with ``--``. All
the listed commands are executed in order and their output returned.
The commands are split into arguments with the POSIX-compliant
-shlex.split().
+shlex.split().::
From jdoe@example.com Fri Apr 18 12:00:00 2008
From: John Doe <jdoe@example.com>
@@ -109,37 +119,42 @@ shlex.split().
Example emails
==============
-Take a look at my interfaces/email/interactive/examples for some
+Take a look at ``interfaces/email/interactive/examples`` for some
more examples.
Procmail rules
==============
-The file _procmailrc as it stands is fairly appropriate for as a
-dedicated user's ~/.procmailrc. It forwards matching mail to
-be-handle-mail, which should be installed somewhere in the user's
-path. All non-matching mail is dumped into /dev/null. Everything
-procmail does will be logged to ~/be-mail/procmail.log.
+The file ``_procmailrc`` as it stands is fairly appropriate for as a
+dedicated user's ``~/.procmailrc``. It forwards matching mail to
+``be-handle-mail``, which should be installed somewhere in the user's
+path. All non-matching mail is dumped into ``/dev/null``. Everything
+procmail does will be logged to ``~/be-mail/procmail.log``.
If you're piggybacking the interface on top of an existing account,
-you probably only need to add the be-handle-mail stanza to your
-existing ~/.procmailrc, since you will still want to receive non-bug
-emails.
+you probably only need to add the ``be-handle-mail`` stanza to your
+existing ``~/.procmailrc``, since you will still want to receive
+non-bug emails.
+
+Note that you will probably have to add a::
+
+ --repo /path/to/served/repository
-Note that you will probably have to add a
- --be-dir /path/to/served/repository
-option to the be-handle-mail invocation so it knows what repository to
+option to the ``be-handle-mail`` invocation so it knows what repository to
serve.
Multiple repositories may be served by the same email address by adding
-multiple be-handle-mail stanzas, each matching a different tag, for
-example the "[be-bug" portion of the stanza could be "[projectX-bug",
-"[projectY-bug", etc. If you change the base tag, be sure to add a
- --tag-base "projectX-bug"
-or equivalent to your be-handle-mail invocation.
+multiple ``be-handle-mail`` stanzas, each matching a different tag, for
+example the ``[be-bug`` portion of the stanza could be ``[projectX-bug``,
+``[projectY-bug``, etc. If you change the base tag, be sure to add a::
+
+ --tag-base "projectX-bug"
+
+or equivalent to your ``be-handle-mail`` invocation.
Testing
=======
-Send test emails in to be-handle-mail with something like
- cat examples/blank | ./be-handle-mail -o -l - -a
+Send test emails in to ``be-handle-mail`` with something like::
+
+ cat examples/blank | ./be-handle-mail -o -l - -a
diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail
index fa80698..c8343fc 100755
--- a/interfaces/email/interactive/be-handle-mail
+++ b/interfaces/email/interactive/be-handle-mail
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -58,44 +58,51 @@ import shlex
import sys
import time
import traceback
+import types
import doctest
import unittest
-from becommands import subscribe
-import libbe.cmdutil, libbe.encoding, libbe.utility, libbe.diff, \
- libbe.bugdir, libbe.bug, libbe.comment
+import libbe.bugdir
+import libbe.bug
+import libbe.comment
+import libbe.diff
+import libbe.command
+import libbe.command.subscribe as subscribe
+import libbe.storage
+import libbe.ui.command_line
+import libbe.util.encoding
+import libbe.util.utility
import send_pgp_mime
-THIS_SERVER = u"thor.physics.drexel.edu"
-THIS_ADDRESS = u"BE Bugs <wking@thor.physics.drexel.edu>"
-
+THIS_SERVER = u'thor.physics.drexel.edu'
+THIS_ADDRESS = u'BE Bugs <wking@thor.physics.drexel.edu>'
+UI = None
_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
-BE_DIR = _THIS_DIR
-LOGPATH = os.path.join(_THIS_DIR, u"be-handle-mail.log")
+LOGPATH = os.path.join(_THIS_DIR, u'be-handle-mail.log')
LOGFILE = None
# Tag strings generated by generate_global_tags()
-SUBJECT_TAG_BASE = u"be-bug"
+SUBJECT_TAG_BASE = u'be-bug'
SUBJECT_TAG_RESPONSE = None
SUBJECT_TAG_START = None
SUBJECT_TAG_NEW = None
SUBJECT_TAG_COMMENT = None
SUBJECT_TAG_CONTROL = None
-BREAK = u"--"
-NEW_REQUIRED_PSEUDOHEADERS = [u"Version"]
-NEW_OPTIONAL_PSEUDOHEADERS = [u"Reporter", u"Assign", u"Depend", u"Severity",
- u"Status", u"Tag", u"Target",
- u"Confirm", u"Subscribe"]
-CONTROL_COMMENT = u"#"
-ALLOWED_COMMANDS = [u"assign", u"comment", u"commit", u"depend", u"help",
- u"list", u"merge", u"new", u"open", u"severity", u"show",
- u"status", u"subscribe", u"tag", u"target"]
+BREAK = u'--'
+NEW_REQUIRED_PSEUDOHEADERS = [u'Version']
+NEW_OPTIONAL_PSEUDOHEADERS = [u'Reporter', u'Assign', u'Depend', u'Severity',
+ u'Status', u'Tag', u'Target',
+ u'Confirm', u'Subscribe']
+CONTROL_COMMENT = u'#'
+ALLOWED_COMMANDS = [u'assign', u'comment', u'commit', u'depend', u'diff',
+ u'due', u'help', u'list', u'merge', u'new', u'severity',
+ u'show', u'status', u'subscribe', u'tag', u'target']
AUTOCOMMIT = True
-libbe.encoding.ENCODING = u"utf-8" # force default encoding
-ENCODING = libbe.encoding.get_encoding()
+ENCODING = u'utf-8'
+libbe.util.encoding.ENCODING = ENCODING # force default encoding
class InvalidEmail (ValueError):
def __init__(self, msg, message):
@@ -103,10 +110,10 @@ class InvalidEmail (ValueError):
self.msg = msg
def response(self):
header = self.msg.response_header
- body = [u"Error processing email:\n",
- self.response_body(), u""]
+ body = [u'Error processing email:\n',
+ self.response_body(), u'']
response_generator = \
- send_pgp_mime.PGPMimeMessageFactory(u"\n".join(body))
+ send_pgp_mime.PGPMimeMessageFactory(u'\n'.join(body))
response = MIMEMultipart()
response.attach(response_generator.plain())
response.attach(self.msg.msg)
@@ -114,44 +121,44 @@ class InvalidEmail (ValueError):
return ret
def response_body(self):
err_text = [unicode(self)]
- return u"\n".join(err_text)
+ return u'\n'.join(err_text)
class InvalidSubject (InvalidEmail):
def __init__(self, msg, message=None):
if message == None:
- message = u"Invalid subject"
+ message = u'Invalid subject'
InvalidEmail.__init__(self, msg, message)
def response_body(self):
- err_text = u"\n".join([unicode(self), u"",
- u"full subject was:",
+ err_text = u'\n'.join([unicode(self), u'',
+ u'full subject was:',
self.msg.subject()])
return err_text
class InvalidPseudoHeader (InvalidEmail):
def response_body(self):
- err_text = [u"Invalid pseudo-header:\n",
+ err_text = [u'Invalid pseudo-header:\n',
unicode(self)]
- return u"\n".join(err_text)
+ return u'\n'.join(err_text)
class InvalidCommand (InvalidEmail):
def __init__(self, msg, command, message=None):
- bigmessage = u"Invalid execution command '%s'" % command
+ bigmessage = u'Invalid execution command "%s"' % command
if message != None:
- bigmessage += u"\n%s" % message
+ bigmessage += u'\n%s' % message
InvalidEmail.__init__(self, msg, bigmessage)
self.command = command
class InvalidOption (InvalidCommand):
def __init__(self, msg, option, message=None):
- bigmessage = u"Invalid option '%s'" % (option)
+ bigmessage = u'Invalid option "%s"' % (option)
if message != None:
- bigmessage += u"\n%s" % message
+ bigmessage += u'\n%s' % message
InvalidCommand.__init__(self, msg, info, command, bigmessage)
self.option = option
class NotificationFailed (Exception):
def __init__(self, msg):
- bigmessage = "Notification failed: %s" % msg
+ bigmessage = 'Notification failed: %s' % msg
Exception.__init__(self, bigmessage)
self.short_msg = msg
@@ -165,11 +172,11 @@ class ID (object):
def __init__(self, command):
self.command = command
def extract_id(self):
- if hasattr(self, "cached_id"):
+ if hasattr(self, 'cached_id'):
return self._cached_id
assert self.command.ret == 0, self.command.ret
- if self.command.command == u"new":
- regexp = re.compile(u"Created bug with ID (.*)")
+ if self.command.command.name == u'new':
+ regexp = re.compile(u'Created bug with ID (.*)')
else:
raise NotImplementedError, self.command.command
match = regexp.match(self.command.stdout)
@@ -178,13 +185,12 @@ class ID (object):
return self._cached_id
def __str__(self):
if self.command.ret != 0:
- return "<id for %s>" % repr(self.command)
- return "<id %s>" % self.extract_id()
+ return '<id for %s>' % repr(self.command)
+ return '<id %s>' % self.extract_id()
class Command (object):
"""
- A becommands command wrapper.
- Doesn't validate input, so do that before initializing.
+ A libbe.command.Command handler.
Initialize with
Command(msg, command, args=None, stdin=None)
@@ -196,18 +202,17 @@ class Command (object):
"""
def __init__(self, msg, command, args=None, stdin=None):
self.msg = msg
- self.command = command
if args == None:
self.args = []
else:
self.args = args
- self.stdin = stdin
+ self.command = libbe.command.get_command_class(command_name=command)()
+ self.command._setup_io = lambda i_enc,o_enc : None
self.ret = None
+ self.stdin = stdin
self.stdout = None
- self.stderr = None
- self.err = None
def __str__(self):
- return "<command: %s %s>" % (self.command, " ".join([str(s) for s in self.args]))
+ return '<command: %s %s>' % (self.command, ' '.join([str(s) for s in self.args]))
def normalize_args(self):
"""
Expand any ID placeholders in self.args.
@@ -221,60 +226,25 @@ class Command (object):
info. Returns the exit code, stdout, and stderr produced by the
command.
"""
- if self.command in [None, u""]: # don't accept blank commands
- raise InvalidCommand(self.msg, self, "Blank")
- elif self.command not in ALLOWED_COMMANDS:
- raise InvalidCommand(self.msg, self, "Not allowed")
- assert self.ret == None, u"running %s twice!" % unicode(self)
+ if self.command.name in [None, u'']: # don't accept blank commands
+ raise InvalidCommand(self.msg, self, 'Blank')
+ elif self.command.name not in ALLOWED_COMMANDS:
+ raise InvalidCommand(self.msg, self, 'Not allowed')
+ assert self.ret == None, u'running %s twice!' % unicode(self)
self.normalize_args()
- # set stdin and catch stdout and stderr
- if self.stdin != None:
- orig_stdin = sys.stdin
- sys.stdin = StringIO.StringIO(self.stdin)
- new_stdout = codecs.getwriter(ENCODING)(StringIO.StringIO())
- new_stderr = codecs.getwriter(ENCODING)(StringIO.StringIO())
- orig_stdout = sys.stdout
- orig_stderr = sys.stderr
- sys.stdout = new_stdout
- sys.stderr = new_stderr
- # run the command
- os.chdir(BE_DIR)
- try:
- self.ret = libbe.cmdutil.execute(self.command, self.args,
- manipulate_encodings=False)
- except libbe.cmdutil.GetHelp:
- print libbe.cmdutil.help(command)
- except libbe.cmdutil.GetCompletions:
- self.err = InvalidOption(self.msg, self.command, u"--complete")
- except libbe.cmdutil.UsageError, e:
- self.err = InvalidCommand(self.msg, self,
- "%s\n%s" % (type(e), unicode(e)))
- except libbe.cmdutil.UserError, e:
- self.err = InvalidCommand(self.msg, self,
- "%s\n%s" % (type(e), unicode(e)))
- # restore stdin, stdout, and stderr
- if self.stdin != None:
- sys.stdin = orig_stdin
- sys.stdout.flush()
- sys.stderr.flush()
- sys.stdout = orig_stdout
- sys.stderr = orig_stderr
- self.stdout = codecs.decode(new_stdout.getvalue(), ENCODING)
- self.stderr = codecs.decode(new_stderr.getvalue(), ENCODING)
- if self.err != None:
- raise self.err
- return (self.ret, self.stdout, self.stderr)
+ UI.io.set_stdin(self.stdin)
+ self.ret = libbe.ui.command_line.dispatch(UI, self.command, self.args)
+ self.stdout = UI.io.get_stdout()
+ return (self.ret, self.stdout)
def response_msg(self):
if self.ret == None: self.ret = -1
- response_body = [u"Results of running: (exit code %d)" % self.ret,
- u" %s %s" % (self.command, u" ".join(self.args))]
+ response_body = [u'Results of running: (exit code %d)' % self.ret,
+ u' %s %s' % (self.command.name,u' '.join(self.args))]
if self.stdout != None and len(self.stdout) > 0:
- response_body.extend([u"", u"stdout:", u"", self.stdout])
- if self.stderr != None and len(self.stderr) > 0:
- response_body.extend([u"", u"stderr:", u"", self.stderr])
- response_body.append(u"") # trailing endline
+ response_body.extend([u'', u'output:', u'', self.stdout])
+ response_body.append(u'') # trailing endline
response_generator = \
- send_pgp_mime.PGPMimeMessageFactory(u"\n".join(response_body))
+ send_pgp_mime.PGPMimeMessageFactory(u'\n'.join(response_body))
return response_generator.plain()
class DiffTree (libbe.diff.DiffTree):
@@ -304,6 +274,8 @@ class DiffTree (libbe.diff.DiffTree):
"""
def report_or_none(self):
report = self.report()
+ if report == None:
+ return None
payload = report.get_payload()
if payload == None or len(payload) == 0:
return None
@@ -311,27 +283,27 @@ class DiffTree (libbe.diff.DiffTree):
def report_string(self):
report = self.report_or_none()
if report == None:
- return "No changes"
+ return 'No changes'
else:
- return send_pgp_mime.flatten(self.report(), to_unicode=True)
+ return send_pgp_mime.flatten(report, to_unicode=True)
def make_root(self):
return MIMEMultipart()
def join(self, root, parent, data_part):
- if hasattr(parent, "attach_child_text"):
+ if hasattr(parent, 'attach_child_text'):
self.attach_child_text = True
if data_part != None:
- send_pgp_mime.append_text(parent.data_mime_part, u"\n\n%s" % (data_part))
+ send_pgp_mime.append_text(parent.data_mime_part, u'\n\n%s' % (data_part))
self.data_mime_part = parent.data_mime_part
else:
self.data_mime_part = None
if data_part != None:
self.data_mime_part = send_pgp_mime.encodedMIMEText(data_part)
- if parent != None and parent.name in [u"new", u"rem", u"mod"]:
+ if parent != None and parent.name in [u'new', u'rem', u'mod']:
self.attach_child_text = True
if data_part == None: # make blank data_mime_part for children's appends
- self.data_mime_part = send_pgp_mime.encodedMIMEText(u"")
+ self.data_mime_part = send_pgp_mime.encodedMIMEText(u'')
if self.data_mime_part != None:
- self.data_mime_part[u"Content-Description"] = self.name
+ self.data_mime_part[u'Content-Description'] = self.name
root.attach(self.data_mime_part)
def data_part(self, depth, indent=False):
return libbe.diff.DiffTree.data_part(self, depth, indent=indent)
@@ -353,19 +325,19 @@ class Message (object):
p=email.Parser.Parser()
self.msg=p.parsestr(self.text)
if LOGFILE != None:
- LOGFILE.write(u"handling %s\n" % self.author_addr())
- LOGFILE.write(u"\n%s\n\n" % self.text)
+ LOGFILE.write(u'handling %s\n' % self.author_addr())
+ LOGFILE.write(u'\n%s\n\n' % self.text)
self.confirm = True # enable/disable confirmation email
def _yes_no(self, boolean):
if boolean == True:
- return "yes"
- return "no"
+ return 'yes'
+ return 'no'
def author_tuple(self):
"""
Extract and normalize the sender's email address. Returns a
(name, email) tuple.
"""
- if not hasattr(self, "author_tuple_cache"):
+ if not hasattr(self, 'author_tuple_cache'):
self._author_tuple_cache = \
send_pgp_mime.source_email(self.msg, return_realname=True)
return self._author_tuple_cache
@@ -380,24 +352,24 @@ class Message (object):
return self.msg[attr_name]
return default
def message_id(self, default=None):
- return self.default_msg_attribute_access("message-id", default=default)
+ return self.default_msg_attribute_access('message-id', default=default)
def subject(self):
- if "subject" not in self.msg:
- raise InvalidSubject(self, u"Email must contain a subject")
- return self.msg["subject"]
+ if 'subject' not in self.msg:
+ raise InvalidSubject(self, u'Email must contain a subject')
+ return self.msg['subject']
def _split_subject(self):
"""
Returns (tag, subject), with missing values replaced by None.
"""
- if hasattr(self, "_split_subject_cache"):
+ if hasattr(self, '_split_subject_cache'):
return self._split_subject_cache
- args = self.subject().split(u"]",1)
+ args = self.subject().split(u']',1)
if len(args) < 1:
self._split_subject_cache = (None, None)
elif len(args) < 2:
- self._split_subject_cache = (args[0]+u"]", None)
+ self._split_subject_cache = (args[0]+u']', None)
else:
- self._split_subject_cache = (args[0]+u"]", args[1].strip())
+ self._split_subject_cache = (args[0]+u']', args[1].strip())
return self._split_subject_cache
def _subject_tag_type(self):
"""
@@ -410,13 +382,13 @@ class Message (object):
type = None
value = None
if tag == SUBJECT_TAG_NEW:
- type = u"new"
+ type = u'new'
elif tag == SUBJECT_TAG_CONTROL:
- type = u"control"
+ type = u'control'
else:
match = SUBJECT_TAG_COMMENT.match(tag)
if len(match.groups()) == 1:
- type = u"comment"
+ type = u'comment'
value = match.group(1)
return (type, value)
def validate_subject(self):
@@ -426,14 +398,14 @@ class Message (object):
tag,subject = self._split_subject()
if not tag.startswith(SUBJECT_TAG_START):
raise InvalidSubject(
- self, u"Subject must start with '%s'" % SUBJECT_TAG_START)
+ self, u'Subject must start with "%s"' % SUBJECT_TAG_START)
tag_type,value = self._subject_tag_type()
if tag_type == None:
- raise InvalidSubject(self, u"Invalid tag '%s'" % tag)
- elif tag_type == u"new" and len(subject) == 0:
- raise InvalidSubject(self, u"Cannot create a bug with blank title")
- elif tag_type == u"comment" and len(value) == 0:
- raise InvalidSubject(self, u"Must specify a bug ID to comment")
+ raise InvalidSubject(self, u'Invalid tag "%s"' % tag)
+ elif tag_type == u'new' and len(subject) == 0:
+ raise InvalidSubject(self, u'Cannot create a bug with blank title')
+ elif tag_type == u'comment' and len(value) == 0:
+ raise InvalidSubject(self, u'Must specify a bug ID to comment')
def _get_bodies_and_mime_types(self):
"""
Traverse the email message returning (body, mime_type) for
@@ -445,7 +417,7 @@ class Message (object):
continue
body,mime_type=(part.get_payload(decode=True),part.get_content_type())
charset = part.get_content_charset(msg_charset).lower()
- if mime_type.startswith("text/"):
+ if mime_type.startswith('text/'):
body = unicode(body, charset) # convert text types to unicode
yield (body, mime_type)
def _parse_body_pseudoheaders(self, body, required, optional,
@@ -465,15 +437,15 @@ class Message (object):
line = line.strip()
if len(line) == 0:
break
- if ":" not in line:
+ if ':' not in line:
raise InvalidPseudoheader(self, line)
- key,value = line.split(":", 1)
+ key,value = line.split(':', 1)
value = value.strip()
if key not in all:
raise InvalidPseudoHeader(self, key)
if len(value) == 0:
raise InvalidEmail(
- self, u"Blank value for: %s" % key)
+ self, u'Blank value for: %s' % key)
dictionary[key] = value
missing = []
for key in required:
@@ -481,9 +453,9 @@ class Message (object):
missing.append(key)
if len(missing) > 0:
raise InvalidPseudoHeader(self,
- u"Missing required pseudo-headers:\n%s"
- % u", ".join(missing))
- remaining_body = u"\n".join(body_lines[i:]).strip()
+ u'Missing required pseudo-headers:\n%s'
+ % u', '.join(missing))
+ remaining_body = u'\n'.join(body_lines[i:]).strip()
return (remaining_body, dictionary)
def _strip_footer(self, body):
body_lines = body.splitlines()
@@ -491,7 +463,7 @@ class Message (object):
if line.startswith(BREAK):
break
i += 1 # increment past the current valid line.
- return u"\n".join(body_lines[:i]).strip()
+ return u'\n'.join(body_lines[:i]).strip()
def parse(self):
"""
Parse the commands given in the email. Raises assorted
@@ -500,22 +472,22 @@ class Message (object):
"""
self.validate_subject()
tag_type,value = self._subject_tag_type()
- if tag_type == u"new":
+ if tag_type == u'new':
commands = self.parse_new()
- elif tag_type == u"comment":
+ elif tag_type == u'comment':
commands = self.parse_comment(value)
- elif tag_type == u"control":
+ elif tag_type == u'control':
commands = self.parse_control()
else:
- raise Exception, u"Unrecognized tag type '%s'" % tag_type
+ raise Exception, u'Unrecognized tag type "%s"' % tag_type
return commands
def parse_new(self):
- command = u"new"
+ command = u'new'
tag,subject = self._split_subject()
summary = subject
- options = {u"Reporter": self.author_addr(),
- u"Confirm": self._yes_no(self.confirm),
- u"Subscribe": "no",
+ options = {u'Reporter': self.author_addr(),
+ u'Confirm': self._yes_no(self.confirm),
+ u'Subscribe': 'no',
}
body,mime_type = list(self._get_bodies_and_mime_types())[0]
comment_body,options = \
@@ -523,49 +495,54 @@ class Message (object):
NEW_REQUIRED_PSEUDOHEADERS,
NEW_OPTIONAL_PSEUDOHEADERS,
options)
- if options[u"Confirm"].lower() == "no":
+ if options[u'Confirm'].lower() == 'no':
self.confirm = False
- if options[u"Subscribe"].lower() == "yes" and self.confirm == True:
+ if options[u'Subscribe'].lower() == 'yes' and self.confirm == True:
# respond with the subscription format rather than the
# normal command-output format, because the subscription
# format is more user-friendly.
self.confirm = False
- args = [u"--reporter", options[u"Reporter"]]
+ args = [u'--reporter', options[u'Reporter']]
args.append(summary)
commands = [Command(self, command, args)]
id = ID(commands[0])
comment_body = self._strip_footer(comment_body)
if len(comment_body) > 0:
- command = u"comment"
- comment = u"Version: %s\n\n"%options[u"Version"] + comment_body
- args = [u"--author", self.author_addr(),
- u"--alt-id", self.message_id(),
- u"--content-type", mime_type]
+ command = u'comment'
+ comment = u'Version: %s\n\n'%options[u'Version'] + comment_body
+ args = [u'--author', self.author_addr(),
+ u'--alt-id', self.message_id(),
+ u'--content-type', mime_type]
args.append(id)
- args.append(u"-")
- commands.append(Command(self, u"comment", args, stdin=comment))
+ args.append(u'-')
+ commands.append(Command(self, u'comment', args, stdin=comment))
for key,value in options.items():
- if key in [u"Version", u"Reporter", u"Confirm"]:
+ if key in [u'Version', u'Reporter', u'Confirm']:
continue # we've already handled these options
command = key.lower()
- args = [id, value]
- if key == u"Subscribe":
- if value.lower() != "yes":
+ if key in [u'Depend', u'Tag', u'Target', u'Subscribe']:
+ args = [id, value]
+ else:
+ args = [value, id]
+ if key == u'Subscribe':
+ if value.lower() != 'yes':
continue
- args = ["--subscriber", self.author_addr(), id]
+ args = ['--subscriber', self.author_addr(), id]
commands.append(Command(self, command, args))
return commands
def parse_comment(self, bug_uuid):
- command = u"comment"
+ command = u'comment'
bug_id = bug_uuid
author = self.author_addr()
alt_id = self.message_id()
body,mime_type = list(self._get_bodies_and_mime_types())[0]
- if mime_type == "text/plain":
+ if mime_type == 'text/plain':
body = self._strip_footer(body)
content_type = mime_type
- args = [u"--author", author, u"--alt-id", alt_id,
- u"--content-type", content_type, bug_id, u"-"]
+ args = [u'--author', author]
+ if alt_id != None:
+ args.extend([u'--alt-id', alt_id])
+ args.extend([u'--content-type', content_type, bug_id, u'-'])
commands = [Command(self, command, args, stdin=body)]
return commands
def parse_control(self):
@@ -577,39 +554,46 @@ class Message (object):
continue
if line.startswith(BREAK):
break
+ if type(line) == types.UnicodeType:
+ # work around http://bugs.python.org/issue1170
+ line = line.encode('unicode escape')
fields = shlex.split(line)
+ if type(line) == types.UnicodeType:
+ # work around http://bugs.python.org/issue1170
+ for field in fields:
+ field = unicode(field, 'unicode escape')
command,args = (fields[0], fields[1:])
commands.append(Command(self, command, args))
if len(commands) == 0:
- raise InvalidEmail(self, u"No commands in control email.")
+ raise InvalidEmail(self, u'No commands in control email.')
return commands
- def run(self):
+ def run(self, repo='.'):
self._begin_response()
commands = self.parse()
try:
- for command in commands:
+ for i,command in enumerate(commands):
command.run()
self._add_response(command.response_msg())
finally:
if AUTOCOMMIT == True:
tag,subject = self._split_subject()
- self.commit_command = Command(self, "commit", [subject])
+ self.commit_command = Command(self, 'commit', [subject])
self.commit_command.run()
if LOGFILE != None:
- LOGFILE.write(u"Autocommit:\n%s\n\n" %
+ LOGFILE.write(u'Autocommit:\n%s\n\n' %
send_pgp_mime.flatten(self.commit_command.response_msg(),
to_unicode=True))
def _begin_response(self):
tag,subject = self._split_subject()
- response_header = [u"From: %s" % THIS_ADDRESS,
- u"To: %s" % self.author_addr(),
- u"Date: %s" % libbe.utility.time_to_str(time.time()),
- u"Subject: %s Re: %s"%(SUBJECT_TAG_RESPONSE,subject)
+ response_header = [u'From: %s' % THIS_ADDRESS,
+ u'To: %s' % self.author_addr(),
+ u'Date: %s' % libbe.util.utility.time_to_str(time.time()),
+ u'Subject: %s Re: %s'%(SUBJECT_TAG_RESPONSE,subject)
]
if self.message_id() != None:
- response_header.append(u"In-reply-to: %s" % self.message_id())
+ response_header.append(u'In-reply-to: %s' % self.message_id())
self.response_header = \
- send_pgp_mime.header_from_text(text=u"\n".join(response_header))
+ send_pgp_mime.header_from_text(text=u'\n'.join(response_header))
self._response_messages = []
def _add_response(self, response_message):
self._response_messages.append(response_message)
@@ -625,86 +609,54 @@ class Message (object):
def subscriber_emails(self, previous_revision=None):
if previous_revision == None:
if AUTOCOMMIT != True: # no way to tell what's changed
- raise NotificationFailed("Autocommit dissabled")
+ raise NotificationFailed('Autocommit dissabled')
if len(self._response_messages) == 0:
- raise NotificationFailed("Initial email failed.")
+ raise NotificationFailed('Initial email failed.')
if self.commit_command.ret != 0:
# commit failed. Error already logged.
- raise NotificationFailed("Commit failed")
+ raise NotificationFailed('Commit failed')
- # read only bugdir.
- bd = libbe.bugdir.BugDir(from_disk=True,
- manipulate_encodings=False)
- if bd.vcs.versioned == False: # no way to tell what's changed
- raise NotificationFailed("Not versioned")
+ bd = UI.storage_callbacks.get_bugdir()
+ writeable = bd.storage.writeable
+ bd.storage.writeable = False
+ if bd.storage.versioned == False: # no way to tell what's changed
+ bd.storage.writeable = writeable
+ raise NotificationFailed('Not versioned')
bd.load_all_bugs()
subscribers = subscribe.get_bugdir_subscribers(bd, THIS_SERVER)
-
if len(subscribers) == 0:
- return []
+ bd.storage.writeable = writeable
+ return []
+ for subscriber,subscriptions in subscribers.items():
+ subscribers[subscriber] = []
+ for id,types in subscriptions.items():
+ for type in types:
+ subscribers[subscriber].append(
+ libbe.diff.Subscription(id,type))
before_bd, after_bd = self._get_before_and_after_bugdirs(bd, previous_revision)
diff = Diff(before_bd, after_bd)
- diff_tree = diff.report_tree(diff_tree=DiffTree)
- bug_index = {}
- for child in diff_tree.child_by_path("/bugs/new"):
- bug_index[child.name] = ("added", child)
- for child in diff_tree.child_by_path("/bugs/mod"):
- bug_index[child.name] = ("modified", child)
- for child in diff_tree.child_by_path("/bugs/rem"):
- bug_index[child.name] = ("removed", child)
+ diff.full_report(diff_tree=DiffTree)
header = self._subscriber_header(bd, previous_revision)
emails = []
for subscriber,subscriptions in subscribers.items():
- header.replace_header("to", subscriber)
- parts = []
- if "DIR" in subscriptions: # make sure we check the DIR level first
- ordered_subscriptions = [("DIR", subscriptions.pop("DIR"))]
- else:
- ordered_subscriptions = []
- ordered_subscriptions.extend(subscriptions.items())
- for id,types in ordered_subscriptions:
- if id == "DIR":
- if subscribe.BUGDIR_TYPE_ALL in types:
- parts.append(diff_tree.report_or_none())
- break # we've attached everything, so stop checking.
- if subscribe.BUGDIR_TYPE_NEW in types:
- new = diff_tree.child_by_path("/bugs/new")
- parts.append(new.report_or_none())
- continue # move on to next id
- # if we get this far, id refers to a bug.
- assert types == [subscribe.BUG_TYPE_ALL], types
- if id not in bug_index:
- continue # no changes here, move on to next id
- type,bug_root = bug_index[id]
- if type == "added" \
- and "DIR" in subscriptions \
- and subscriptions["DIR"] == subscribe.BUGDIR_TYPE_NEW:
- # this info already attached at the DIR level
- continue # move on to next id
- parts.append(bug_root.report_or_none())
- parts = [p for p in parts if p != None]
- if len(parts) == 0:
- continue # no email to this subscriber
- elif len(parts) == 1:
- root = parts[0]
- else: # join subscription parts into a single body
- root = MIMEMultipart()
- root[u"Content-Description"] = u"Multiple subscription trees."
- for part in parts:
- root.attach(part)
- emails.append(send_pgp_mime.attach_root(header, root))
- if LOGFILE != None:
- LOGFILE.write(u"Preparing to notify %s of changes\n" % subscriber)
+ header.replace_header('to', subscriber)
+ report = diff.report_tree(subscriptions, diff_tree=DiffTree)
+ root = report.report_or_none()
+ if root != None:
+ emails.append(send_pgp_mime.attach_root(header, root))
+ if LOGFILE != None:
+ LOGFILE.write(u'Preparing to notify %s of changes\n' % subscriber)
+ bd.storage.writeable = writeable
return emails
def _get_before_and_after_bugdirs(self, bd, previous_revision=None):
if previous_revision == None:
commit_msg = self.commit_command.stdout
- assert commit_msg.startswith("Committed "), commit_msg
- after_revision = commit_msg[len("Committed "):]
- before_revision = bd.vcs.revision_id(-2)
+ assert commit_msg.startswith('Committed '), commit_msg
+ after_revision = commit_msg[len('Committed '):]
+ before_revision = bd.storage.revision_id(-2)
else:
before_revision = previous_revision
if before_revision == None:
@@ -712,36 +664,36 @@ class Message (object):
before_bd = libbe.bugdir.BugDir(from_disk=False,
manipulate_encodings=False)
else:
- before_bd = bd.duplicate_bugdir(before_revision)
+ before_bd = libbe.bugdir.RevisionedBugDir(bd, before_revision)
#after_bd = bd.duplicate_bugdir(after_revision)
after_bd = bd # assume no changes since commit a few cycles ago
return (before_bd, after_bd)
def _subscriber_header(self, bd, previous_revision=None):
- root_dir = os.path.basename(bd.root)
+ root_dir = os.path.basename(bd.storage.repo)
if previous_revision == None:
- subject = "Changes to %s on %s by %s" \
+ subject = 'Changes to %s on %s by %s' \
% (root_dir, THIS_SERVER, self.author_addr())
else:
- subject = "Changes to %s on %s since revision %s" \
+ subject = 'Changes to %s on %s since revision %s' \
% (root_dir, THIS_SERVER, previous_revision)
- header = [u"From: %s" % THIS_ADDRESS,
- u"To: %s" % u"DUMMY-AUTHOR",
- u"Date: %s" % libbe.utility.time_to_str(time.time()),
- u"Subject: %s Re: %s" % (SUBJECT_TAG_RESPONSE, subject)
+ header = [u'From: %s' % THIS_ADDRESS,
+ u'To: %s' % u'DUMMY-AUTHOR',
+ u'Date: %s' % libbe.util.utility.time_to_str(time.time()),
+ u'Subject: %s Re: %s' % (SUBJECT_TAG_RESPONSE, subject)
]
- return send_pgp_mime.header_from_text(text=u"\n".join(header))
+ return send_pgp_mime.header_from_text(text=u'\n'.join(header))
-def generate_global_tags(tag_base=u"be-bug"):
+def generate_global_tags(tag_base=u'be-bug'):
"""
Generate a series of tags from a base tag string.
"""
global SUBJECT_TAG_BASE, SUBJECT_TAG_START, SUBJECT_TAG_RESPONSE, \
SUBJECT_TAG_NEW, SUBJECT_TAG_COMMENT, SUBJECT_TAG_CONTROL
SUBJECT_TAG_BASE = tag_base
- SUBJECT_TAG_START = u"[%s" % tag_base
- SUBJECT_TAG_RESPONSE = u"[%s]" % tag_base
- SUBJECT_TAG_NEW = u"[%s:submit]" % tag_base
- SUBJECT_TAG_COMMENT = re.compile(u"\[%s:([\-0-9a-z]*)]" % tag_base)
+ SUBJECT_TAG_START = u'[%s' % tag_base
+ SUBJECT_TAG_RESPONSE = u'[%s]' % tag_base
+ SUBJECT_TAG_NEW = u'[%s:submit]' % tag_base
+ SUBJECT_TAG_COMMENT = re.compile(u'\[%s:([\-0-9a-z/]*)]' % tag_base)
SUBJECT_TAG_CONTROL = SUBJECT_TAG_RESPONSE
def open_logfile(logpath=None):
@@ -754,27 +706,25 @@ def open_logfile(logpath=None):
"""
global LOGPATH, LOGFILE
if logpath != None:
- if logpath == u"-":
- LOGPATH = u"stderr"
+ if logpath == u'-':
+ LOGPATH = u'stderr'
LOGFILE = sys.stderr
- elif logpath == u"none":
- LOGPATH = u"none"
+ elif logpath == u'none':
+ LOGPATH = u'none'
LOGFILE = None
elif os.path.isabs(logpath):
LOGPATH = logpath
else:
LOGPATH = os.path.join(_THIS_DIR, logpath)
- if LOGFILE == None and LOGPATH != u"none":
- LOGFILE = codecs.open(LOGPATH, u"a+", ENCODING)
- LOGFILE.write(u"Default encoding: %s\n" % ENCODING)
+ if LOGFILE == None and LOGPATH != u'none':
+ LOGFILE = codecs.open(LOGPATH, u'a+',
+ libbe.util.encoding.get_filesystem_encoding())
def close_logfile():
- if LOGFILE != None and LOGPATH not in [u"stderr", u"none"]:
+ if LOGFILE != None and LOGPATH not in [u'stderr', u'none']:
LOGFILE.close()
def test():
- unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
- suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
result = unittest.TextTestRunner(verbosity=2).run(suite)
num_errors = len(result.errors)
num_failures = len(result.failures)
@@ -783,15 +733,15 @@ def test():
def main(args):
from optparse import OptionParser
- global AUTOCOMMIT, BE_DIR
+ global AUTOCOMMIT, UI
- usage="be-handle-mail [options]\n\n%s" % (__doc__)
+ usage='be-handle-mail [options]\n\n%s' % (__doc__)
parser = OptionParser(usage=usage)
- parser.add_option('-b', '--be-dir', dest='be_dir', default=BE_DIR,
- metavar="DIR",
- help='Select the BE directory to serve (%default).')
+ parser.add_option('-r', '--repo', dest='repo', default=_THIS_DIR,
+ metavar='REPO',
+ help='Select the BE repository to serve (%default).')
parser.add_option('-t', '--tag-base', dest='tag_base',
- default=SUBJECT_TAG_BASE, metavar="TAG",
+ default=SUBJECT_TAG_BASE, metavar='TAG',
help='Set the subject tag base (%default).')
parser.add_option('-o', '--output', dest='output', action='store_true',
help="Don't mail the generated message, print it to stdout instead. Useful for testing be-handle-mail functionality without the whole mail transfer agent and procmail setup.")
@@ -817,27 +767,28 @@ def main(args):
num_bad = 1
sys.exit(num_bad)
- BE_DIR = options.be_dir
AUTOCOMMIT = options.autocommit
if options.notify_since == None:
msg_text = sys.stdin.read()
- libbe.encoding.set_IO_stream_encodings(ENCODING) # _after_ reading message
open_logfile(options.logfile)
generate_global_tags(options.tag_base)
+ io = libbe.command.StringInputOutput()
+ UI = libbe.command.UserInterface(io, location=options.repo)
+
if options.notify_since != None:
if options.subscribers == True:
if LOGFILE != None:
- LOGFILE.write(u"Checking for subscribers to notify since revision %s\n"
+ LOGFILE.write(u'Checking for subscribers to notify since revision %s\n'
% options.notify_since)
try:
m = Message(disable_parsing=True)
emails = m.subscriber_emails(options.notify_since)
except NotificationFailed, e:
if LOGFILE != None:
- LOGFILE.write(unicode(e) + u"\n")
+ LOGFILE.write(unicode(e) + u'\n')
else:
for msg in emails:
if options.output == True:
@@ -845,12 +796,14 @@ def main(args):
else:
send_pgp_mime.mail(msg, send_pgp_mime.sendmail)
close_logfile()
+ UI.cleanup()
sys.exit(0)
if len(msg_text.strip()) == 0: # blank email!?
if LOGFILE != None:
- LOGFILE.write(u"Blank email!\n")
+ LOGFILE.write(u'Blank email!\n')
close_logfile()
+ UI.cleanup()
sys.exit(1)
try:
m = Message(msg_text)
@@ -859,9 +812,11 @@ def main(args):
response = e.response()
except Exception, e:
if LOGFILE != None:
- LOGFILE.write(u"Uncaught exception:\n%s\n" % (e,))
+ LOGFILE.write(u'Uncaught exception:\n%s\n' % (e,))
traceback.print_tb(sys.exc_traceback, file=LOGFILE)
close_logfile()
+ m.commit_command.cleanup()
+ UI.cleanup()
sys.exit(1)
else:
response = m.response_email()
@@ -869,21 +824,21 @@ def main(args):
print send_pgp_mime.flatten(response, to_unicode=True)
elif m.confirm == True:
if LOGFILE != None:
- LOGFILE.write(u"Sending response to %s\n" % m.author_addr())
- LOGFILE.write(u"\n%s\n\n" % send_pgp_mime.flatten(response,
+ LOGFILE.write(u'Sending response to %s\n' % m.author_addr())
+ LOGFILE.write(u'\n%s\n\n' % send_pgp_mime.flatten(response,
to_unicode=True))
send_pgp_mime.mail(response, send_pgp_mime.sendmail)
else:
if LOGFILE != None:
- LOGFILE.write(u"Response declined by %s\n" % m.author_addr())
+ LOGFILE.write(u'Response declined by %s\n' % m.author_addr())
if options.subscribers == True:
if LOGFILE != None:
- LOGFILE.write(u"Checking for subscribers\n")
+ LOGFILE.write(u'Checking for subscribers\n')
try:
emails = m.subscriber_emails()
except NotificationFailed, e:
if LOGFILE != None:
- LOGFILE.write(unicode(e) + u"\n")
+ LOGFILE.write(unicode(e) + u'\n')
else:
for msg in emails:
if options.output == True:
@@ -892,7 +847,8 @@ def main(args):
send_pgp_mime.mail(msg, send_pgp_mime.sendmail)
close_logfile()
-
+ m.commit_command.cleanup()
+ UI.cleanup()
class GenerateGlobalTagsTestCase (unittest.TestCase):
def setUp(self):
@@ -914,37 +870,40 @@ class GenerateGlobalTagsTestCase (unittest.TestCase):
def test_restore_global_tags(self):
"Test global tag restoration by teardown function."
global SUBJECT_TAG_BASE
- self.failUnlessEqual(SUBJECT_TAG_BASE, u"be-bug")
- SUBJECT_TAG_BASE = "projectX-bug"
- self.failUnlessEqual(SUBJECT_TAG_BASE, u"projectX-bug")
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u'be-bug')
+ SUBJECT_TAG_BASE = 'projectX-bug'
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u'projectX-bug')
self.restore_global_tags()
- self.failUnlessEqual(SUBJECT_TAG_BASE, u"be-bug")
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u'be-bug')
def test_subject_tag_base(self):
"Should set SUBJECT_TAG_BASE global correctly"
- generate_global_tags(u"projectX-bug")
- self.failUnlessEqual(SUBJECT_TAG_BASE, u"projectX-bug")
+ generate_global_tags(u'projectX-bug')
+ self.failUnlessEqual(SUBJECT_TAG_BASE, u'projectX-bug')
def test_subject_tag_start(self):
"Should set SUBJECT_TAG_START global correctly"
- generate_global_tags(u"projectX-bug")
- self.failUnlessEqual(SUBJECT_TAG_START, u"[projectX-bug")
+ generate_global_tags(u'projectX-bug')
+ self.failUnlessEqual(SUBJECT_TAG_START, u'[projectX-bug')
def test_subject_tag_response(self):
"Should set SUBJECT_TAG_RESPONSE global correctly"
- generate_global_tags(u"projectX-bug")
- self.failUnlessEqual(SUBJECT_TAG_RESPONSE, u"[projectX-bug]")
+ generate_global_tags(u'projectX-bug')
+ self.failUnlessEqual(SUBJECT_TAG_RESPONSE, u'[projectX-bug]')
def test_subject_tag_new(self):
"Should set SUBJECT_TAG_NEW global correctly"
- generate_global_tags(u"projectX-bug")
- self.failUnlessEqual(SUBJECT_TAG_NEW, u"[projectX-bug:submit]")
+ generate_global_tags(u'projectX-bug')
+ self.failUnlessEqual(SUBJECT_TAG_NEW, u'[projectX-bug:submit]')
def test_subject_tag_control(self):
"Should set SUBJECT_TAG_CONTROL global correctly"
- generate_global_tags(u"projectX-bug")
- self.failUnlessEqual(SUBJECT_TAG_CONTROL, u"[projectX-bug]")
+ generate_global_tags(u'projectX-bug')
+ self.failUnlessEqual(SUBJECT_TAG_CONTROL, u'[projectX-bug]')
def test_subject_tag_comment(self):
"Should set SUBJECT_TAG_COMMENT global correctly"
- generate_global_tags(u"projectX-bug")
- m = SUBJECT_TAG_COMMENT.match("[projectX-bug:xyz-123]")
+ generate_global_tags(u'projectX-bug')
+ m = SUBJECT_TAG_COMMENT.match('[projectX-bug:abc/xyz-123]')
self.failUnlessEqual(len(m.groups()), 1)
- self.failUnlessEqual(m.group(1), u"xyz-123")
+ self.failUnlessEqual(m.group(1), u'abc/xyz-123')
+
+unitsuite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+suite = unittest.TestSuite([unitsuite, doctest.DocTestSuite()])
if __name__ == "__main__":
main(sys.argv)
diff --git a/interfaces/email/interactive/becommands b/interfaces/email/interactive/becommands
deleted file mode 120000
index 8af773c..0000000
--- a/interfaces/email/interactive/becommands
+++ /dev/null
@@ -1 +0,0 @@
-../../../becommands \ No newline at end of file
diff --git a/interfaces/email/interactive/examples/email_bugs b/interfaces/email/interactive/examples/email_bugs
new file mode 100644
index 0000000..949e1c1
--- /dev/null
+++ b/interfaces/email/interactive/examples/email_bugs
@@ -0,0 +1,37 @@
+From jdoe@example.com Fri Apr 18 12:00:00 2008
+Content-Type: text/xml; charset="utf-8"
+MIME-Version: 1.0
+Content-Transfer-Encoding: quoted-printable
+From: jdoe@example.com
+To: a@b.com
+Date: Fri, 18 Apr 2008 12:00:00 +0000
+Subject: [be-bug:xml] Updates to a, b
+
+<?xml version="1.0" encoding="utf-8" ?>
+<be-xml>
+ <version>
+ <tag>1.0.0</tag>
+ <branch-nick>be</branch-nick>
+ <revno>446</revno>
+ <revision-id>wking@drexel.edu-20091119214553-iqyw2cpqluww3zna</revision-id>
+ </version>
+ <bug>
+ <uuid>a</uuid>
+ <short-name>a</short-name>
+ <severity>minor</severity>
+ <status>open</status>
+ <creator>John Doe &lt;jdoe@example.com&gt;</creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug A</summary>
+ </bug>
+ <bug>
+ <uuid>b</uuid>
+ <short-name>b</short-name>
+ <severity>minor</severity>
+ <status>closed</status>
+ <creator>Jane Doe &lt;jdoe@example.com&gt;</creator>
+ <created>Thu, 01 Jan 1970 00:00:00 +0000</created>
+ <summary>Bug B</summary>
+ </bug>
+</be-xml>
+
diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py
index c19483e..517b1f0 100644
--- a/interfaces/email/interactive/send_pgp_mime.py
+++ b/interfaces/email/interactive/send_pgp_mime.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
#
-# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by