diff options
Diffstat (limited to 'libbe/bug.py')
-rw-r--r-- | libbe/bug.py | 150 |
1 files changed, 99 insertions, 51 deletions
diff --git a/libbe/bug.py b/libbe/bug.py index dfa49f2..7554318 100644 --- a/libbe/bug.py +++ b/libbe/bug.py @@ -1,21 +1,20 @@ # 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 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. +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# 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 errno @@ -34,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/ @@ -68,7 +72,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 +92,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 +182,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 == None: return None return utility.str_to_time(self.time_string) def _set_time(self, value): @@ -188,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) @@ -253,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 == settings_object.EMPTY: + if value == None: return "" - else: - return str(value) + return str(value) def xml(self, show_comments=False): if self.bugdir == None: @@ -283,7 +287,7 @@ class Bug(settings_object.SavedSettingsObject): ("summary", self.summary)] ret = '<bug>\n' for (k,v) in info: - if v is not settings_object.EMPTY: + if v is not None: ret += ' <%s>%s</%s>\n' % (k,xml.sax.saxutils.escape(v),k) for estr in self.extra_strings: ret += ' <extra-string>%s</extra-string>\n' % estr @@ -335,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) @@ -348,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) @@ -365,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 @@ -477,8 +503,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) @@ -486,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): |