aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/comment.py
diff options
context:
space:
mode:
authorW. Trevor King <wking@drexel.edu>2008-11-22 16:14:04 -0500
committerW. Trevor King <wking@drexel.edu>2008-11-22 16:14:04 -0500
commit93801606303d79cfe9c1483cd6627cb1b93dedc7 (patch)
treea2a60422a8ce46be21a23de2f69df758255f12e1 /libbe/comment.py
parent23179f50092d91dbeab97ad2b88cdaadb79b615f (diff)
downloadbugseverywhere-93801606303d79cfe9c1483cd6627cb1b93dedc7.tar.gz
Oops, these new submods are used by the new, classified Bug & BugDir.
I'd forgotten tell bzr...
Diffstat (limited to 'libbe/comment.py')
-rw-r--r--libbe/comment.py382
1 files changed, 382 insertions, 0 deletions
diff --git a/libbe/comment.py b/libbe/comment.py
new file mode 100644
index 0000000..95dade6
--- /dev/null
+++ b/libbe/comment.py
@@ -0,0 +1,382 @@
+# Bugs Everywhere, a distributed bugtracker
+# Copyright (C) 2005 Aaron Bentley and Panometrics, Inc.
+# <abentley@panoramicfeedback.com>
+#
+# 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
+import os
+import os.path
+import time
+import textwrap
+import doctest
+
+from beuuid import uuid_gen
+import mapfile
+from tree import Tree
+import utility
+
+INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
+
+def _list_to_root(comments, bug):
+ """
+ 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.
+
+ No Comment method should use the dummy comment.
+ """
+ root_comments = []
+ uuid_map = {}
+ for comment in comments:
+ assert comment.uuid != None
+ uuid_map[comment.uuid] = comment
+ for comm in comments:
+ if comm.in_reply_to == None:
+ root_comments.append(comm)
+ else:
+ 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
+
+def loadComments(bug):
+ path = bug.get_path("comments")
+ if not os.path.isdir(path):
+ return Comment(bug, uuid=INVALID_UUID)
+ comments = []
+ for uuid in os.listdir(path):
+ if uuid.startswith('.'):
+ continue
+ comm = Comment(bug, uuid, loadNow=True)
+ comments.append(comm)
+ return _list_to_root(comments, bug)
+
+def saveComments(bug):
+ path = bug.get_path("comments")
+ bug.rcs.mkdir(path)
+ for comment in bug.comment_root.traverse():
+ comment.save()
+
+class Comment(Tree):
+ def __init__(self, bug=None, uuid=None, loadNow=False,
+ in_reply_to=None, body=None):
+ """
+ Set loadNow=True to load an old bug.
+ Set loadNow=False to create a new bug.
+
+ The uuid option is required when loadNow==True.
+
+ The in_reply_to and body options are only used if
+ loadNow==False (the default). When loadNow==True, they are
+ loaded from the bug database.
+
+ in_reply_to should be the uuid string of the parent comment.
+ """
+ Tree.__init__(self)
+ self.bug = bug
+ if bug != None:
+ self.rcs = bug.rcs
+ else:
+ self.rcs = None
+ if loadNow == True:
+ self.uuid = uuid
+ self.load()
+ else:
+ if uuid != None:
+ self.uuid = uuid
+ else:
+ self.uuid = uuid_gen()
+ self.time = time.time()
+ if self.rcs != None:
+ self.From = self.rcs.get_user_id()
+ else:
+ self.From = None
+ self.in_reply_to = in_reply_to
+ self.content_type = "text/plain"
+ self.body = body
+
+ def traverse(self, *args, **kwargs):
+ """Avoid working with the possible dummy root comment"""
+ for comment in Tree.traverse(self, *args, **kwargs):
+ if comment.uuid == INVALID_UUID:
+ continue
+ yield comment
+
+ def _clean_string(self, value):
+ """
+ >>> comm = Comment()
+ >>> comm._clean_string(None)
+ ''
+ >>> comm._clean_string("abc")
+ 'abc'
+ """
+ if value == None:
+ return ""
+ return value
+
+ def string(self, indent=0, shortname=None):
+ """
+ >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
+ >>> comm.time = utility.str_to_time("Thu, 20 Nov 2008 15:55:11 +0000")
+ >>> print comm.string(indent=2, shortname="com-1")
+ --------- Comment ---------
+ Name: com-1
+ From:
+ Date: Thu, 20 Nov 2008 15:55:11 +0000
+ <BLANKLINE>
+ Some insightful remarks
+ """
+ if shortname == None:
+ shortname = self.uuid
+ lines = []
+ lines.append("--------- Comment ---------")
+ lines.append("Name: %s" % shortname)
+ lines.append("From: %s" % self._clean_string(self.From))
+ lines.append("Date: %s" % utility.time_to_str(self.time))
+ lines.append("")
+ lines.append(textwrap.fill(self._clean_string(self.body),
+ width=(79-indent)))
+
+ istring = ' '*indent
+ sep = '\n' + istring
+ return istring + sep.join(lines).rstrip('\n')
+
+ def __str__(self):
+ """
+ >>> comm = Comment(bug=None, body="Some insightful remarks")
+ >>> comm.uuid = "com-1"
+ >>> comm.time = utility.str_to_time("Thu, 20 Nov 2008 15:55:11 +0000")
+ >>> comm.From = "Jane Doe <jdoe@example.com>"
+ >>> print comm
+ --------- Comment ---------
+ Name: com-1
+ From: Jane Doe <jdoe@example.com>
+ Date: Thu, 20 Nov 2008 15:55:11 +0000
+ <BLANKLINE>
+ Some insightful remarks
+ """
+ return self.string()
+
+ def get_path(self, name=None):
+ my_dir = os.path.join(self.bug.get_path("comments"), self.uuid)
+ if name is None:
+ return my_dir
+ assert name in ["values", "body"]
+ return os.path.join(my_dir, name)
+
+ def load(self):
+ map = mapfile.map_load(self.get_path("values"))
+ self.time = utility.str_to_time(map["Date"])
+ self.From = map["From"]
+ self.in_reply_to = map.get("In-reply-to")
+ self.content_type = map.get("Content-type", "text/plain")
+ self.body = self.rcs.get_file_contents(self.get_path("body"))
+
+ def save(self):
+ assert self.rcs != None
+ map_file = {"Date": utility.time_to_str(self.time)}
+ self._add_headers(map_file, ("From", "in_reply_to", "content_type"))
+ self.rcs.mkdir(self.get_path())
+ mapfile.map_save(self.rcs, self.get_path("values"), map_file)
+ self.rcs.set_file_contents(self.get_path("body"), self.body)
+
+ def _add_headers(self, map, names):
+ map_names = {}
+ for name in names:
+ map_names[name] = self._pyname_to_header(name)
+ self._add_attrs(map, map_names)
+
+ def _pyname_to_header(self, name):
+ return name.capitalize().replace('_', '-')
+
+ def _add_attrs(self, map, map_names):
+ for name in map_names.keys():
+ value = getattr(self, name)
+ if value is not None:
+ map[map_names[name]] = value
+
+ def remove(self):
+ for comment in self.traverse():
+ path = comment.get_path()
+ self.rcs.recursive_remove(path)
+
+ def add_reply(self, reply):
+ if reply.time != None and self.time != None:
+ assert reply.time > self.time
+ if self.uuid != INVALID_UUID:
+ reply.in_reply_to = self.uuid
+ self.append(reply)
+
+ def new_reply(self, body=None):
+ """
+ >>> comm = Comment(bug=None, body="Some insightful remarks")
+ >>> repA = comm.new_reply("Critique original comment")
+ >>> repB = repA.new_reply("Begin flamewar :p")
+ """
+ reply = Comment(self.bug, body=body)
+ self.add_reply(reply)
+ return reply
+
+ def string_thread(self, name_map={}, indent=0,
+ auto_name_map=False, bug_shortname=None):
+ """
+ Return a sting displaying a thread of comments.
+ bug_shortname is only used if auto_name_map == True.
+
+ >>> a = Comment(bug=None, uuid="a", body="Insightful remarks")
+ >>> a.time = utility.str_to_time("Thu, 20 Nov 2008 01:00:00 +0000")
+ >>> b = a.new_reply("Critique original comment")
+ >>> b.uuid = "b"
+ >>> b.time = utility.str_to_time("Thu, 20 Nov 2008 02:00:00 +0000")
+ >>> c = b.new_reply("Begin flamewar :p")
+ >>> c.uuid = "c"
+ >>> c.time = utility.str_to_time("Thu, 20 Nov 2008 03:00:00 +0000")
+ >>> d = a.new_reply("Useful examples")
+ >>> d.uuid = "d"
+ >>> d.time = utility.str_to_time("Thu, 20 Nov 2008 04:00:00 +0000")
+ >>> a.sort(key=lambda comm : comm.time)
+ >>> print a.string_thread()
+ --------- Comment ---------
+ Name: a
+ From:
+ Date: Thu, 20 Nov 2008 01:00:00 +0000
+ <BLANKLINE>
+ Insightful remarks
+ --------- Comment ---------
+ Name: b
+ From:
+ Date: Thu, 20 Nov 2008 02:00:00 +0000
+ <BLANKLINE>
+ Critique original comment
+ --------- Comment ---------
+ Name: c
+ From:
+ Date: Thu, 20 Nov 2008 03:00:00 +0000
+ <BLANKLINE>
+ Begin flamewar :p
+ --------- Comment ---------
+ Name: d
+ From:
+ Date: Thu, 20 Nov 2008 04:00:00 +0000
+ <BLANKLINE>
+ Useful examples
+ >>> print a.string_thread(auto_name_map=True, bug_shortname="bug-1")
+ --------- Comment ---------
+ Name: bug-1:1
+ From:
+ Date: Thu, 20 Nov 2008 01:00:00 +0000
+ <BLANKLINE>
+ Insightful remarks
+ --------- Comment ---------
+ Name: bug-1:2
+ From:
+ Date: Thu, 20 Nov 2008 02:00:00 +0000
+ <BLANKLINE>
+ Critique original comment
+ --------- Comment ---------
+ Name: bug-1:3
+ From:
+ Date: Thu, 20 Nov 2008 03:00:00 +0000
+ <BLANKLINE>
+ Begin flamewar :p
+ --------- Comment ---------
+ Name: bug-1:4
+ From:
+ Date: Thu, 20 Nov 2008 04:00:00 +0000
+ <BLANKLINE>
+ Useful examples
+ """
+ if auto_name_map == True:
+ name_map = {}
+ for shortname,comment in self.comment_shortnames(bug_shortname):
+ name_map[comment.uuid] = shortname
+ stringlist = []
+ for depth,comment in self.thread(flatten=True):
+ ind = 2*depth+indent
+ if comment.uuid in name_map:
+ sname = name_map[comment.uuid]
+ else:
+ sname = None
+ stringlist.append(comment.string(indent=ind, shortname=sname))
+ return '\n'.join(stringlist)
+
+ def comment_shortnames(self, bug_shortname=""):
+ """
+ Iterate through (id, comment) pairs, in time order.
+ (This is a user-friendly id, not the comment uuid).
+
+ SIDE-EFFECT : will sort the comment tree by comment.time
+
+ >>> a = Comment(bug=None, uuid="a")
+ >>> b = a.new_reply()
+ >>> b.uuid = "b"
+ >>> c = b.new_reply()
+ >>> c.uuid = "c"
+ >>> d = a.new_reply()
+ >>> d.uuid = "d"
+ >>> for id,name in a.comment_shortnames("bug-1"):
+ ... print id, name.uuid
+ bug-1:1 a
+ bug-1:2 b
+ bug-1:3 c
+ bug-1:4 d
+ """
+ self.sort(key=lambda comm : comm.time)
+ for num,comment in enumerate(self.traverse()):
+ yield ("%s:%d" % (bug_shortname, num+1), comment)
+
+ def comment_from_shortname(self, comment_shortname, *args, **kwargs):
+ """
+ Use a comment shortname to look up a comment.
+ >>> a = Comment(bug=None, uuid="a")
+ >>> b = a.new_reply()
+ >>> b.uuid = "b"
+ >>> c = b.new_reply()
+ >>> c.uuid = "c"
+ >>> d = a.new_reply()
+ >>> d.uuid = "d"
+ >>> comm = a.comment_from_shortname("bug-1:3", bug_shortname="bug-1")
+ >>> id(comm) == id(c)
+ True
+ """
+ for cur_name, comment in self.comment_shortnames(*args, **kwargs):
+ if comment_shortname == cur_name:
+ return comment
+ raise KeyError(comment_shortname)
+
+ def comment_from_uuid(self, uuid):
+ """
+ Use a comment shortname to look up a comment.
+ >>> a = Comment(bug=None, uuid="a")
+ >>> b = a.new_reply()
+ >>> b.uuid = "b"
+ >>> c = b.new_reply()
+ >>> c.uuid = "c"
+ >>> d = a.new_reply()
+ >>> d.uuid = "d"
+ >>> comm = a.comment_from_uuid("d")
+ >>> id(comm) == id(d)
+ True
+ """
+ for comment in self.traverse():
+ if comment.uuid == uuid:
+ return comment
+ raise KeyError(uuid)
+
+suite = doctest.DocTestSuite()