aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/comment.py
diff options
context:
space:
mode:
authorGianluca Montecchi <gian@grys.it>2009-10-01 23:39:28 +0200
committerGianluca Montecchi <gian@grys.it>2009-10-01 23:39:28 +0200
commited4a943875d81732bfa3127eb252c2db2e3588f4 (patch)
tree1fa7da00b01b8807adac3f3e3231fc8a78b3a6c7 /libbe/comment.py
parente42729af0efc1a4c064e8875e728f9c3c1694a1d (diff)
downloadbugseverywhere-ed4a943875d81732bfa3127eb252c2db2e3588f4.tar.gz
Merged with head branch
Diffstat (limited to 'libbe/comment.py')
-rw-r--r--libbe/comment.py538
1 files changed, 0 insertions, 538 deletions
diff --git a/libbe/comment.py b/libbe/comment.py
index 9d4f744..e69de29 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -1,538 +0,0 @@
-# Bugs Everywhere, a distributed bugtracker
-# Copyright (C) 2008-2009 Chris Ball <cjb@laptop.org>
-# Thomas Habets <thomas@habets.pp.se>
-# W. Trevor King <wking@drexel.edu>
-# <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 email.mime.base, email.encoders
-import os
-import os.path
-import time
-import xml.sax.saxutils
-import textwrap
-import doctest
-
-from beuuid import uuid_gen
-from properties import Property, doc_property, local_property, \
- defaulting_property, checked_property, cached_property, \
- primed_property, change_hook_property, settings_property
-import settings_object
-import mapfile
-from tree import Tree
-import utility
-
-
-class InvalidShortname(KeyError):
- def __init__(self, shortname, shortnames):
- msg = "Invalid shortname %s\n%s" % (shortname, shortnames)
- KeyError.__init__(self, msg)
- self.shortname = shortname
- self.shortnames = shortnames
-
-
-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:
- rep = comm.in_reply_to
- if rep == None or rep == settings_object.EMPTY or rep == bug.uuid:
- 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, load_full=False):
- """
- Set load_full=True when you want to load the comment completely
- from disk *now*, rather than waiting and lazy loading as required.
- """
- 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, from_disk=True)
- if load_full == True:
- comm.load_settings()
- dummy = comm.body # force the body to load
- 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, settings_object.SavedSettingsObject):
- """
- >>> c = Comment()
- >>> c.uuid != None
- True
- >>> c.uuid = "some-UUID"
- >>> print c.content_type
- text/plain
- """
-
- settings_properties = []
- required_saved_properties = []
- _prop_save_settings = settings_object.prop_save_settings
- _prop_load_settings = settings_object.prop_load_settings
- def _versioned_property(settings_properties=settings_properties,
- required_saved_properties=required_saved_properties,
- **kwargs):
- if "settings_properties" not in kwargs:
- kwargs["settings_properties"] = settings_properties
- if "required_saved_properties" not in kwargs:
- kwargs["required_saved_properties"]=required_saved_properties
- return settings_object.versioned_property(**kwargs)
-
- @_versioned_property(name="From",
- doc="The author of the comment")
- def From(): return {}
-
- @_versioned_property(name="In-reply-to",
- doc="UUID for parent comment or bug")
- def in_reply_to(): return {}
-
- @_versioned_property(name="Content-type",
- doc="Mime type for comment body",
- default="text/plain",
- require_save=True)
- def content_type(): return {}
-
- @_versioned_property(name="Date",
- doc="An RFC 2822 timestamp for comment creation")
- def time_string(): return {}
-
- def _get_time(self):
- if self.time_string == None:
- return None
- return utility.str_to_time(self.time_string)
- def _set_time(self, value):
- self.time_string = utility.time_to_str(value)
- time = property(fget=_get_time,
- fset=_set_time,
- doc="An integer version of .time_string")
-
- def _get_comment_body(self):
- if self.rcs != None and self.sync_with_disk == True:
- import rcs
- binary = not self.content_type.startswith("text/")
- return self.rcs.get_file_contents(self.get_path("body"), binary=binary)
- def _set_comment_body(self, old=None, new=None, force=False):
- if (self.rcs != None and self.sync_with_disk == True) or force==True:
- assert new != None, "Can't save empty comment"
- binary = not self.content_type.startswith("text/")
- self.rcs.set_file_contents(self.get_path("body"), new, binary=binary)
-
- @Property
- @change_hook_property(hook=_set_comment_body)
- @cached_property(generator=_get_comment_body)
- @local_property("body")
- @doc_property(doc="The meat of the comment")
- def body(): return {}
-
- def _get_rcs(self):
- if hasattr(self.bug, "rcs"):
- return self.bug.rcs
-
- @Property
- @cached_property(generator=_get_rcs)
- @local_property("rcs")
- @doc_property(doc="A revision control system instance.")
- def rcs(): return {}
-
- def __init__(self, bug=None, uuid=None, from_disk=False,
- in_reply_to=None, body=None):
- """
- Set from_disk=True to load an old comment.
- Set from_disk=False to create a new comment.
-
- The uuid option is required when from_disk==True.
-
- The in_reply_to and body options are only used if
- from_disk==False (the default). When from_disk==True, they are
- loaded from the bug database.
-
- in_reply_to should be the uuid string of the parent comment.
- """
- Tree.__init__(self)
- settings_object.SavedSettingsObject.__init__(self)
- self.bug = bug
- self.uuid = uuid
- if from_disk == True:
- self.sync_with_disk = True
- else:
- self.sync_with_disk = False
- if uuid == None:
- self.uuid = uuid_gen()
- self.time = int(time.time()) # only save to second precision
- if self.rcs != None:
- self.From = self.rcs.get_user_id()
- self.in_reply_to = in_reply_to
- 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 _setting_attr_string(self, setting):
- value = getattr(self, setting)
- if value == settings_object.EMPTY:
- return ""
- else:
- return str(value)
-
- def xml(self, indent=0, shortname=None):
- """
- >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
- >>> comm.uuid = "0123"
- >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
- >>> print comm.xml(indent=2, shortname="com-1")
- <comment>
- <uuid>0123</uuid>
- <short-name>com-1</short-name>
- <from></from>
- <date>Thu, 01 Jan 1970 00:00:00 +0000</date>
- <content-type>text/plain</content-type>
- <body>Some
- insightful
- remarks</body>
- </comment>
- """
- if shortname == None:
- shortname = self.uuid
- 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 "")
- email.encoders.encode_base64(msg)
- body = msg.as_string()
- info = [("uuid", self.uuid),
- ("short-name", shortname),
- ("in-reply-to", self.in_reply_to),
- ("from", self._setting_attr_string("From")),
- ("date", self.time_string),
- ("content-type", self.content_type),
- ("body", body)]
- lines = ["<comment>"]
- for (k,v) in info:
- if v not in [settings_object.EMPTY, None]:
- lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
- lines.append("</comment>")
- istring = ' '*indent
- sep = '\n' + istring
- return istring + sep.join(lines).rstrip('\n')
-
- def string(self, indent=0, shortname=None):
- """
- >>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
- >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000"
- >>> print comm.string(indent=2, shortname="com-1")
- --------- Comment ---------
- Name: com-1
- From:
- Date: Thu, 01 Jan 1970 00:00:00 +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._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
- return istring + sep.join(lines).rstrip('\n')
-
- def __str__(self):
- """
- >>> comm = Comment(bug=None, body="Some insightful remarks")
- >>> comm.uuid = "com-1"
- >>> comm.time_string = "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_settings(self):
- self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
- self._setup_saved_settings()
-
- def save_settings(self):
- parent_dir = os.path.dirname(self.get_path())
- self.rcs.mkdir(parent_dir)
- self.rcs.mkdir(self.get_path())
- path = self.get_path("values")
- mapfile.map_save(self.rcs, path, self._get_saved_settings())
-
- 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)
-
- def remove(self):
- for comment in self.traverse():
- path = comment.get_path()
- self.rcs.recursive_remove(path)
-
- def add_reply(self, reply, allow_time_inversion=False):
- if self.uuid != INVALID_UUID:
- reply.in_reply_to = self.uuid
- self.append(reply)
- #raise Exception, "adding reply \n%s\n%s" % (self, 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")
- >>> repB.in_reply_to == repA.uuid
- True
- """
- 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={},
- indent=0, flatten=True,
- auto_name_map=False, bug_shortname=None):
- """
- Return a string displaying a thread of comments.
- bug_shortname is only used if auto_name_map == True.
-
- string_method_name (defaults to "string") is the name of the
- Comment method used to generate the output string for each
- Comment in the thread. The method must take the arguments
- indent and shortname.
-
- SIDE-EFFECT: if auto_name_map==True, calls comment_shortnames()
- which will sort the tree by comment.time. Avoid by calling
- name_map = {}
- for shortname,comment in comm.comment_shortnames(bug_shortname):
- name_map[comment.uuid] = shortname
- comm.sort(key=lambda c : c.From) # your sort
- comm.string_thread(name_map=name_map)
-
- >>> 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(flatten=True)
- --------- 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=flatten):
- ind = 2*depth+indent
- if comment.uuid in name_map:
- sname = name_map[comment.uuid]
- else:
- sname = None
- string_fn = getattr(comment, string_method_name)
- stringlist.append(string_fn(indent=ind, shortname=sname))
- return '\n'.join(stringlist)
-
- def xml_thread(self, name_map={}, indent=0,
- auto_name_map=False, bug_shortname=None):
- return self.string_thread(string_method_name="xml", name_map=name_map,
- indent=indent, auto_name_map=auto_name_map,
- bug_shortname=bug_shortname)
-
- def comment_shortnames(self, bug_shortname=None):
- """
- 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
- """
- if bug_shortname == None:
- bug_shortname = ""
- 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 InvalidShortname(comment_shortname,
- list(self.comment_shortnames(*args, **kwargs)))
-
- 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()