diff options
-rw-r--r-- | becommands/show.py | 5 | ||||
-rw-r--r-- | libbe/bug.py | 9 | ||||
-rw-r--r-- | libbe/comment.py | 24 | ||||
-rwxr-xr-x | xml/be-xml-to-mbox | 181 | ||||
-rwxr-xr-x | xml/catmutt | 59 |
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 <jdoe@example.com></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})." |