aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2009-07-19 07:57:28 -0400
committerW. Trevor King <wking@drexel.edu>2009-07-19 07:57:28 -0400
commit170b87f3499b2c8a224dccf5927ae9b4e6be2013 (patch)
tree0294a7c3e15b3e7c4d037c32c87ec96bf685b891
parente0af316ac69977437d063785040d3e4e74feed53 (diff)
downloadbugseverywhere-170b87f3499b2c8a224dccf5927ae9b4e6be2013.tar.gz
Reworked be-handle-mail to be more like the Debian Bug Tracker.
Changed all the example emails over to the new format. Now it's time to try them all out and fix all the bugs ;).
-rwxr-xr-xinterfaces/email/interactive/be-handle-mail238
-rw-r--r--interfaces/email/interactive/examples/comment4
-rw-r--r--interfaces/email/interactive/examples/help9
-rw-r--r--interfaces/email/interactive/examples/invalid_command6
-rw-r--r--interfaces/email/interactive/examples/invalid_subject2
-rw-r--r--interfaces/email/interactive/examples/list4
-rw-r--r--interfaces/email/interactive/examples/missing_command6
-rw-r--r--interfaces/email/interactive/examples/new7
-rw-r--r--interfaces/email/interactive/examples/new_with_comment6
-rw-r--r--interfaces/email/interactive/examples/show6
-rw-r--r--interfaces/email/interactive/examples/unicode6
11 files changed, 192 insertions, 102 deletions
diff --git a/interfaces/email/interactive/be-handle-mail b/interfaces/email/interactive/be-handle-mail
index 490c733..339affa 100755
--- a/interfaces/email/interactive/be-handle-mail
+++ b/interfaces/email/interactive/be-handle-mail
@@ -40,17 +40,29 @@ import sys
import time
import traceback
-SUBJECT_TAG = "[be-bug]"
-HANDLER_ADDRESS = "BE Bugs <wking@thor.physics.drexel.edu>"
+HANDLER_ADDRESS = u"BE Bugs <wking@thor.physics.drexel.edu>"
_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
BE_DIR = _THIS_DIR
-LOGPATH = os.path.join(_THIS_DIR, "be-handle-mail.log")
+LOGPATH = os.path.join(_THIS_DIR, u"be-handle-mail.log")
LOGFILE = None
-libbe.encoding.ENCODING = "utf-8" # force default encoding
-ENCODING = libbe.encoding.get_encoding()
+SUBJECT_TAG_BASE = u"[be-bug"
+SUBJECT_TAG_NEW = u"%s:submit]" % SUBJECT_TAG_BASE
+SUBJECT_TAG_COMMENT = re.compile(u"%s:([\-0-9a-z]*)]"
+ % SUBJECT_TAG_BASE.replace("[","\["))
+SUBJECT_TAG_CONTROL = u"%s]" % SUBJECT_TAG_BASE
+
+NEW_REQUIRED_PSEUDOHEADERS = [u"Version"]
+NEW_OPTIONAL_PSEUDOHEADERS = [u"Reporter"]
+
+CONTROL_COMMENT = u"#"
+CONTROL_BREAK = u"--"
-ALLOWED_COMMANDS = ["new", "comment", "list", "show", "help"]
+ALLOWED_COMMANDS = [u"new", u"comment", u"list", u"show", u"help"]
+
+
+libbe.encoding.ENCODING = u"utf-8" # force default encoding
+ENCODING = libbe.encoding.get_encoding()
class InvalidEmail (ValueError):
def __init__(self, msg, message):
@@ -75,7 +87,7 @@ class InvalidEmail (ValueError):
class InvalidSubject (InvalidEmail):
def __init__(self, msg, message=None):
if message == None:
- message = "Invalid subject"
+ message = u"Invalid subject"
InvalidEmail.__init__(self, msg, message)
def response_body(self):
err_text = u"\n".join([unicode(self), u"",
@@ -83,23 +95,23 @@ class InvalidSubject (InvalidEmail):
self.msg.subject()])
return err_text
-class InvalidEmailCommand (InvalidSubject):
- def __init__(self, msg, message=None):
- if message == None:
- message = "Invalid command '%s'" % msg.subject_command()
- InvalidSubject.__init__(self, msg, message)
+class InvalidPseudoHeader (InvalidEmail):
+ def response_body(self):
+ err_text = [u"Invalid pseudo-header:\n",
+ unicode(self)]
+ return u"\n".join(err_text)
class InvalidExecutionCommand (InvalidEmail):
def __init__(self, msg, command, message=None):
if message == None:
- message = "Invalid execution command '%s'" % command
+ message = u"Invalid execution command '%s'" % command
InvalidEmail.__init__(self, msg, message)
self.command = command
class InvalidOption (InvalidExecutionCommand):
def __init__(self, msg, option, message=None):
if message == None:
- message = "Invalid option '%s' to command '%s'" % (option, command)
+ message = u"Invalid option '%s' to command '%s'" % (option, command)
InvalidCommand.__init__(self, msg, info, command, message)
self.option = option
@@ -114,8 +126,8 @@ class ID (object):
self.command = command
def extract_id(self):
assert self.command.ret == 0, self.command.ret
- if self.command.command == "new":
- regexp = re.compile("Created bug with ID (.*)")
+ if self.command.command == u"new":
+ regexp = re.compile(u"Created bug with ID (.*)")
else:
raise NotImplementedError, self.command.command
match = regexp.match(self.command.stdout)
@@ -162,7 +174,11 @@ class Command (object):
info. Returns the exit code, stdout, and stderr produced by the
command.
"""
- assert self.ret == None, "running %s twice!" % str(self)
+ if self.command in [None, u""]: # don't accept blank commands
+ raise InvalidCommand(self.msg, self)
+ elif self.command not in ALLOWED_COMMANDS:
+ raise InvalidCommand(self.msg, self)
+ assert self.ret == None, u"running %s twice!" % unicode(self)
self.normalize_args()
# set stdin and catch stdout and stderr
if self.stdin != None:
@@ -185,7 +201,7 @@ class Command (object):
except libbe.cmdutil.GetHelp:
print libbe.cmdutil.help(command)
except libbe.cmdutil.GetCompletions:
- self.err = InvalidOption(self.msg, self.command, "--complete")
+ self.err = InvalidOption(self.msg, self.command, u"--complete")
except libbe.cmdutil.UsageError, e:
self.err = InvalidCommand(self.msg, self.command, e)
except libbe.cmdutil.UserError, e:
@@ -222,8 +238,8 @@ class Message (object):
p=email.Parser.Parser()
self.msg=p.parsestr(self.text)
if LOGFILE != None:
- LOGFILE.write("handling %s\n" % self.author_addr())
- LOGFILE.write("\n%s\n\n" % self.text)
+ LOGFILE.write(u"handling %s\n" % self.author_addr())
+ LOGFILE.write(u"\n%s\n\n" % self.text)
def author_tuple(self):
"""
Extract and normalize the sender's email address. Returns a
@@ -247,44 +263,57 @@ class Message (object):
return self.default_msg_attribute_access("message-id", default=default)
def subject(self):
if "subject" not in self.msg:
- raise InvalidSubject(self, "Email must contain a subject")
+ raise InvalidSubject(self, u"Email must contain a subject")
return self.msg["subject"]
def _split_subject(self):
"""
- Returns (tag, command, arg), with missing values replaced by
- None.
+ Returns (tag, subject), with missing values replaced by None.
"""
if hasattr(self, "_split_subject_cache"):
return self._split_subject_cache
- args = self.subject().split()
+ args = self.subject().split(u"]",1)
if len(args) < 1:
- self._split_subject_cache = (None, None, None)
+ self._split_subject_cache = (None, None)
elif len(args) < 2:
- self._split_subject_cache = (args[0], None, None)
- elif len(args) > 2:
- self._split_subject_cache = (args[0], args[1], tuple(args[2:]))
+ self._split_subject_cache = (args[0]+u"]", None)
else:
- self._split_subject_cache = (args[0], args[1], tuple())
+ self._split_subject_cache = (args[0]+u"]", args[1].strip())
return self._split_subject_cache
+ def _subject_tag_type(self):
+ """
+ Parse subject tag, return (type, value), where type is one of
+ None, "new", "comment", or "control"; and value is None except
+ in the case of "comment", in which case it's the bug
+ ID/shortname.
+ """
+ tag,subject = self._split_subject()
+ type = None
+ value = None
+ if tag == SUBJECT_TAG_NEW:
+ type = u"new"
+ elif tag == SUBJECT_TAG_CONTROL:
+ type = u"control"
+ else:
+ match = SUBJECT_TAG_COMMENT.match(tag)
+ if len(match.groups()) == 1:
+ type = u"comment"
+ value = match.group(1)
+ return (type, value)
def validate_subject(self):
"""
- Validate the subject line as best we can without attempting
- command execution.
+ Validate the subject line.
"""
- tag,command,args = self._split_subject()
- if tag != SUBJECT_TAG:
+ tag,subject = self._split_subject()
+ if not tag.startswith(SUBJECT_TAG_BASE):
raise InvalidSubject(
- self, "Subject must start with '%s '" % SUBJECT_TAG)
- elif command == None: # don't accept blank commands
- raise InvalidEmailCommand(self)
- if command not in ALLOWED_COMMANDS:
- raise InvalidEmailCommand(self)
- def subject_command(self):
- tag,command,args = self._split_subject()
- return command
- def subject_args(self):
- tag,command,args = self._split_subject()
- return args
+ self, u"Subject must start with '%s'" % SUBJECT_TAG_BASE)
+ 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")
def _get_bodies_and_mime_types(self):
"""
Traverse the email message returning (body, mime_type) for
@@ -293,8 +322,43 @@ class Message (object):
for part in self.msg.walk():
if part.is_multipart():
continue
- body,mime_type = (part.get_payload(decode=1), part.get_content_type())
+ body,mime_type=(part.get_payload(decode=1),part.get_content_type())
yield (body, mime_type)
+ def _parse_body_pseudoheaders(self, body, required, optional,
+ dictionary=None):
+ """
+ Grab any pseudo-headers from the beginning of body. Raise
+ InvalidPseudoHeader on errors. Returns the body text after
+ the pseudo-header and a dictionary of set options. If you
+ like, you can initialize the dictionary with some defaults
+ and pass your initialized dict in as dictionary.
+ """
+ if dictionary == None:
+ dictionary = {}
+ body_lines = body.splitlines()
+ all = required+optional
+ for i,line in enumerate(body_lines):
+ line = line.strip()
+ if len(line) == 0:
+ break
+ 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)
+ dictionary[key] = value
+ missing = []
+ for key in required:
+ if key not in dictionary:
+ 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()
+ return (remaining_body, dictionary)
def parse(self):
"""
Parse the commands given in the email. Raises assorted
@@ -302,37 +366,55 @@ class Message (object):
otherwise returns a list of suggested commands to run.
"""
self.validate_subject()
- tag,command,arg_tuple = self._split_subject()
- args = list(arg_tuple)
+ tag_type,value = self._subject_tag_type()
commands = []
- if command == "new":
+ if tag_type == u"new":
+ command = u"new"
+ tag,subject = self._split_subject()
+ summary = subject
+ options = {u"Reporter": self.author_addr()}
body,mime_type = list(self._get_bodies_and_mime_types())[0]
- lines = body.strip().split("\n", 1)
- summary = lines[0]
- if "--reporter" not in args and "-r" not in args:
- args = ["--reporter", self.author_addr()]+args
+ comment_body,options = \
+ self._parse_body_pseudoheaders(body,
+ NEW_REQUIRED_PSEUDOHEADERS,
+ NEW_OPTIONAL_PSEUDOHEADERS,
+ options)
+ args = [u"--reporter", options[u"Reporter"]]
args.append(summary)
commands.append(Command(self, command, args))
- if len(lines) == 2:
- comment = lines[1]
- args = ["--author", self.author_addr(),
- "--alt-id", self.message_id(),
- "--content-type", mime_type]
+ 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]
args.append(ID(commands[0]))
- args.append("-")
- commands.append(Command(self, "comment", args, stdin=comment))
- elif command == "comment":
- if "--author" not in args and "-a" not in args:
- args = ["--author", self.author_addr()] + args
- if "--alt-id" not in args:
- args = ["--alt-id", self.message_id()] + args
+ args.append(u"-")
+ commands.append(Command(self, u"comment", args, stdin=comment))
+ elif tag_type == u"comment":
+ command = u"comment"
+ bug_id = value
+ author = self.author_addr()
+ alt_id = self.message_id()
body,mime_type = list(self._get_bodies_and_mime_types())[0]
- if "--content-type" not in args and "-c" not in args:
- args = ["--content-type", mime_type] + args
- args.append("-")
+ content_type = mime_type
+ args = [u"--author", author, u"--alt-id", alt_id,
+ u"--content-type", content_type, bug_id, u"-"]
commands.append(Command(self, command, args, stdin=body))
+ elif tag_type == u"control":
+ body,mime_type = list(self._get_bodies_and_mime_types())[0]
+ for line in body.splitlines():
+ line = line.strip()
+ if line.startswith(CONTROL_COMMENT) or len(line) == 0:
+ continue
+ if line.startswith(CONTROL_BREAK):
+ break
+ command,args = line.split(u" ",1)
+ commands.append(Command(self, command, args))
+ if len(commands) == 0:
+ raise InvalidEmail(self, u"No commands in control email.")
else:
- commands.append(Command(self, command, args))
+ raise Exception, u"Unrecognized tag type '%s'" % tag_type
return commands
def run(self):
self._begin_response()
@@ -378,22 +460,22 @@ def open_logfile(logpath=None):
"""
global LOGPATH, LOGFILE
if logpath != None:
- if logpath == "-":
- LOGPATH = "stderr"
+ if logpath == u"-":
+ LOGPATH = u"stderr"
LOGFILE = sys.stderr
- elif logpath == "none":
- LOGPATH = "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 != "none":
- LOGFILE = codecs.open(LOGPATH, "a+", ENCODING)
- LOGFILE.write("Default encoding: %s\n" % ENCODING)
+ if LOGFILE == None and LOGPATH != u"none":
+ LOGFILE = codecs.open(LOGPATH, u"a+", ENCODING)
+ LOGFILE.write(u"Default encoding: %s\n" % ENCODING)
def close_logfile():
- if LOGFILE != None and LOGPATH not in ["stderr", "none"]:
+ if LOGFILE != None and LOGPATH not in [u"stderr", u"none"]:
LOGFILE.close()
@@ -414,7 +496,7 @@ def main():
open_logfile(options.logfile)
if len(msg_text.strip()) == 0: # blank email!?
if LOGFILE != None:
- LOGFILE.write("Blank email!")
+ LOGFILE.write(u"Blank email!\n")
close_logfile()
sys.exit(1)
try:
@@ -424,7 +506,7 @@ def main():
response = e.response()
except Exception, e:
if LOGFILE != None:
- LOGFILE.write("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()
sys.exit(1)
diff --git a/interfaces/email/interactive/examples/comment b/interfaces/email/interactive/examples/comment
index 1d60748..f22e4b2 100644
--- a/interfaces/email/interactive/examples/comment
+++ b/interfaces/email/interactive/examples/comment
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] comment a1d
+Subject: [be-bug:a1d] Subject ignored
We sure do.
+--
+Goofy tagline ignored
diff --git a/interfaces/email/interactive/examples/help b/interfaces/email/interactive/examples/help
deleted file mode 100644
index 14e887c..0000000
--- a/interfaces/email/interactive/examples/help
+++ /dev/null
@@ -1,9 +0,0 @@
-From jdoe@example.com Fri Apr 18 11:18:58 2008
-Message-ID: <abcd@example.com>
-Date: Fri, 18 Apr 2008 12:00:00 +0000
-From: John Doe <jdoe@example.com>
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 8bit
-Subject: [be-bug] help
-
-Dummy content
diff --git a/interfaces/email/interactive/examples/invalid_command b/interfaces/email/interactive/examples/invalid_command
index 4d18f09..f2963c7 100644
--- a/interfaces/email/interactive/examples/invalid_command
+++ b/interfaces/email/interactive/examples/invalid_command
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] close
+Subject: [be-bug]
-Dummy content
+close
+--
+Close is currently disabled for the email interface.
diff --git a/interfaces/email/interactive/examples/invalid_subject b/interfaces/email/interactive/examples/invalid_subject
index e148d0b..95112dd 100644
--- a/interfaces/email/interactive/examples/invalid_subject
+++ b/interfaces/email/interactive/examples/invalid_subject
@@ -6,4 +6,4 @@ Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subject: Spam!
-Dummy content
+This should elicit an "invalid header" response email.
diff --git a/interfaces/email/interactive/examples/list b/interfaces/email/interactive/examples/list
index 333315f..acba424 100644
--- a/interfaces/email/interactive/examples/list
+++ b/interfaces/email/interactive/examples/list
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] list --status all
+Subject: [be-bug] Subject ignored
+list --status all
+--
Dummy content
diff --git a/interfaces/email/interactive/examples/missing_command b/interfaces/email/interactive/examples/missing_command
index fefe41b..bb390fc 100644
--- a/interfaces/email/interactive/examples/missing_command
+++ b/interfaces/email/interactive/examples/missing_command
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] abcde
+Subject: [be-bug] Subject ignored
-Dummy content
+abcde
+--
+This should elicit a "invalid command 'abcde'" response email.
diff --git a/interfaces/email/interactive/examples/new b/interfaces/email/interactive/examples/new
index 7ac6dce..b0bc3d7 100644
--- a/interfaces/email/interactive/examples/new
+++ b/interfaces/email/interactive/examples/new
@@ -4,6 +4,9 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] new
+Subject: [be-bug:submit] Need tests for the email interface.
-Need tests for the email interface.
+Version: XYZ
+
+--
+Goofy tagline not included, and no comment added.
diff --git a/interfaces/email/interactive/examples/new_with_comment b/interfaces/email/interactive/examples/new_with_comment
index 8bd50aa..1077f0f 100644
--- a/interfaces/email/interactive/examples/new_with_comment
+++ b/interfaces/email/interactive/examples/new_with_comment
@@ -4,8 +4,10 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] new
+Subject: [be-bug:submit] Need tests for the email interface.
-Need tests for the email interface.
+Version: XYZ
I think so anyway.
+--
+Goofy tagline not included.
diff --git a/interfaces/email/interactive/examples/show b/interfaces/email/interactive/examples/show
index 3ff56f4..c5f8a4d 100644
--- a/interfaces/email/interactive/examples/show
+++ b/interfaces/email/interactive/examples/show
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] show --xml 361
+Subject: [be-bug] Subject ignored
-Dummy content
+show --xml 361
+--
+Can we show a bug?
diff --git a/interfaces/email/interactive/examples/unicode b/interfaces/email/interactive/examples/unicode
index e5b0775..f0e8001 100644
--- a/interfaces/email/interactive/examples/unicode
+++ b/interfaces/email/interactive/examples/unicode
@@ -4,6 +4,8 @@ Date: Fri, 18 Apr 2008 12:00:00 +0000
From: John Doe <jdoe@example.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
-Subject: [be-bug] show --xml f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a
+Subject: [be-bug] Subject ignored
-Dummy content
+show --xml f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a
+--
+Can we handle unicode output?