From: W. Trevor King Date: Fri, 20 Nov 2009 22:09:08 +0000 (-0500) Subject: Added Bug.from_xml() + some .from_xml() fixups. X-Git-Tag: 1.0.0~59^2~77^2~10 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=75fedab07f9e566ca1c984051d7deece4d910e2c;p=be.git Added Bug.from_xml() + some .from_xml() fixups. Moved comment.InvalidXML to utility.InvalidXML, so that bug and comment can share it. Added docstring explaining the __init__ arguments. Added indent and shortname options to Bug.xml() to match Comment.xml(). Added .extra_strings export to Comment.xml(). Converted Bug.xml() from string addition to list joining, which avoids a bunch of memory allocation/deallocation. Assorted " -> ' replacements. Elaborated doctests to check UTF-8, extra_strings, ... Added new comparison cmp_extra_strings for both bug. and comment.DEFAULT_CMP_FULL_CMP_LIST. --- diff --git a/libbe/bug.py b/libbe/bug.py index 48f8358..fecb9b7 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -25,6 +25,10 @@ import os.path import errno import time 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 import xml.sax.saxutils import doctest @@ -271,40 +275,100 @@ class Bug(settings_object.SavedSettingsObject): return str(value) return value - def xml(self, show_comments=False): - if self.bugdir == None: - shortname = self.uuid - else: - shortname = self.bugdir.bug_shortname(self) + def xml(self, indent=0, shortname=None, show_comments=False): + if shortname == None: + if self.bugdir == None: + shortname = self.uuid + else: + shortname = self.bugdir.bug_shortname(self) if self.time == None: timestring = "" else: timestring = utility.time_to_str(self.time) - info = [("uuid", self.uuid), - ("short-name", shortname), - ("severity", self.severity), - ("status", self.status), - ("assigned", self.assigned), - ("target", self.target), - ("reporter", self.reporter), - ("creator", self.creator), - ("created", timestring), - ("summary", self.summary)] - ret = '\n' + info = [('uuid', self.uuid), + ('short-name', shortname), + ('severity', self.severity), + ('status', self.status), + ('assigned', self.assigned), + ('target', self.target), + ('reporter', self.reporter), + ('creator', self.creator), + ('created', timestring), + ('summary', self.summary)] + lines = [''] for (k,v) in info: if v is not None: - ret += ' <%s>%s\n' % (k,xml.sax.saxutils.escape(v),k) + lines.append(' <%s>%s' % (k,xml.sax.saxutils.escape(v),k)) for estr in self.extra_strings: - ret += ' %s\n' % estr + lines.append(' %s\n' % estr) if show_comments == True: - comout = self.comment_root.xml_thread(auto_name_map=True, + comout = self.comment_root.xml_thread(indent=indent+2, + auto_name_map=True, bug_shortname=shortname) if len(comout) > 0: - ret += comout+'\n' - ret += '' - return ret + lines.append(comout) + lines.append('') + istring = ' '*indent + sep = '\n' + istring + return istring + sep.join(lines).rstrip('\n') + + def from_xml(self, xml_string, verbose=True): + """ + Note: If a bug uuid is given, set .alt_id to it's value. + >>> bugA = Bug(uuid="0123", summary="Need to test Bug.from_xml()") + >>> bugA.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> bugA.creator = u'Fran\xe7ois' + >>> bugA.extra_strings += ['TAG: very helpful'] + >>> commA = bugA.comment_root.new_reply(body='comment A') + >>> commB = bugA.comment_root.new_reply(body='comment B') + >>> commC = commA.new_reply(body='comment C') + >>> xml = bugA.xml(shortname="bug-1") + >>> bugB = Bug() + >>> bugB.from_xml(xml, verbose=True) + >>> bugB.xml(shortname="bug-1") == xml + False + >>> bugB.uuid = bugB.alt_id + >>> bugB.xml(shortname="bug-1") == xml + True + """ + if type(xml_string) == types.UnicodeType: + xml_string = xml_string.strip().encode('unicode_escape') + bug = ElementTree.XML(xml_string) + if bug.tag != 'bug': + raise utility.InvalidXML( \ + 'bug', bug, 'root element must be ') + tags=['uuid','short-name','severity','status','assigned','target', + 'reporter', 'creator', 'created', 'summary', 'extra-string', + 'comment'] + uuid = None + estrs = [] + for child in bug.getchildren(): + if child.tag == 'short-name': + pass + elif child.tag in tags: + if child.text == None or len(child.text) == 0: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text) + text = text.decode('unicode_escape').strip() + if child.tag == "uuid": + uuid = text + continue # don't set the bug's uuid tag. + if child.tag == 'extra-string': + estrs.append(text) + continue # don't set the bug's extra_string yet. + else: + attr_name = child.tag.replace('-','_') + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + % (child.tag, comment.tag) + if uuid not in [None, self.uuid]: + if not hasattr(self, 'alt_id') or self.alt_id == None: + self.alt_id = uuid + self.extra_strings = estrs def string(self, shortlist=False, show_comments=False): if self.bugdir == None: @@ -525,6 +589,7 @@ cmp_assigned = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "assigned") cmp_target = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "target") cmp_reporter = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "reporter") cmp_summary = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "summary") +cmp_extra_strings = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "extra_strings") # chronological rankings (newer < older) cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True) @@ -547,7 +612,8 @@ def cmp_comments(bug_1, bug_2): DEFAULT_CMP_FULL_CMP_LIST = \ (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator, - cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid) + cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid, + cmp_extra_strings) class BugCompoundComparator (object): def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): diff --git a/libbe/comment.py b/libbe/comment.py index 5f67878..678d8ba 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -51,14 +51,6 @@ class InvalidShortname(KeyError): self.shortname = shortname self.shortnames = shortnames -class InvalidXML(ValueError): - def __init__(self, element, comment): - msg = "Invalid comment xml: %s\n %s\n" \ - % (comment, ElementTree.tostring(element)) - ValueError.__init__(self, msg) - self.element = element - self.comment = comment - class MissingReference(ValueError): def __init__(self, comment): msg = "Missing reference to %s" % (comment.in_reply_to) @@ -331,27 +323,29 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ if shortname == None: shortname = self.uuid - if self.content_type.startswith("text/"): - body = (self.body or "").rstrip('\n') + if self.content_type.startswith('text/'): + body = (self.body or '').rstrip('\n') else: maintype,subtype = self.content_type.split('/',1) msg = email.mime.base.MIMEBase(maintype, subtype) - msg.set_payload(self.body or "") + msg.set_payload(self.body or '') email.encoders.encode_base64(msg) - body = base64.encodestring(self.body or "") - info = [("uuid", self.uuid), - ("alt-id", self.alt_id), - ("short-name", shortname), - ("in-reply-to", self.in_reply_to), - ("author", self._setting_attr_string("author")), - ("date", self.date), - ("content-type", self.content_type), - ("body", body)] - lines = [""] + body = base64.encodestring(self.body or '') + info = [('uuid', self.uuid), + ('alt-id', self.alt_id), + ('short-name', shortname), + ('in-reply-to', self.in_reply_to), + ('author', self._setting_attr_string('author')), + ('date', self.date), + ('content-type', self.content_type), + ('body', body)] + lines = [''] for (k,v) in info: if v != None: lines.append(' <%s>%s' % (k,xml.sax.saxutils.escape(v),k)) - lines.append("") + for estr in self.extra_strings: + lines.append(' %s\n' % estr) + lines.append('') istring = ' '*indent sep = '\n' + istring return istring + sep.join(lines).rstrip('\n') @@ -363,58 +357,61 @@ class Comment(Tree, settings_object.SavedSettingsObject): >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") >>> commA.uuid = "0123" >>> commA.date = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> commA.author = u'Fran\xe7ois' + >>> commA.extra_strings += ['TAG: very helpful'] >>> xml = commA.xml(shortname="com-1") >>> commB = Comment() - >>> commB.from_xml(xml) - >>> attrs=['uuid','alt_id','in_reply_to','author','date','content_type','body'] - >>> for attr in attrs: # doctest: +ELLIPSIS - ... if getattr(commB, attr) != getattr(commA, attr): - ... estr = "Mismatch on %s: '%s' should be '%s'" - ... args = (attr, getattr(commB, attr), getattr(commA, attr)) - ... print estr % args - Mismatch on uuid: '...' should be '0123' - Mismatch on alt_id: '0123' should be 'None' - >>> print commB.alt_id - 0123 - >>> commA.author - >>> commB.author + >>> commB.from_xml(xml, verbose=True) + >>> commB.xml(shortname="com-1") == xml + False + >>> commB.uuid = commB.alt_id + >>> commB.alt_id = None + >>> commB.xml(shortname="com-1") == xml + True """ if type(xml_string) == types.UnicodeType: - xml_string = xml_string.strip().encode("unicode_escape") + xml_string = xml_string.strip().encode('unicode_escape') comment = ElementTree.XML(xml_string) - if comment.tag != "comment": - raise InvalidXML(comment, "root element must be ") - tags=['uuid','alt-id','in-reply-to','author','date','content-type','body'] + if comment.tag != 'comment': + raise utility.InvalidXML( \ + 'comment', comment, 'root element must be ') + tags=['uuid','alt-id','in-reply-to','author','date','content-type', + 'body','extra-string'] uuid = None body = None + estrs = [] for child in comment.getchildren(): - if child.tag == "short-name": + if child.tag == 'short-name': pass elif child.tag in tags: if child.text == None or len(child.text) == 0: text = settings_object.EMPTY else: text = xml.sax.saxutils.unescape(child.text) - text = unicode(text).decode("unicode_escape").strip() - if child.tag == "uuid": + text = text.decode('unicode_escape').strip() + if child.tag == 'uuid': uuid = text - continue # don't set the bug's uuid tag. - if child.tag == "body": + continue # don't set the comment's uuid tag. + if child.tag == 'body': body = text - continue # don't set the bug's body yet. + continue # don't set the comment's body yet. + if child.tag == 'extra-string': + estrs.append(text) + continue # don't set the comment's extra_string yet. else: attr_name = child.tag.replace('-','_') setattr(self, attr_name, text) elif verbose == True: - print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + print >> sys.stderr, 'Ignoring unknown tag %s in %s' \ % (child.tag, comment.tag) if self.alt_id == None and uuid not in [None, self.uuid]: self.alt_id = uuid if body != None: - if self.content_type.startswith("text/"): - self.body = body+"\n" # restore trailing newline + if self.content_type.startswith('text/'): + self.body = body+'\n' # restore trailing newline else: self.body = base64.decodestring(body) + self.extra_strings = estrs def string(self, indent=0, shortname=None): """ @@ -726,12 +723,14 @@ cmp_author = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "autho cmp_in_reply_to = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "in_reply_to") cmp_content_type = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "content_type") cmp_body = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "body") +cmp_extra_strings = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "extra_strings") # chronological rankings (newer < older) cmp_time = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "time", invert=True) + DEFAULT_CMP_FULL_CMP_LIST = \ (cmp_time, cmp_author, cmp_content_type, cmp_body, cmp_in_reply_to, - cmp_uuid) + cmp_uuid, cmp_extra_strings) class CommentCompoundComparator (object): def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST): diff --git a/libbe/utility.py b/libbe/utility.py index 4126913..7510b16 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -29,6 +29,21 @@ import time import types import doctest +class InvalidXML(ValueError): + """ + Invalid XML while parsing for a *.from_xml() method. + type - string identifying *, e.g. "bug", "comment", ... + element - ElementTree.Element instance which caused the error + error - string describing the error + """ + def __init__(self, type, element, error): + msg = 'Invalid %s xml: %s\n %s\n' \ + % (type, error, ElementTree.tostring(element)) + ValueError.__init__(self, msg) + self.type = type + self.element = element + self.error = error + def search_parent_directories(path, filename): """ Find the file (or directory) named filename in path or in any