Added cmp functions to libbe.comment, and fleshed them out in libbe.bug.
[be.git] / libbe / bug.py
index f5f479c588f311284054646266dc166bbd6dcbc4..7554318824dc5faaea78a97909defaf2d239690f 100644 (file)
@@ -33,6 +33,11 @@ import comment
 import utility
 
 
+class DiskAccessRequired (Exception):
+    def __init__(self, goal):
+        msg = "Cannot %s without accessing the disk" % goal
+        Exception.__init__(self, msg)
+
 ### Define and describe valid bug categories
 # Use a tuple of (category, description) tuples since we don't have
 # ordered dicts in Python yet http://www.python.org/dev/peps/pep-0372/
@@ -177,7 +182,7 @@ class Bug(settings_object.SavedSettingsObject):
     def time_string(): return {}
 
     def _get_time(self):
-        if self.time_string in [None, settings_object.EMPTY]:
+        if self.time_string == None:
             return None
         return utility.str_to_time(self.time_string)
     def _set_time(self, value):
@@ -187,15 +192,8 @@ class Bug(settings_object.SavedSettingsObject):
                     doc="An integer version of .time_string")
 
     def _extra_strings_check_fn(value):
-        "Require an iterable full of strings"
-        if value == settings_object.EMPTY:
-            return True
-        elif not hasattr(value, "__iter__"):
-            return False
-        for x in value:
-            if type(x) not in types.StringTypes:
-                return False
-        return True
+        return utility.iterable_full_of_strings(value, \
+                         alternative=settings_object.EMPTY)
     def _extra_strings_change_hook(self, old, new):
         self.extra_strings.sort() # to make merging easier
         self._prop_save_settings(old, new)
@@ -252,12 +250,19 @@ class Bug(settings_object.SavedSettingsObject):
     def __repr__(self):
         return "Bug(uuid=%r)" % self.uuid
 
+    def __str__(self):
+        return self.string(shortlist=True)
+
+    def __cmp__(self, other):
+        return cmp_full(self, other)
+
+    # serializing methods
+
     def _setting_attr_string(self, setting):
         value = getattr(self, setting)
-        if value in [None, settings_object.EMPTY]:
+        if value == None:
             return ""
-        else:
-            return str(value)
+        return str(value)
 
     def xml(self, show_comments=False):
         if self.bugdir == None:
@@ -334,11 +339,7 @@ class Bug(settings_object.SavedSettingsObject):
             output = bugout
         return output
 
-    def __str__(self):
-        return self.string(shortlist=True)
-
-    def __cmp__(self, other):
-        return cmp_full(self, other)
+    # methods for saving/loading/acessing settings and properties.
 
     def get_path(self, name=None):
         my_dir = os.path.join(self.bugdir.get_path("bugs"), self.uuid)
@@ -347,11 +348,47 @@ class Bug(settings_object.SavedSettingsObject):
         assert name in ["values", "comments"]
         return os.path.join(my_dir, name)
 
+    def set_sync_with_disk(self, value):
+        self.sync_with_disk = value
+        for comment in self.comments():
+            comment.set_sync_with_disk(value)
+
     def load_settings(self):
+        if self.sync_with_disk == False:
+            raise DiskAccessRequired("load settings")
         self.settings = mapfile.map_load(self.rcs, self.get_path("values"))
         self._setup_saved_settings()
 
+    def save_settings(self):
+        if self.sync_with_disk == False:
+            raise DiskAccessRequired("save settings")
+        assert self.summary != None, "Can't save blank bug"
+        self.rcs.mkdir(self.get_path())
+        path = self.get_path("values")
+        mapfile.map_save(self.rcs, path, self._get_saved_settings())
+
+    def save(self):
+        """
+        Save any loaded contents to disk.  Because of lazy loading of
+        comments, this is actually not too inefficient.
+        
+        However, if self.sync_with_disk = True, then any changes are
+        automatically written to disk as soon as they happen, so
+        calling this method will just waste time (unless something
+        else has been messing with your on-disk files).
+        """
+        sync_with_disk = self.sync_with_disk
+        if sync_with_disk == False:
+            self.set_sync_with_disk(True)
+        self.save_settings()
+        if len(self.comment_root) > 0:
+            comment.saveComments(self)
+        if sync_with_disk == False:
+            self.set_sync_with_disk(False)
+
     def load_comments(self, load_full=True):
+        if self.sync_with_disk == False:
+            raise DiskAccessRequired("load comments")
         if load_full == True:
             # Force a complete load of the whole comment tree
             self.comment_root = self._get_comment_root(load_full=True)
@@ -364,25 +401,15 @@ class Bug(settings_object.SavedSettingsObject):
             self.comment_root = None
             self.sync_with_disk = True
 
-    def save_settings(self):
-        assert self.summary != None, "Can't save blank bug"
-        
-        self.rcs.mkdir(self.get_path())
-        path = self.get_path("values")
-        mapfile.map_save(self.rcs, path, self._get_saved_settings())
-        
-    def save(self):
-        self.save_settings()
-
-        if len(self.comment_root) > 0:
-            self.rcs.mkdir(self.get_path("comments"))
-            comment.saveComments(self)
-
     def remove(self):
+        if self.sync_with_disk == False:
+            raise DiskAccessRequired("remove")
         self.comment_root.remove()
         path = self.get_path()
         self.rcs.recursive_remove(path)
     
+    # methods for managing comments
+
     def comments(self):
         for comment in self.comment_root.traverse():
             yield comment
@@ -485,13 +512,35 @@ def cmp_attr(bug_1, bug_2, attr, invert=False):
         return cmp(val_1, val_2)
 
 # alphabetical rankings (a < z)
+cmp_uuid = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "uuid")
 cmp_creator = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "creator")
 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")
 # chronological rankings (newer < older)
 cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True)
 
+def cmp_comments(bug_1, bug_2):
+    """
+    Compare two bugs' comments lists.  Doesn't load any new comments,
+    so you should call each bug's .load_comments() first if you want a
+    full comparison.
+    """
+    comms_1 = sorted(bug_1.comments(), key = lambda comm : comm.uuid)
+    comms_2 = sorted(bug_2.comments(), key = lambda comm : comm.uuid)
+    result = cmp(len(comms_1), len(comms_2))
+    if result != 0:
+        return result
+    for c_1,c_2 in zip(comms_1, comms_2):
+        result = cmp(c_1, c_2)
+        if result != 0:
+            return result
+    return 0
+
 DEFAULT_CMP_FULL_CMP_LIST = \
-    (cmp_status,cmp_severity,cmp_assigned,cmp_time,cmp_creator)
+    (cmp_status, cmp_severity, cmp_assigned, cmp_time, cmp_creator,
+     cmp_reporter, cmp_target, cmp_comments, cmp_summary, cmp_uuid)
 
 class BugCompoundComparator (object):
     def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST):