aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2009-06-22 12:20:09 -0400
committerW. Trevor King <wking@drexel.edu>2009-06-22 12:20:09 -0400
commit11556f64815eeba2bb9bea5fd804841152d836bd (patch)
treea1e5b94a8d294f123001f33995d99a3b9bdc69d7
parent665e64fbe8fea91f68be42cbbda79082d9a58c30 (diff)
parent107c15bfd9c5702554d14583dd589226cc573985 (diff)
downloadbugseverywhere-11556f64815eeba2bb9bea5fd804841152d836bd.tar.gz
Merged be-xml-to-mbox
-rw-r--r--becommands/show.py5
-rw-r--r--libbe/bug.py9
-rw-r--r--libbe/comment.py24
-rwxr-xr-xxml/be-xml-to-mbox181
-rwxr-xr-xxml/catmutt59
5 files changed, 258 insertions, 20 deletions
diff --git a/becommands/show.py b/becommands/show.py
index 4b0078f..b33a96b 100644
--- a/becommands/show.py
+++ b/becommands/show.py
@@ -41,10 +41,7 @@ def execute(args, test=False):
<short-name>a</short-name>
<severity>minor</severity>
<status>open</status>
- <assigned><class 'libbe.settings_object.EMPTY'></assigned>
- <target><class 'libbe.settings_object.EMPTY'></target>
- <reporter><class 'libbe.settings_object.EMPTY'></reporter>
- <creator>John Doe <jdoe@example.com></creator>
+ <creator>John Doe &lt;jdoe@example.com&gt;</creator>
<created>...</created>
<summary>Bug A</summary>
</bug>
diff --git a/libbe/bug.py b/libbe/bug.py
index fe059fa..8c095c5 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -18,6 +18,7 @@ import os
import os.path
import errno
import time
+import xml.sax.saxutils
import doctest
from beuuid import uuid_gen
@@ -244,9 +245,7 @@ class Bug(settings_object.SavedSettingsObject):
if self.time == None:
timestring = ""
else:
- htime = utility.handy_time(self.time)
- ftime = utility.time_to_str(self.time)
- timestring = "%s (%s)" % (htime, ftime)
+ timestring = utility.time_to_str(self.time)
info = [("uuid", self.uuid),
("short-name", shortname),
@@ -260,8 +259,8 @@ class Bug(settings_object.SavedSettingsObject):
("summary", self.summary)]
ret = '<bug>\n'
for (k,v) in info:
- if v is not None:
- ret += ' <%s>%s</%s>\n' % (k,v,k)
+ if v is not settings_object.EMPTY:
+ ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k)
if show_comments == True:
comout = self.comment_root.xml_thread(auto_name_map=True,
diff --git a/libbe/comment.py b/libbe/comment.py
index e5c86c7..d0fa5ee 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -19,6 +19,7 @@
import os
import os.path
import time
+import xml.sax.saxutils
import textwrap
import doctest
@@ -223,8 +224,8 @@ class Comment(Tree, settings_object.SavedSettingsObject):
>>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
>>> print comm.xml(indent=2, shortname="com-1")
<comment>
- <name>com-1</name>
<uuid>0123</uuid>
+ <short-name>com-1</short-name>
<from></from>
<date>Thu, 01 Jan 1970 00:00:00 +0000</date>
<body>Some
@@ -234,16 +235,17 @@ class Comment(Tree, settings_object.SavedSettingsObject):
"""
if shortname == None:
shortname = self.uuid
- lines = ["<comment>",
- " <name>%s</name>" % (shortname,),
- " <uuid>%s</uuid>" % self.uuid,]
- if self.in_reply_to != None:
- lines.append(" <in_reply_to>%s</in_reply_to>" % self.in_reply_to)
- lines.extend([
- " <from>%s</from>" % self._setting_attr_string("From"),
- " <date>%s</date>" % self.time_string,
- " <body>%s</body>" % (self.body or "").rstrip('\n'),
- "</comment>\n"])
+ info = [("uuid", self.uuid),
+ ("short-name", shortname),
+ ("in-reply-to", self.in_reply_to),
+ ("from", self._setting_attr_string("From")),
+ ("date", self.time_string),
+ ("body", (self.body or "").rstrip('\n'))]
+ lines = ["<comment>"]
+ for (k,v) in info:
+ if v not in [settings_object.EMPTY, None]:
+ lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
+ lines.append("</comment>")
istring = ' '*indent
sep = '\n' + istring
return istring + sep.join(lines).rstrip('\n')
diff --git a/xml/be-xml-to-mbox b/xml/be-xml-to-mbox
new file mode 100755
index 0000000..0f430dc
--- /dev/null
+++ b/xml/be-xml-to-mbox
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+# Copyright (C) 2009 William 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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
+"""
+Convert xml output of `be list --xml` into mbox format for browsing
+with a mail reader. For example
+ $ be list --xml --status=all | be-xml-to-mbox | catmutt
+
+mbox is a flat-file format, consisting of a series of messages.
+Messages begin with a a From_ line, followed by RFC 822 email,
+followed by a blank line.
+"""
+
+#from mailbox import mbox, Message # the mailbox people really want an on-disk copy
+import email.utils
+import types
+
+from libbe.utility import str_to_time as rfc2822_to_gmtime_integer
+from time import asctime, gmtime
+from xml.sax import make_parser
+from xml.sax.handler import ContentHandler
+from xml.sax.saxutils import unescape
+
+
+DEFAULT_DOMAIN = "invalid.com"
+DEFAULT_EMAIL = "dummy@" + DEFAULT_DOMAIN
+
+def rfc2822_to_asctime(rfc2822_string):
+ """Convert an RFC 2822-fomatted string into a asctime string.
+ >>> rfc2822_to_asctime("Thu, 01 Jan 1970 00:00:00 +0000")
+ "Thu Jan 01 00:00:00 1970"
+ """
+ if rfc2822_string == "":
+ return asctime(gmtime(0))
+ return asctime(gmtime(rfc2822_to_gmtime_integer(rfc2822_string)))
+
+class LimitedAttrDict (dict):
+ """
+ Dict with error checking, to avoid invalid bug/comment fields.
+ """
+ _attrs = [] # override with list of valid attribute names
+ def __init__(self, **kwargs):
+ dict.__init__(self)
+ for key,value in kwargs.items():
+ self[key] = value
+ def __setitem__(self, key, item):
+ self._validate_key(key)
+ dict.__setitem__(self, key, item)
+ def _validate_key(self, key):
+ if key in self._attrs:
+ return
+ elif type(key) not in types.StringTypes:
+ raise TypeError, "Invalid attribute type %s for '%s'" % (type(key), key)
+ else:
+ raise ValueError, "Invalid attribute name '%s'" % key
+
+class Bug (LimitedAttrDict):
+ _attrs = [u"uuid",
+ u"short-name",
+ u"severity",
+ u"status",
+ u"assigned",
+ u"target",
+ u"reporter",
+ u"creator",
+ u"created",
+ u"summary",
+ u"comments"]
+ def print_to_mbox(self):
+ name,addr = email.utils.parseaddr(self["creator"])
+ print "From %s %s" % (addr, rfc2822_to_asctime(self["created"]))
+ print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN)
+ print "Date: %s" % self["created"]
+ print "From: %s" % self["creator"]
+ print "Content-Type: %s" % "text/plain; charset=utf-8"
+ print "Content-Transfer-Encoding: 8bit"
+ print "Subject: %s: %s" % (self["short-name"], self["summary"])
+ print ""
+ print self["summary"]
+ print ""
+ for comment in self["comments"]:
+ comment.print_to_mbox(self)
+
+class Comment (LimitedAttrDict):
+ _attrs = [u"uuid",
+ u"short-name",
+ u"in-reply-to",
+ u"from",
+ u"date",
+ u"body"]
+ def print_to_mbox(self, bug):
+ name,addr = email.utils.parseaddr(self["from"])
+ print "From %s %s" % (addr, rfc2822_to_asctime(self["date"]))
+ print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN)
+ print "Date: %s" % self["date"]
+ print "From: %s" % self["from"]
+ print "Content-Type: %s" % "text/plain; charset=utf-8"
+ print "Content-Transfer-Encoding: 8bit"
+ print "Subject: %s: %s" % (self["short-name"], bug["summary"])
+ if "in-reply-to" not in self.keys():
+ self["in-reply-to"] = bug["uuid"]
+ print "In-Reply-To: <%s@%s>" % (self["in-reply-to"], DEFAULT_DOMAIN)
+ print ""
+ print self["body"]
+ print ""
+
+class BE_list_handler (ContentHandler):
+ def __init__(self):
+ self.reset()
+
+ def reset(self):
+ self.bug = None
+ self.comment = None
+ self.text_field = None
+
+ def startElement(self, name, attributes):
+ if name == "bug":
+ assert self.bug == None, "Nested bugs?!"
+ assert self.comment == None
+ assert self.text_field == None
+ self.bug = Bug(comments=[])
+ elif name == "comment":
+ assert self.bug != None, "<comment> not in <bug>?"
+ assert self.text_field == None, "<comment> in text field %s?" % self.text_field
+ self.comment = Comment()
+ elif self.bug != None and self.comment == None:
+ # parse bug text field
+ if name != "comment":
+ self.text_field = name
+ self.text_data = ""
+ elif self.bug != None and self.comment != None:
+ # parse comment text field
+ self.text_field = name
+ self.text_data = ""
+
+ def endElement(self, name):
+ if name == "bug":
+ assert self.bug != None, "Invalid XML?"
+ self.bug.print_to_mbox()
+ self.bug = None
+ elif name == "comment":
+ assert self.bug != None, "<comment> not in <bug>?"
+ assert self.text_field == None, "<comment> in text field %s?" % self.text_field
+ assert self.comment != None, "Invalid XML?"
+ self.bug["comments"].append(self.comment)
+ # comments printed by bug.print_to_mbox()
+ self.comment = None
+ elif self.bug != None and self.comment == None:
+ # parse bug text field
+ self.bug[self.text_field] = unescape(self.text_data.strip())
+ self.text_field = None
+ self.text_data = None
+ elif self.bug != None and self.comment != None:
+ # parse comment text field
+ self.comment[self.text_field] = unescape(self.text_data.strip())
+ self.text_field = None
+ self.text_data = None
+
+ def characters(self, data):
+ if self.text_field != None:
+ self.text_data += data
+
+if __name__ == "__main__":
+ import sys
+
+ parser = make_parser()
+ parser.setContentHandler(BE_list_handler())
+ parser.parse(sys.stdin)
diff --git a/xml/catmutt b/xml/catmutt
new file mode 100755
index 0000000..601f14f
--- /dev/null
+++ b/xml/catmutt
@@ -0,0 +1,59 @@
+#!/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})."