From ab1b33b702c4c84a90fd5d7ba8c6dd0078fc303a Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 6 Jul 2009 15:54:13 -0400 Subject: Added ability to show individual comments with "be show". --- libbe/cmdutil.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index edc6442..0dd8ad0 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -141,7 +141,7 @@ def option_value_pairs(options, parser): def default_complete(options, args, parser, bugid_args={}): """ - A dud complete implementation for becommands to that the + A dud complete implementation for becommands so that the --complete argument doesn't cause any problems. Use this until you've set up a command-specific complete function. @@ -149,15 +149,25 @@ def default_complete(options, args, parser, bugid_args={}): arguments taking bug shortnames and the values are functions for filtering, since that's a common enough operation. e.g. for "be open [options] BUGID" - bugid_args = {0: lambda bug : bug.active == False} + bugid_args = {0: lambda bug : bug.active == False} + A positional argument of -1 specifies all remaining arguments + (e.g in the case of "be show BUGID BUGID ..."). """ for option,value in option_value_pairs(options, parser): if value == "--complete": raise cmdutil.GetCompletions() + if len(bugid_args.keys()) > 0: + max_pos_arg = max(bugid_args.keys()) + else: + max_pos_arg = -1 for pos,value in enumerate(args): if value == "--complete": + filter = None if pos in bugid_args: filter = bugid_args[pos] + if pos > max_pos_arg and -1 in bugid_args: + filter = bugid_args[-1] + if filter != None: bugshortnames = [] try: bd = bugdir.BugDir(from_disk=True, -- cgit From 0f09cef7c497ea826df526d2d3a5b018279c4d2f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 10 Jul 2009 14:11:23 -0400 Subject: Simplified error handling in ./be Removed superfluous nesting in ./be's error catching. Also replaced KeyErrors due to unknown commands with the more specific cmdutil.UnknownCommand, since all sorts of programming errors can raise KeyErrors. Untested, since my working tree is a mess at the moment, but what could go wrong? ;) --- libbe/cmdutil.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 0dd8ad0..7414e46 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -32,10 +32,10 @@ class UserError(Exception): def __init__(self, msg): Exception.__init__(self, msg) -class UserErrorWrap(UserError): - def __init__(self, exception): - UserError.__init__(self, str(exception)) - self.exception = exception +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd class UsageError(Exception): pass @@ -58,13 +58,13 @@ def get_command(command_name): >>> get_command("asdf") Traceback (most recent call last): - UserError: Unknown command asdf + UnknownCommand: Unknown command asdf >>> repr(get_command("list")).startswith(" Date: Fri, 10 Jul 2009 17:58:49 -0400 Subject: seems to work ;) --- libbe/cmdutil.py | 8 ++++-- libbe/comment.py | 86 +++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 22 deletions(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 7414e46..ada0423 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -56,9 +56,11 @@ def iter_commands(): def get_command(command_name): """Retrieves the module for a user command - >>> get_command("asdf") - Traceback (most recent call last): - UnknownCommand: Unknown command asdf + >>> try: + ... get_command("asdf") + ... except UnknownCommand, e: + ... print e + Unknown command asdf >>> repr(get_command("list")).startswith(" # Thomas Habets # W. Trevor King -# # # 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 @@ -22,8 +21,11 @@ import email.mime.base, email.encoders import os import os.path import time +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 textwrap import doctest from beuuid import uuid_gen @@ -43,16 +45,25 @@ 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 INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" -def _list_to_root(comments, bug): +def list_to_root(comments, bug, root=None): """ - Convert a raw list of comments to single (dummy) root comment. We - use a dummy root comment, because there can be several comment - threads rooted on the same parent bug. To simplify comment - interaction, we condense these threads into a single thread with a - Comment dummy root. + Convert a raw list of comments to single root comment. We use a + dummy root comment by default, because there can be several + comment threads rooted on the same parent bug. To simplify + comment interaction, we condense these threads into a single + thread with a Comment dummy root. Can also be used to append + a list of subcomments to a non-dummy root comment, so long as + all the new comments are descendants of the root comment. No Comment method should use the dummy comment. """ @@ -61,6 +72,10 @@ def _list_to_root(comments, bug): for comment in comments: assert comment.uuid != None uuid_map[comment.uuid] = comment + if root == None: + root = Comment(bug, uuid=INVALID_UUID) + else: + uuid_map[root.uuid] = root for comm in comments: rep = comm.in_reply_to if rep == None or rep == settings_object.EMPTY or rep == bug.uuid: @@ -69,9 +84,8 @@ def _list_to_root(comments, bug): parentUUID = comm.in_reply_to parent = uuid_map[parentUUID] parent.add_reply(comm) - dummy_root = Comment(bug, uuid=INVALID_UUID) - dummy_root.extend(root_comments) - return dummy_root + root.extend(root_comments) + return root def loadComments(bug, load_full=False): """ @@ -90,7 +104,7 @@ def loadComments(bug, load_full=False): comm.load_settings() dummy = comm.body # force the body to load comments.append(comm) - return _list_to_root(comments, bug) + return list_to_root(comments, bug) def saveComments(bug): path = bug.get_path("comments") @@ -265,6 +279,47 @@ class Comment(Tree, settings_object.SavedSettingsObject): sep = '\n' + istring return istring + sep.join(lines).rstrip('\n') + def from_xml(self, xml_string, verbose=True): + """ + Warning: does not check for comment uuid collision. + >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") + >>> commA.uuid = "0123" + >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" + >>> xml = commA.xml(shortname="com-1") + >>> commB = Comment() + >>> commB.from_xml(xml) + >>> attrs=['uuid','in_reply_to','From','time_string','content_type','body'] + >>> for attr in attrs: + ... 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 + """ + comment = ElementTree.XML(xml_string) + if comment.tag != "comment": + raise InvalidXML(comment, "root element must be ") + tags=['uuid','in-reply-to','from','date','content-type','body'] + for child in comment.getchildren(): + if child.tag == "short-name": + pass + elif child.tag in tags: + if child.text == None: + text = settings_object.EMPTY + else: + text = xml.sax.saxutils.unescape(child.text.strip()) + if child.tag == 'from': + attr_name = "From" + elif child.tag == 'date': + attr_name = 'time_string' + else: + attr_name = child.tag.replace('-','_') + if attr_name == "body": + text += '\n' # replace strip()ed trailing newline + setattr(self, attr_name, text) + elif verbose == True: + print >> sys.stderr, "Ignoring unknown tag %s in %s" \ + % (child.tag, comment.tag) + def string(self, indent=0, shortname=None): """ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") @@ -287,13 +342,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): lines.append("From: %s" % (self._setting_attr_string("From"))) lines.append("Date: %s" % self.time_string) lines.append("") - #lines.append(textwrap.fill(self.body or "", - # width=(79-indent))) if self.content_type.startswith("text/"): lines.extend((self.body or "").splitlines()) else: lines.append("Content type %s not printable. Try XML output instead" % self.content_type) - # some comments shouldn't be wrapped... istring = ' '*indent sep = '\n' + istring @@ -335,9 +387,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): def save(self): assert self.body != None, "Can't save blank comment" - #if self.in_reply_to == None: - # raise Exception, str(self)+'\n'+str(self.settings)+'\n'+str(self._settings_loaded) - #assert self.in_reply_to != None, "Comment must be a reply to something" self.save_settings() self._set_comment_body(new=self.body, force=True) @@ -362,7 +411,6 @@ class Comment(Tree, settings_object.SavedSettingsObject): """ reply = Comment(self.bug, body=body) self.add_reply(reply) - #raise Exception, "new reply added (%s),\n%s\n%s\n\t--%s--" % (body, self, reply, reply.in_reply_to) return reply def string_thread(self, string_method_name="string", name_map={}, -- cgit From f3d16a7f477e1470aeb4bf342888c082ee1b7d67 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 11 Jul 2009 06:46:49 -0400 Subject: Fixed minor doctest failure in cmdutil.py --- libbe/cmdutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index ada0423..e56cdea 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -60,7 +60,7 @@ def get_command(command_name): ... get_command("asdf") ... except UnknownCommand, e: ... print e - Unknown command asdf + Unknown command 'asdf' >>> repr(get_command("list")).startswith(" Date: Sat, 11 Jul 2009 07:13:17 -0400 Subject: Fixed versioned_property(default=None, generator=None) defaults. Now the behavior conforms to the docstring: If both default and generator are None, then the property will be a defaulting property which defaults to None. --- libbe/settings_object.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'libbe') diff --git a/libbe/settings_object.py b/libbe/settings_object.py index 7326d1b..d362bf9 100644 --- a/libbe/settings_object.py +++ b/libbe/settings_object.py @@ -125,7 +125,7 @@ def versioned_property(name, doc, required_saved_properties.append(name) def decorator(funcs): fulldoc = doc - if default != None: + if default != None or generator == None: defaulting = defaulting_property(default=default, null=EMPTY, default_mutable=mutable) fulldoc += "\n\nThis property defaults to %s" % default @@ -145,7 +145,7 @@ def versioned_property(name, doc, settings = settings_property(name=name, null=UNPRIMED) docp = doc_property(doc=fulldoc) deco = hooked(primed(settings(docp(funcs)))) - if default != None: + if default != None or generator == None: deco = defaulting(deco) if generator != None: deco = cached(deco) @@ -235,7 +235,7 @@ class SavedSettingsObjectTests(unittest.TestCase): # access missing setting self.failUnless(t._settings_loaded == False, t._settings_loaded) self.failUnless(len(t.settings) == 0, len(t.settings)) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) # accessing t.content_type triggers the priming, which runs # t._setup_saved_settings, which fills out t.settings with # EMPTY data. t._settings_loaded is still false though, since @@ -250,16 +250,16 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) # now we set a value - t.content_type = None - self.failUnless(t.settings["Content-type"] == None, + t.content_type = 5 + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) - self.failUnless(t.content_type == None, t.content_type) - self.failUnless(t.settings["Content-type"] == None, + self.failUnless(t.content_type == 5, t.content_type) + self.failUnless(t.settings["Content-type"] == 5, t.settings["Content-type"]) # now we set another value t.content_type = "text/plain" @@ -273,7 +273,7 @@ class SavedSettingsObjectTests(unittest.TestCase): self.failUnless(t._settings_loaded == True, t._settings_loaded) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) - self.failUnless(t.content_type == EMPTY, t.content_type) + self.failUnless(t.content_type == None, t.content_type) self.failUnless(len(t.settings) == 1, len(t.settings)) self.failUnless(t.settings["Content-type"] == EMPTY, t.settings["Content-type"]) @@ -376,7 +376,7 @@ class SavedSettingsObjectTests(unittest.TestCase): t.load_settings() self.failUnless(SAVES == [], SAVES) self.failUnless(t._settings_loaded == True, t._settings_loaded) - self.failUnless(t.list_type == EMPTY, t.list_type) + self.failUnless(t.list_type == None, t.list_type) self.failUnless(SAVES == [ "'None' -> ''" ], SAVES) -- cgit From f87355fddc2a931e3f984d074a713f4313d5f709 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 11 Jul 2009 07:46:22 -0400 Subject: Adjustments to new versioned_property behavior. Also adjusted libbe/comment.py to move to user-specified alt_ids, rather than uuids. --- libbe/bug.py | 16 ++++++++-------- libbe/comment.py | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 17 deletions(-) (limited to 'libbe') diff --git a/libbe/bug.py b/libbe/bug.py index dfa49f2..611a9cc 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -68,7 +68,7 @@ def load_severities(severity_def): global severity_values global severity_description global severity_index - if severity_def == settings_object.EMPTY: + if severity_def == None: return severity_values = tuple([val for val,description in severity_def]) severity_description = dict(severity_def) @@ -88,9 +88,9 @@ def load_status(active_status_def, inactive_status_def): global status_values global status_description global status_index - if active_status_def == settings_object.EMPTY: + if active_status_def == None: active_status_def = globals()["active_status_def"] - if inactive_status_def == settings_object.EMPTY: + if inactive_status_def == None: inactive_status_def = globals()["inactive_status_def"] active_status_values = tuple([val for val,description in active_status_def]) inactive_status_values = tuple([val for val,description in inactive_status_def]) @@ -178,7 +178,7 @@ class Bug(settings_object.SavedSettingsObject): def time_string(): return {} def _get_time(self): - if self.time_string == None or self.time_string == settings_object.EMPTY: + if self.time_string in [None, settings_object.EMPTY]: return None return utility.str_to_time(self.time_string) def _set_time(self, value): @@ -255,7 +255,7 @@ class Bug(settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -283,7 +283,7 @@ class Bug(settings_object.SavedSettingsObject): ("summary", self.summary)] ret = '\n' for (k,v) in info: - if v is not settings_object.EMPTY: + if v is not None: ret += ' <%s>%s\n' % (k,xml.sax.saxutils.escape(v),k) for estr in self.extra_strings: ret += ' %s\n' % estr @@ -477,8 +477,8 @@ def cmp_attr(bug_1, bug_2, attr, invert=False): return 1 val_1 = getattr(bug_1, attr) val_2 = getattr(bug_2, attr) - if val_1 == settings_object.EMPTY: val_1 = None - if val_2 == settings_object.EMPTY: val_2 = None + if val_1 == None: val_1 = None + if val_2 == None: val_2 = None if invert == True : return -cmp(val_1, val_2) diff --git a/libbe/comment.py b/libbe/comment.py index c4000fa..f573cea 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -72,13 +72,16 @@ def list_to_root(comments, bug, root=None): for comment in comments: assert comment.uuid != None uuid_map[comment.uuid] = comment + for comment in comments: + if comment.alt_id != None and comment.alt_id not in uuid_map: + uuid_map[comment.alt_id] = comment if root == None: root = Comment(bug, uuid=INVALID_UUID) else: uuid_map[root.uuid] = root for comm in comments: rep = comm.in_reply_to - if rep == None or rep == settings_object.EMPTY or rep == bug.uuid: + if rep == None or rep == bug.uuid: root_comments.append(comm) else: parentUUID = comm.in_reply_to @@ -136,6 +139,10 @@ class Comment(Tree, settings_object.SavedSettingsObject): kwargs["required_saved_properties"]=required_saved_properties return settings_object.versioned_property(**kwargs) + @_versioned_property(name="Alt-id", + doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.") + def alt_id(): return {} + @_versioned_property(name="From", doc="The author of the comment") def From(): return {} @@ -231,7 +238,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): def _setting_attr_string(self, setting): value = getattr(self, setting) - if value == settings_object.EMPTY: + if value in [None, settings_object.EMPTY]: return "" else: return str(value) @@ -264,6 +271,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): email.encoders.encode_base64(msg) body = msg.as_string() info = [("uuid", self.uuid), + ("alt-id", self.alt_id), ("short-name", shortname), ("in-reply-to", self.in_reply_to), ("from", self._setting_attr_string("From")), @@ -272,7 +280,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): ("body", body)] lines = [""] for (k,v) in info: - if v not in [settings_object.EMPTY, None]: + if v != None: lines.append(' <%s>%s' % (k,xml.sax.saxutils.escape(v),k)) lines.append("") istring = ' '*indent @@ -281,33 +289,44 @@ class Comment(Tree, settings_object.SavedSettingsObject): def from_xml(self, xml_string, verbose=True): """ - Warning: does not check for comment uuid collision. + Note: If alt-id is not given, translates any fields to + fields. >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n") >>> commA.uuid = "0123" >>> commA.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" >>> xml = commA.xml(shortname="com-1") >>> commB = Comment() >>> commB.from_xml(xml) - >>> attrs=['uuid','in_reply_to','From','time_string','content_type','body'] - >>> for attr in attrs: + >>> attrs=['uuid','alt_id','in_reply_to','From','time_string','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.From + >>> commB.From """ comment = ElementTree.XML(xml_string) if comment.tag != "comment": raise InvalidXML(comment, "root element must be ") - tags=['uuid','in-reply-to','from','date','content-type','body'] + tags=['uuid','alt-id','in-reply-to','from','date','content-type','body'] + uuid = None for child in comment.getchildren(): if child.tag == "short-name": pass elif child.tag in tags: - if child.text == None: + if child.text == None or len(child.text) == 0: text = settings_object.EMPTY else: text = xml.sax.saxutils.unescape(child.text.strip()) - if child.tag == 'from': + if child.tag == "uuid": + uuid = text + continue # don't set the bug's uuid tag. + elif child.tag == 'from': attr_name = "From" elif child.tag == 'date': attr_name = 'time_string' @@ -319,6 +338,8 @@ class Comment(Tree, settings_object.SavedSettingsObject): elif verbose == True: print >> sys.stderr, "Ignoring unknown tag %s in %s" \ % (child.tag, comment.tag) + if self.alt_id == None and uuid != None: + self.alt_id = uuid def string(self, indent=0, shortname=None): """ -- cgit From c5fcffdcf6944eecc8fac0582ab938961eb987a8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 11 Jul 2009 08:01:45 -0400 Subject: "be comment --xml" now translates comment uuids to alt_ids. --- libbe/comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index f573cea..d4d47a8 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -338,7 +338,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): elif verbose == True: print >> sys.stderr, "Ignoring unknown tag %s in %s" \ % (child.tag, comment.tag) - if self.alt_id == None and uuid != None: + if self.alt_id == None and uuid not in [None, self.uuid]: self.alt_id = uuid def string(self, indent=0, shortname=None): -- cgit From a03fea1b2b2eeb95d8ab62c49724bbf0cf631f19 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 11 Jul 2009 08:13:34 -0400 Subject: Removed from copyright blurbs. These didn't work with my update_copyright.sh. I went with Aaron Bentley and Panometrics, Inc. instead of Aaron Bentley and Panometrics, Inc. just because of line length, but I'm open to convincing if people prefer the latter... --- libbe/arch.py | 1 - libbe/beuuid.py | 1 - libbe/bug.py | 1 - libbe/bugdir.py | 1 - libbe/bzr.py | 1 - libbe/cmdutil.py | 1 - libbe/config.py | 1 - libbe/diff.py | 1 - libbe/editor.py | 1 - libbe/encoding.py | 1 - libbe/mapfile.py | 1 - libbe/plugin.py | 1 - libbe/rcs.py | 1 - libbe/utility.py | 1 - 14 files changed, 14 deletions(-) (limited to 'libbe') diff --git a/libbe/arch.py b/libbe/arch.py index 3051b34..8f7603b 100644 --- a/libbe/arch.py +++ b/libbe/arch.py @@ -2,7 +2,6 @@ # Ben Finney # James Rowe # W. Trevor King -# # # 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 diff --git a/libbe/beuuid.py b/libbe/beuuid.py index de67cb7..020ea9f 100644 --- a/libbe/beuuid.py +++ b/libbe/beuuid.py @@ -1,5 +1,4 @@ # Copyright (C) 2008-2009 W. Trevor King -# # # 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 diff --git a/libbe/bug.py b/libbe/bug.py index 611a9cc..8cb8a0a 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -1,7 +1,6 @@ # Copyright (C) 2008-2009 Chris Ball # Thomas Habets # W. Trevor King -# # # 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 diff --git a/libbe/bugdir.py b/libbe/bugdir.py index d4a39cb..67ff074 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -3,7 +3,6 @@ # Chris Ball # Oleg Romanyshyn # W. Trevor King -# # # 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 diff --git a/libbe/bzr.py b/libbe/bzr.py index d73392a..56a1648 100644 --- a/libbe/bzr.py +++ b/libbe/bzr.py @@ -2,7 +2,6 @@ # Ben Finney # Marien Zwart # W. Trevor King -# # # 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 diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index e56cdea..b39aa51 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Oleg Romanyshyn # W. Trevor King -# # # 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 diff --git a/libbe/config.py b/libbe/config.py index 7f600a5..fafb4f0 100644 --- a/libbe/config.py +++ b/libbe/config.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King -# # # 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 diff --git a/libbe/diff.py b/libbe/diff.py index a349e14..13efc9f 100644 --- a/libbe/diff.py +++ b/libbe/diff.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King -# # # 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 diff --git a/libbe/editor.py b/libbe/editor.py index 6638ed9..4bab0fa 100644 --- a/libbe/editor.py +++ b/libbe/editor.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King -# # # 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 diff --git a/libbe/encoding.py b/libbe/encoding.py index bdac98e..84c360a 100644 --- a/libbe/encoding.py +++ b/libbe/encoding.py @@ -1,6 +1,5 @@ # Bugs Everywhere, a distributed bugtracker # Copyright (C) 2008-2009 W. Trevor King -# # # 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 diff --git a/libbe/mapfile.py b/libbe/mapfile.py index 9ff2215..40386e2 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King -# # # 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 diff --git a/libbe/plugin.py b/libbe/plugin.py index 3c7c753..a21ba91 100644 --- a/libbe/plugin.py +++ b/libbe/plugin.py @@ -1,7 +1,6 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # Marien Zwart # W. Trevor King -# # # 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 diff --git a/libbe/rcs.py b/libbe/rcs.py index 2c416f4..844920a 100644 --- a/libbe/rcs.py +++ b/libbe/rcs.py @@ -3,7 +3,6 @@ # Ben Finney # Chris Ball # W. Trevor King -# # # 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 diff --git a/libbe/utility.py b/libbe/utility.py index 8a0f318..2e8918e 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -1,6 +1,5 @@ # Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. # W. Trevor King -# # # 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 -- cgit From 5208a2808a2a87cbfa9d8589d5ac254aa0dea64f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 11 Jul 2009 09:46:38 -0400 Subject: Updating "be set --help" and "be status --help". I don't really like the "defaults to None" for the settings that have funky initialization procedures (most of them :p), but I'm not sure how to handle that cleanly yet. Perhaps be set --current I also need to find a method of adding complicated settings like the nested lists for severities, etc from the "be set" commandline. --- libbe/bugdir.py | 2 +- libbe/settings_object.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'libbe') diff --git a/libbe/bugdir.py b/libbe/bugdir.py index 67ff074..fed9aa3 100644 --- a/libbe/bugdir.py +++ b/libbe/bugdir.py @@ -135,7 +135,7 @@ class BugDir (list, settings_object.SavedSettingsObject): return settings_object.versioned_property(**kwargs) @_versioned_property(name="target", - doc="The current project development target") + doc="The current project development target.") def target(): return {} def _guess_encoding(self): diff --git a/libbe/settings_object.py b/libbe/settings_object.py index d362bf9..1dadd0a 100644 --- a/libbe/settings_object.py +++ b/libbe/settings_object.py @@ -128,14 +128,14 @@ def versioned_property(name, doc, if default != None or generator == None: defaulting = defaulting_property(default=default, null=EMPTY, default_mutable=mutable) - fulldoc += "\n\nThis property defaults to %s" % default + fulldoc += "\n\nThis property defaults to %s." % default if generator != None: cached = cached_property(generator=generator, initVal=EMPTY, mutable=mutable) - fulldoc += "\n\nThis property is generated with %s" % generator + fulldoc += "\n\nThis property is generated with %s." % generator if check_fn != None: fn_checked = fn_checked_property(value_allowed_fn=check_fn) - fulldoc += "\n\nThis property is checked with %s" % check_fn + fulldoc += "\n\nThis property is checked with %s." % check_fn if allowed != None: checked = checked_property(allowed=allowed) fulldoc += "\n\nThe allowed values for this property are: %s." \ -- cgit From 76d552e5401df990a601f245f30f45d7c13cdd1e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 12 Jul 2009 08:38:40 -0400 Subject: Added be-mbox-to-xml. Reworked to allow "be comment" to handle unicode strings (see bug e4ed63f6-9000-4d0b-98c3-487269140141). The solution was to escape all the unicode to produce and ASCII string before calling ElementTree.XML, and then converting back to unicode afterwards. Added a unicode-containing comment to the end of bug f7ccd916-b5c7-4890-a2e3-8c8ace17ae3a so that there's a handy unicode comment for testing. XML headers (e.g. '') are now added to all xml output from be. Switched non-text/* encoding library to base64 instead of email.encoders, which makes that code in libbe/comment.py simpler. Changed libbe/mapfile.py error encoding from string_escape to unicode_escape so it can handle unicode. Everything's still untested, and be-xml-to-mbox doesn't handle unicode yet, but I felt this commit was getting a bit unwieldy ;). --- libbe/comment.py | 24 +++++++++++++++++++----- libbe/mapfile.py | 4 ++-- 2 files changed, 21 insertions(+), 7 deletions(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index d4d47a8..7acbbb1 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -17,10 +17,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA -import email.mime.base, email.encoders +import base64 import os import os.path import time +import types try: # import core module, Python >= 2.5 from xml.etree import ElementTree except ImportError: # look for non-core module @@ -80,10 +81,13 @@ def list_to_root(comments, bug, root=None): else: uuid_map[root.uuid] = root for comm in comments: + if comm.in_reply_to == INVALID_UUID: + comm.in_reply_to = None rep = comm.in_reply_to if rep == None or rep == bug.uuid: root_comments.append(comm) else: + print comm.in_reply_to parentUUID = comm.in_reply_to parent = uuid_map[parentUUID] parent.add_reply(comm) @@ -269,7 +273,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): msg = email.mime.base.MIMEBase(maintype, subtype) msg.set_payload(self.body or "") email.encoders.encode_base64(msg) - body = msg.as_string() + body = base64.encodestring(self.body or "") info = [("uuid", self.uuid), ("alt-id", self.alt_id), ("short-name", shortname), @@ -310,11 +314,14 @@ class Comment(Tree, settings_object.SavedSettingsObject): >>> commA.From >>> commB.From """ + if type(xml_string) == types.UnicodeType: + 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','from','date','content-type','body'] uuid = None + body = None for child in comment.getchildren(): if child.tag == "short-name": pass @@ -322,24 +329,31 @@ class Comment(Tree, settings_object.SavedSettingsObject): if child.text == None or len(child.text) == 0: text = settings_object.EMPTY else: - text = xml.sax.saxutils.unescape(child.text.strip()) + text = xml.sax.saxutils.unescape(child.text) + text = unicode(text).decode("unicode_escape").strip() if child.tag == "uuid": uuid = text continue # don't set the bug's uuid tag. + if child.tag == "body": + body = text + continue # don't set the bug's body yet. elif child.tag == 'from': attr_name = "From" elif child.tag == 'date': attr_name = 'time_string' else: attr_name = child.tag.replace('-','_') - if attr_name == "body": - text += '\n' # replace strip()ed trailing newline setattr(self, attr_name, text) elif verbose == True: 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 + else: + self.body = base64.decodestring(body) def string(self, indent=0, shortname=None): """ diff --git a/libbe/mapfile.py b/libbe/mapfile.py index 40386e2..b183bfe 100644 --- a/libbe/mapfile.py +++ b/libbe/mapfile.py @@ -67,9 +67,9 @@ def generate(map): assert(':' not in key) assert(len(key) > 0) except AssertionError: - raise IllegalKey(key.encode('string_escape')) + raise IllegalKey(unicode(key).encode('unicode_escape')) if "\n" in map[key]: - raise IllegalValue(map[key].encode('string_escape')) + raise IllegalValue(unicode(map[key]).encode('unicode_escape')) lines = [] for key in keys: -- cgit From 51dee303dd3673dc702d54107c272f4180f43fb7 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 12 Jul 2009 08:52:50 -0400 Subject: Minor fixes to get unittests working again. --- libbe/comment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'libbe') diff --git a/libbe/comment.py b/libbe/comment.py index 7acbbb1..a085741 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -87,7 +87,6 @@ def list_to_root(comments, bug, root=None): if rep == None or rep == bug.uuid: root_comments.append(comm) else: - print comm.in_reply_to parentUUID = comm.in_reply_to parent = uuid_map[parentUUID] parent.add_reply(comm) @@ -351,7 +350,7 @@ class Comment(Tree, settings_object.SavedSettingsObject): self.alt_id = uuid if body != None: if self.content_type.startswith("text/"): - self.body = body + self.body = body+"\n" # restore trailing newline else: self.body = base64.decodestring(body) -- cgit From 096ab4f25e368641de3c57266d1611797eb7497e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 12 Jul 2009 09:50:24 -0400 Subject: Added timezone handling to libbe.utility.str_to_time. --- libbe/utility.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'libbe') diff --git a/libbe/utility.py b/libbe/utility.py index 2e8918e..e16b94a 100644 --- a/libbe/utility.py +++ b/libbe/utility.py @@ -20,6 +20,7 @@ import os import shutil import tempfile import time +import types import doctest @@ -76,17 +77,34 @@ def time_to_str(time_val): return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val)) def str_to_time(str_time): - """Convert an RFC 2822-fomatted string into a time falue. + """Convert an RFC 2822-fomatted string into a time value. >>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") 0 >>> q = time.time() >>> str_to_time(time_to_str(q)) == int(q) True + >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000") + 36000 """ - return calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timezone_str = str_time[-5:] + if timezone_str != "+0000": + str_time = str_time.replace(timezone_str, "+0000") + time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) + timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT + timezone_tuple = time.strptime(timezone_str[1:], "%H%M") + timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60 + return time_val + timesign*timezone def handy_time(time_val): return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val)) +def time_to_gmtime(str_time): + """Convert an RFC 2822-fomatted string to a GMT string. + >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000") + 'Thu, 01 Jan 1970 10:00:00 +0000' + """ + time_val = str_to_time(str_time) + return time_to_str(time_val) + suite = doctest.DocTestSuite() -- cgit From b5c4896d7ffd219a3118a3e6885db5956bf79e55 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 12 Jul 2009 14:32:55 -0400 Subject: Added "be comment --xml --ignore-missing-references ID COMMENT". Now you don't have to edit them out by hand. --- libbe/cmdutil.py | 3 ++- libbe/comment.py | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index b39aa51..0bee9db 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -71,7 +71,8 @@ def get_command(command_name): def execute(cmd, args): enc = encoding.get_encoding() - get_command(cmd).execute([a.decode(enc) for a in args]) + cmd = get_command(cmd) + cmd.execute([a.decode(enc) for a in args]) return 0 def help(cmd=None): diff --git a/libbe/comment.py b/libbe/comment.py index a085741..68deaf3 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -20,6 +20,7 @@ import base64 import os import os.path +import sys import time import types try: # import core module, Python >= 2.5 @@ -54,9 +55,17 @@ class InvalidXML(ValueError): self.element = element self.comment = comment +class MissingReference(ValueError): + def __init__(self, comment): + msg = "Missing reference to %s" % (comment.in_reply_to) + ValueError.__init__(self, msg) + self.reference = comment.in_reply_to + self.comment = comment + INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!" -def list_to_root(comments, bug, root=None): +def list_to_root(comments, bug, root=None, + ignore_missing_references=False): """ Convert a raw list of comments to single root comment. We use a dummy root comment by default, because there can be several @@ -88,8 +97,17 @@ def list_to_root(comments, bug, root=None): root_comments.append(comm) else: parentUUID = comm.in_reply_to - parent = uuid_map[parentUUID] - parent.add_reply(comm) + try: + parent = uuid_map[parentUUID] + parent.add_reply(comm) + except KeyError, e: + if ignore_missing_references == True: + print >> sys.stderr, \ + "Ignoring missing reference to %s" % parentUUID + comm.in_reply_to = None + root_comments.append(comm) + else: + raise MissingReference(comm) root.extend(root_comments) return root -- cgit From ffb8e95faed6fec4e1b06d7bfba3cc21661c677f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 13 Jul 2009 07:23:16 -0400 Subject: Use CmdOptionParser in "be". All the becommands have been using cmdutil CmdOptionParser for a long time, but "be" parsed its options by hand. Now it used CmdOptionParser, which makes adding new options much easier. --- libbe/cmdutil.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index 0bee9db..a91b1c7 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -75,7 +75,7 @@ def execute(cmd, args): cmd.execute([a.decode(enc) for a in args]) return 0 -def help(cmd=None): +def help(cmd=None, parser=None): if cmd != None: return get_command(cmd).help() else: @@ -84,17 +84,15 @@ def help(cmd=None): cmdlist.append((name, module.__desc__)) longest_cmd_len = max([len(name) for name,desc in cmdlist]) ret = ["Bugs Everywhere - Distributed bug tracking", - "", - "usage: be [command] [command_options ...] [command_args ...]", - "or: be help", - "or: be help [command]", - "", - "Supported commands"] + "", "Supported commands"] for name, desc in cmdlist: numExtraSpaces = longest_cmd_len-len(name) ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc)) - - return "\n".join(ret) + ret.extend(["", "Run", " be help [command]", "for more information."]) + longhelp = "\n".join(ret) + if parser == None: + return longhelp + return parser.help_str() + "\n" + longhelp def completions(cmd): parser = get_command(cmd).get_parser() @@ -108,6 +106,13 @@ def raise_get_help(option, opt, value, parser): def raise_get_completions(option, opt, value, parser): print "got completion arg" + if hasattr(parser, "command") and parser.command == "be": + comps = [] + for command, module in iter_commands(): + comps.append(command) + for opt in parser.option_list: + comps.append(opt.get_opt_string()) + raise GetCompletions(comps) raise GetCompletions(completions(sys.argv[1])) class CmdOptionParser(optparse.OptionParser): -- cgit From 17adbfb1c04684b986bf2c97cc4fa5197198aadc Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 13 Jul 2009 07:46:58 -0400 Subject: Fixed "be --dir --complete" --- libbe/cmdutil.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'libbe') diff --git a/libbe/cmdutil.py b/libbe/cmdutil.py index a91b1c7..7589241 100644 --- a/libbe/cmdutil.py +++ b/libbe/cmdutil.py @@ -15,6 +15,7 @@ # 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 +import glob import optparse import os from textwrap import TextWrapper @@ -187,6 +188,13 @@ def default_complete(options, args, parser, bugid_args={}): raise GetCompletions(bugshortnames) raise GetCompletions() +def complete_path(path): + """List possible path completions for path.""" + comps = glob.glob(path+"*") + glob.glob(path+"/*") + if len(comps) == 1 and os.path.isdir(comps[0]): + comps.extend(glob.glob(comps[0]+"/*")) + return comps + def underlined(instring): """Produces a version of a string that is underlined with '=' -- cgit