diff options
author | W. Trevor King <wking@drexel.edu> | 2008-12-01 14:42:20 -0500 |
---|---|---|
committer | W. Trevor King <wking@drexel.edu> | 2008-12-01 14:42:20 -0500 |
commit | a98aafc8572bb826a0fda1b6bca0011fc4ef126a (patch) | |
tree | be9f356cf9a737237c360200cdd41fba3ba1ac7b /libbe/comment.py | |
parent | 5c3b1999a162d6b1434e9746717f293ea24462aa (diff) | |
download | bugseverywhere-a98aafc8572bb826a0fda1b6bca0011fc4ef126a.tar.gz |
Added decorator-style properties to libbe/comment.py.
Also some typo corrections and some reworking of bug/bugdir to better
support the lazier loading.
Diffstat (limited to 'libbe/comment.py')
-rw-r--r-- | libbe/comment.py | 228 |
1 files changed, 162 insertions, 66 deletions
diff --git a/libbe/comment.py b/libbe/comment.py index ab0973d..0fd871c 100644 --- a/libbe/comment.py +++ b/libbe/comment.py @@ -23,10 +23,22 @@ 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 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): @@ -55,7 +67,11 @@ def _list_to_root(comments, bug): dummy_root.extend(root_comments) return dummy_root -def loadComments(bug): +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) @@ -64,6 +80,9 @@ def loadComments(bug): 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) @@ -73,15 +92,108 @@ def saveComments(bug): for comment in bug.comment_root.traverse(): comment.save() -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 +# Define an invalid value for our properties, distinct from None, +# which shows that a property has been initialized but has no value. +EMPTY = -1 class Comment(Tree): + """ + >>> c = Comment() + >>> c.uuid != None + True + >>> c.uuid = "some-UUID" + >>> print c.content_type + text/plain + """ + + def _save_settings(self, old, new): + if self.sync_with_disk==True: + self.save_settings() + def _load_settings(self): + if self.sync_with_disk==True and self._settings_loaded==False: + self.load_settings() + else: + for property in self.settings_properties: + if property not in self.settings: + self.settings[property] = EMPTY + + settings_properties = [] + required_saved_properties = ['Content-type'] # to protect against future changes in default values + def _setting_name_to_attr_name(self, name): + "Helper for looking up default vals for required-saved-properties" + return name.lower().replace('-', '_') + + def _versioned_property(name, doc, default=None, save=_save_settings, load=_load_settings, setprops=settings_properties, allowed=None): + "Combine the common decorators in a single function" + setprops.append(name) + def decorator(funcs): + if allowed != None: + checked = checked_property(allowed=allowed) + defaulting = defaulting_property(default=default, null=EMPTY) + change_hook = change_hook_property(hook=save) + primed = primed_property(primer=load) + settings = settings_property(name=name) + docp = doc_property(doc=doc) + deco = defaulting(change_hook(primed(settings(docp(funcs))))) + if allowed != None: + deco = checked(deco) + return Property(deco) + return decorator + + @_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") + 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 + return self.rcs.get_file_contents(self.get_path("body")) + def _set_comment_body(self, value, force=False): + if (self.rcs != None and self.sync_with_disk == True) or force==True: + assert value != None, "Can't save empty comment" + self.rcs.set_file_contents(self.get_path("body"), value) + + @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): """ @@ -98,25 +210,19 @@ class Comment(Tree): """ Tree.__init__(self) self.bug = bug - if bug != None: - self.rcs = bug.rcs - else: - self.rcs = None + self.uuid = uuid + self._settings_loaded = False + self.settings = {} if from_disk == True: - self.uuid = uuid - self.load() + self.sync_with_disk = True else: - if uuid != None: - self.uuid = uuid - else: + self.sync_with_disk = False + if uuid == None: self.uuid = uuid_gen() - self.time = time.time() + self.time = int(time.time()) # only save to second precision 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): @@ -126,22 +232,10 @@ class Comment(Tree): 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, 01 Jan 1970 00:00:00 +0000") + >>> comm.time_string = "Thu, 01 Jan 1970 00:00:00 +0000" >>> print comm.string(indent=2, shortname="com-1") --------- Comment --------- Name: com-1 @@ -157,12 +251,12 @@ class Comment(Tree): 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("From: %s" % (self.From or "")) + lines.append("Date: %s" % self.time_string) lines.append("") - #lines.append(textwrap.fill(self._clean_string(self.body), + #lines.append(textwrap.fill(self.body or "", # width=(79-indent))) - lines.extend(self._clean_string(self.body).splitlines()) + lines.extend((self.body or "").splitlines()) # some comments shouldn't be wrapped... istring = ' '*indent @@ -173,7 +267,7 @@ class Comment(Tree): """ >>> 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.time_string = "Thu, 20 Nov 2008 15:55:11 +0000" >>> comm.From = "Jane Doe <jdoe@example.com>" >>> print comm --------- Comment --------- @@ -192,36 +286,34 @@ class Comment(Tree): assert name in ["values", "body"] return os.path.join(my_dir, name) - def load(self): - map = mapfile.map_load(self.rcs, 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 load_settings(self): + self.settings = mapfile.map_load(self.rcs, self.get_path("values")) + for property in self.settings_properties: + if property not in self.settings: + self.settings[property] = EMPTY + elif self.settings[property] == None: + self.settings[property] = EMPTY + self._settings_loaded = True - 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")) + def save_settings(self): + map = {} + for k,v in self.settings.items(): + if (v != None and v != EMPTY): + map[k] = v + for k in self.required_saved_properties: + map[k] = getattr(self, self._setting_name_to_attr_name(k)) + 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 + path = self.get_path("values") + mapfile.map_save(self.rcs, path, map) + + 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(self.body, force=True) def remove(self): for comment in self.traverse(): @@ -232,15 +324,19 @@ class Comment(Tree): 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, name_map={}, indent=0, flatten=True, |