aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2009-11-20 17:09:08 -0500
committerW. Trevor King <wking@drexel.edu>2009-11-20 17:09:08 -0500
commit75fedab07f9e566ca1c984051d7deece4d910e2c (patch)
treebd986b4758653212b2ea580b8ab0bd29e3faa424
parentccfd3f9b65eba4535d7975395dc689eea40b0547 (diff)
downloadbugseverywhere-75fedab07f9e566ca1c984051d7deece4d910e2c.tar.gz
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.
-rw-r--r--libbe/bug.py112
-rw-r--r--libbe/comment.py97
-rw-r--r--libbe/utility.py15
3 files changed, 152 insertions, 72 deletions
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 = '<bug>\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 = ['<bug>']
for (k,v) in info:
if v is not None:
- ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k)
+ lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
for estr in self.extra_strings:
- ret += ' <extra-string>%s</extra-string>\n' % estr
+ lines.append(' <extra-string>%s</extra-string>\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 += '</bug>'
- return ret
+ lines.append(comout)
+ lines.append('</bug>')
+ 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 <comment>')
+ 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 = ["<comment>"]
+ 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 = ['<comment>']
for (k,v) in info:
if v != None:
lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
- lines.append("</comment>")
+ for estr in self.extra_strings:
+ lines.append(' <extra-string>%s</extra-string>\n' % estr)
+ lines.append('</comment>')
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 <comment>")
- 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 <comment>')
+ 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