diff options
Diffstat (limited to 'misc/xml/be-xml-to-mbox')
-rwxr-xr-x | misc/xml/be-xml-to-mbox | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/misc/xml/be-xml-to-mbox b/misc/xml/be-xml-to-mbox new file mode 100755 index 0000000..c8b7479 --- /dev/null +++ b/misc/xml/be-xml-to-mbox @@ -0,0 +1,220 @@ +#!/usr/bin/env python +# 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 +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 +from libbe.util.encoding import get_output_encoding +from libbe.util.utility import str_to_time as rfc2822_to_gmtime_integer +from time import asctime, gmtime +import types +try: # import core module, Python >= 2.5 + from xml.etree import ElementTree +except ImportError: # look for non-core module + from elementtree import ElementTree +from xml.sax.saxutils import unescape + + +DEFAULT_DOMAIN = "invalid.com" +DEFAULT_EMAIL = "dummy@" + DEFAULT_DOMAIN +DEFAULT_ENCODING = get_output_encoding() + +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"reporter", + u"creator", + u"created", + u"summary", + u"comments", + u"extra-strings"] + def print_to_mbox(self): + if "creator" in self: + # otherwise, probably a `be show` uuid-only bug to avoid + # root comments. + 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; charset=%s" \ + % ("text/plain", DEFAULT_ENCODING) + print "Content-Transfer-Encoding: 8bit" + print "Subject: %s: %s" % (self["short-name"], self["summary"]) + if "extra-strings" in self: + for estr in self["extra-strings"]: + print "X-Extra-String: %s" % estr + print "" + print self["summary"] + print "" + if "comments" in self: + for comment in self["comments"]: + comment.print_to_mbox(self) + def init_from_etree(self, element): + assert element.tag == "bug", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "comment": + comm = Comment() + comm.init_from_etree(field) + if "comments" in self: + self["comments"].append(comm) + else: + self["comments"] = [comm] + elif field.tag == "extra-string": + if "extra-strings" in self: + self["extra-strings"].append(text) + else: + self["extra-strings"] = [text] + else: + self[field.tag] = text + +def wrap_id(id): + if "@" not in id: + return "<%s@%s>" % (id, DEFAULT_DOMAIN) + return id + +class Comment (LimitedAttrDict): + _attrs = [u"uuid", + u"alt-id", + u"short-name", + u"in-reply-to", + u"author", + u"date", + u"content-type", + u"body", + u"extra-strings"] + def print_to_mbox(self, bug=None): + if bug == None: + bug = Bug() + bug[u"uuid"] = u"no-uuid" + name,addr = email.utils.parseaddr(self["author"]) + print "From %s %s" % (addr, rfc2822_to_asctime(self["date"])) + if "uuid" in self: id = self["uuid"] + elif "alt-id" in self: id = self["alt-id"] + else: id = None + if id != None: + print "Message-id: %s" % wrap_id(id) + if "alt-id" in self: + print "Alt-id: %s" % wrap_id(self["alt-id"]) + print "Date: %s" % self["date"] + print "From: %s" % self["author"] + subject = "" + if "short-name" in self: + subject += self["short-name"]+u": " + if "summary" in bug: + subject += bug["summary"] + else: + subject += u"no-subject" + print "Subject: %s" % subject + if "in-reply-to" not in self.keys(): + self["in-reply-to"] = bug["uuid"] + print "In-Reply-To: %s" % wrap_id(self["in-reply-to"]) + if "extra-strings" in self: + for estr in self["extra-strings"]: + print "X-Extra-String: %s" % estr + if self["content-type"].startswith("text/"): + print "Content-Transfer-Encoding: 8bit" + print "Content-Type: %s; charset=%s" \ + % (self["content-type"], DEFAULT_ENCODING) + else: + print "Content-Transfer-Encoding: base64" + print "Content-Type: %s;" % (self["content-type"]) + print "" + print self["body"] + print "" + def init_from_etree(self, element): + assert element.tag == "comment", element.tag + for field in element.getchildren(): + text = unescape(unicode(field.text).decode("unicode_escape").strip()) + if field.tag == "extra-string": + if "extra-strings" in self: + self["extra-strings"].append(text) + else: + self["extra-strings"] = [text] + else: + if field.tag == "body": + text+="\n" + self[field.tag] = text + +def print_to_mbox(element): + if element.tag == "bug": + b = Bug() + b.init_from_etree(element) + b.print_to_mbox() + elif element.tag == "comment": + c = Comment() + c.init_from_etree(element) + c.print_to_mbox() + elif element.tag in ["be-xml"]: + for elt in element.getchildren(): + print_to_mbox(elt) + +if __name__ == "__main__": + import codecs + import sys + + sys.stdin = codecs.getreader(DEFAULT_ENCODING)(sys.stdin) + sys.stdout = codecs.getwriter(DEFAULT_ENCODING)(sys.stdout) + + if len(sys.argv) == 1: # no filename given, use stdin + xml_unicode = sys.stdin.read() + else: + xml_unicode = codecs.open(sys.argv[1], "r", DEFAULT_ENCODING).read() + xml_str = xml_unicode.encode("unicode_escape").replace(r"\n", "\n") + elist = ElementTree.XML(xml_str) + print_to_mbox(elist) |