aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/bug.py
diff options
context:
space:
mode:
authorMatěj Cepl <mcepl@cepl.eu>2024-01-19 03:07:54 +0100
committerMatěj Cepl <mcepl@cepl.eu>2024-01-21 21:57:15 +0100
commitb638466e6a6ada7478758cf740c89650d0f70f59 (patch)
treef34979881a12818ba46a4b74a7a7096e737e441a /libbe/bug.py
parentb11b63156666589ec9749fa318fe7ecd9d1f136d (diff)
downloadbugseverywhere-b638466e6a6ada7478758cf740c89650d0f70f59.tar.gz
WIP plenty of clean-ups and porting to Python 3.
Diffstat (limited to 'libbe/bug.py')
-rw-r--r--libbe/bug.py330
1 files changed, 173 insertions, 157 deletions
diff --git a/libbe/bug.py b/libbe/bug.py
index d211c30..2b79605 100644
--- a/libbe/bug.py
+++ b/libbe/bug.py
@@ -25,11 +25,13 @@
"""
import copy
+import functools
import time
from xml.etree import ElementTree
import xml.sax.saxutils
import libbe
+import libbe.bug
import libbe.util.id
from libbe.storage.util.properties import Property, doc_property, \
local_property, defaulting_property, checked_property, cached_property, \
@@ -88,16 +90,13 @@ def load_severities(severity_def):
load_severities(severity_def)
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
active_status_values = []
inactive_status_values = []
status_values = []
status_description = {}
status_index = {}
+
+
def load_status(active_status_def, inactive_status_def):
global active_status_values
global inactive_status_values
@@ -108,8 +107,10 @@ def load_status(active_status_def, inactive_status_def):
active_status_def = globals()["active_status_def"]
if inactive_status_def is 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])
+ active_status_values = tuple([val for val,
+ description in active_status_def])
+ inactive_status_values = tuple([val for val,
+ description in inactive_status_def])
status_values = active_status_values + inactive_status_values
status_description = dict(tuple(active_status_def) + tuple(inactive_status_def))
status_index = {}
@@ -117,7 +118,9 @@ def load_status(active_status_def, inactive_status_def):
status_index[status] = i
load_status(active_status_def, inactive_status_def)
+DEFAULT_IDX_FULL_IDX_LIST = None # To be redefined later
+@functools.total_ordering
class Bug (settings_object.SavedSettingsObject):
"""A bug (or issue) is a place to store attributes and attach
:py:class:`~libbe.comment.Comment`\s. In mailing-list terms, a bug is
@@ -125,26 +128,26 @@ class Bug (settings_object.SavedSettingsObject):
:py:class:`~libbe.bugdir.BugDir`\s.
>>> b = Bug()
- >>> print b.status
+ >>> print(b.status)
open
- >>> print b.severity
+ >>> print(b.severity)
minor
There are two formats for time, int and string. Setting either
one will adjust the other appropriately. The string form is the
one stored in the bug's settings file on disk.
- >>> print type(b.time)
+ >>> print(type(b.time))
<type 'int'>
- >>> print type(b.time_string)
- <type 'str'>
+ >>> print(type(b.time_string))
+ <class 'str'>
>>> b.time = 0
- >>> print b.time_string
+ >>> print(b.time_string)
Thu, 01 Jan 1970 00:00:00 +0000
>>> b.time_string="Thu, 01 Jan 1970 00:01:00 +0000"
>>> b.time
60
- >>> print b.settings["time"]
+ >>> print(b.settings["time"])
Thu, 01 Jan 1970 00:01:00 +0000
"""
settings_properties = []
@@ -200,10 +203,11 @@ class Bug (settings_object.SavedSettingsObject):
self._cached_time = None
return None
if (not hasattr(self, '_cached_time_string')
- or self.time_string != self._cached_time_string):
+ or self.time_string != self._cached_time_string):
self._cached_time_string = self.time_string
self._cached_time = utility.str_to_time(self.time_string)
return self._cached_time
+
def _set_time(self, value):
if not hasattr(self, '_cached_time') or value != self._cached_time:
self.time_string = utility.time_to_str(value)
@@ -214,13 +218,17 @@ class Bug (settings_object.SavedSettingsObject):
doc="An integer version of .time_string")
def _extra_strings_check_fn(value):
- return utility.iterable_full_of_strings(value, \
- alternative=settings_object.EMPTY)
+ 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.extra_strings.sort() # to make merging easier
self._prop_save_settings(old, new)
+
@_versioned_property(name="extra_strings",
- doc="Space for an array of extra strings. Useful for storing state for functionality implemented purely in becommands/<some_function>.py.",
+ doc="Space for an array of extra strings. " +
+ "Useful for storing state for functionality " +
+ "implemented purely in becommands/<some_function>.py.",
default=[],
check_fn=_extra_strings_check_fn,
change_hook=_extra_strings_change_hook,
@@ -232,7 +240,7 @@ class Bug (settings_object.SavedSettingsObject):
def summary(): return {}
def _get_comment_root(self, load_full=False):
- if self.storage != None and self.storage.is_readable():
+ if self.storage is not None and self.storage.is_readable():
return comment.load_comments(self, load_full=load_full)
else:
return comment.Comment(self, uuid=comment.INVALID_UUID)
@@ -256,10 +264,10 @@ class Bug (settings_object.SavedSettingsObject):
self.time = int(time.time()) # only save to second precision
self.summary = summary
dummy = self.comment_root
- if self.bugdir != None:
+ if self.bugdir is not None:
self.storage = self.bugdir.storage
if from_storage == False:
- if self.storage != None and self.storage.is_writeable():
+ if self.storage is not None and self.storage.is_writeable():
self.save()
def __repr__(self):
@@ -268,8 +276,18 @@ class Bug (settings_object.SavedSettingsObject):
def __str__(self):
return self.string(shortlist=True)
- def __cmp__(self, other):
- return cmp_full(self, other)
+ def __eq__(self, other, cmp_list=DEFAULT_IDX_FULL_IDX_LIST):
+ for comparison in cmp_list:
+ if comparison(self, other) != 0:
+ return False
+ return True
+
+ def __lt__(self, other, cmp_list=DEFAULT_IDX_FULL_IDX_LIST):
+ for comparison in cmp_list:
+ val = comparison(self, other)
+ if val != 0:
+ return val == -1
+ return False
# serializing methods
@@ -282,7 +300,7 @@ class Bug (settings_object.SavedSettingsObject):
return value
def string(self, shortlist=False, show_comments=False):
- if shortlist == False:
+ if not shortlist:
if self.time is None:
timestring = ""
else:
@@ -422,9 +440,9 @@ class Bug (settings_object.SavedSettingsObject):
('created', timestring),
('summary', self.summary)]
lines = ['<bug>']
- for (k,v) in info:
+ for (k, v) in info:
if v is not None:
- lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
+ lines.append(' <%s>%s</%s>' % (k, xml.sax.saxutils.escape(v), k))
for estr in self.extra_strings:
lines.append(' <extra-string>%s</extra-string>' % estr)
if show_comments:
@@ -469,12 +487,12 @@ class Bug (settings_object.SavedSettingsObject):
"""
if type(xml_string) == str:
xml_string = xml_string.strip().encode('unicode_escape')
- if hasattr(xml_string, 'getchildren'): # already an ElementTree Element
+ if isinstance(xml_string, ElementTree.Element):
bug = xml_string
else:
bug = ElementTree.XML(xml_string)
if bug.tag != 'bug':
- raise utility.InvalidXML( \
+ raise utility.InvalidXML(
'bug', bug, 'root element must be <bug>')
tags=['uuid','short-name','severity','status','assigned',
'reporter', 'creator','created','summary','extra-string']
@@ -482,7 +500,7 @@ class Bug (settings_object.SavedSettingsObject):
uuid = None
estrs = []
comments = []
- for child in bug.getchildren():
+ for child in bug:
if child.tag == 'short-name':
pass
elif child.tag == 'comment':
@@ -491,7 +509,7 @@ class Bug (settings_object.SavedSettingsObject):
comments.append(comm)
continue
elif child.tag in tags:
- if child.text == None or len(child.text) == 0:
+ if child.text is None or len(child.text) == 0:
text = settings_object.EMPTY
else:
text = xml.sax.saxutils.unescape(child.text)
@@ -500,7 +518,7 @@ class Bug (settings_object.SavedSettingsObject):
text = text.strip()
if child.tag == 'uuid' and not preserve_uuids:
uuid = text
- continue # don't set the bug's uuid tag.
+ continue # don't set the bug's uuid tag.
elif child.tag == 'created':
if text is not settings_object.EMPTY:
self.time = utility.str_to_time(text)
@@ -508,8 +526,8 @@ class Bug (settings_object.SavedSettingsObject):
continue
elif child.tag == 'extra-string':
estrs.append(text)
- continue # don't set the bug's extra_string yet.
- attr_name = child.tag.replace('-','_')
+ continue # don't set the bug's extra_string yet.
+ attr_name = child.tag.replace('-', '_')
self.explicit_attrs.append(attr_name)
setattr(self, attr_name, text)
else:
@@ -517,7 +535,7 @@ class Bug (settings_object.SavedSettingsObject):
'ignoring unknown tag {0} in {1}'.format(
child.tag, comment.tag))
if uuid != self.uuid:
- if not hasattr(self, 'alt_id') or self.alt_id == None:
+ if not hasattr(self, 'alt_id') or self.alt_id is None:
self.alt_id = uuid
self.extra_strings = estrs
self.add_comments(comments, ignore_missing_references=True)
@@ -539,7 +557,7 @@ class Bug (settings_object.SavedSettingsObject):
>>> commC.uuid = 'commC'
>>> commC.in_reply_to = commA.uuid
>>> bugA.add_comment(commC)
- >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS
+ >>> print(bugA.xml(show_comments=True)) # doctest: +ELLIPSIS
<bug>
<uuid>0123</uuid>
<short-name>/012</short-name>
@@ -589,17 +607,17 @@ class Bug (settings_object.SavedSettingsObject):
if default_parent is None:
default_parent = self.comment_root
for c in list(self.comments()) + comments:
- assert c.uuid != None
+ assert c.uuid is not None
assert c.uuid not in uuid_map
uuid_map[c.uuid] = c
- if c.alt_id != None:
+ if c.alt_id is not None:
uuid_map[c.alt_id] = c
uuid_map[None] = self.comment_root
uuid_map[comment.INVALID_UUID] = self.comment_root
if default_parent != self.comment_root:
assert default_parent.uuid in uuid_map, default_parent.uuid
for c in comments:
- if c.in_reply_to == None \
+ if c.in_reply_to is None \
and default_parent.uuid != comment.INVALID_UUID:
c.in_reply_to = default_parent.uuid
elif c.in_reply_to == comment.INVALID_UUID:
@@ -643,34 +661,34 @@ class Bug (settings_object.SavedSettingsObject):
>>> commB.uuid = 'uuid-commB'
>>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False,
... accept_comments=False, change_exception=False)
- >>> print bugA.creator
+ >>> print(bugA.creator)
Frank
>>> bugA.merge(bugB, accept_changes=False, accept_extra_strings=False,
... accept_comments=False, change_exception=True)
Traceback (most recent call last):
...
ValueError: Merge would change creator "Frank"->"John" for bug 0123
- >>> print bugA.creator
+ >>> print(bugA.creator)
Frank
>>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=False,
... accept_comments=False, change_exception=True)
Traceback (most recent call last):
...
ValueError: Merge would add extra string "TAG: useful" for bug 0123
- >>> print bugA.creator
+ >>> print(bugA.creator)
John
- >>> print bugA.extra_strings
+ >>> print(bugA.extra_strings)
['TAG: favorite', 'TAG: very helpful']
>>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True,
... accept_comments=False, change_exception=True)
Traceback (most recent call last):
...
ValueError: Merge would add comment uuid-commB (alt: None) to bug 0123
- >>> print bugA.extra_strings
+ >>> print(bugA.extra_strings)
['TAG: favorite', 'TAG: useful', 'TAG: very helpful']
>>> bugA.merge(bugB, accept_changes=True, accept_extra_strings=True,
... accept_comments=True, change_exception=True)
- >>> print bugA.xml(show_comments=True) # doctest: +ELLIPSIS
+ >>> print(bugA.xml(show_comments=True)) # doctest: +ELLIPSIS
<bug>
<uuid>0123</uuid>
<short-name>/012</short-name>
@@ -712,19 +730,19 @@ class Bug (settings_object.SavedSettingsObject):
('Merge would change {} "{}"->"{}" for bug {}'
).format(attr, old, new, self.uuid))
for estr in other.extra_strings:
- if not estr in self.extra_strings:
+ if estr not in self.extra_strings:
if accept_extra_strings:
self.extra_strings += [estr]
elif change_exception:
- raise ValueError('Merge would add extra string "%s" for bug %s' \
- % (estr, self.uuid))
+ raise ValueError('Merge would add extra string "%s" for bug %s'
+ % (estr, self.uuid))
for o_comm in other.comments():
try:
s_comm = self.comment_root.comment_from_uuid(o_comm.uuid)
- except KeyError as e:
+ except KeyError:
try:
s_comm = self.comment_root.comment_from_uuid(o_comm.alt_id)
- except KeyError as e:
+ except KeyError:
s_comm = None
if s_comm is None:
if accept_comments:
@@ -733,7 +751,8 @@ class Bug (settings_object.SavedSettingsObject):
o_comm_copy.id = libbe.util.id.ID(o_comm_copy, 'comment')
self.comment_root.add_reply(o_comm_copy)
elif change_exception:
- raise ValueError('Merge would add comment %s (alt: %s) to bug %s' \
+ raise ValueError(
+ 'Merge would add comment %s (alt: %s) to bug %s'
% (o_comm.uuid, o_comm.alt_id, self.uuid))
else:
s_comm.merge(o_comm, accept_changes=accept_changes,
@@ -748,7 +767,7 @@ class Bug (settings_object.SavedSettingsObject):
self.id.storage('values'), '{}\n')
try:
settings = mapfile.parse(settings_mapfile)
- except mapfile.InvalidMapfileContents as e:
+ except mapfile.InvalidMapfileContents:
raise Exception('Invalid settings file for bug %s\n'
'(BE version missmatch?)' % self.id.user())
self._setup_saved_settings(settings)
@@ -767,8 +786,8 @@ class Bug (settings_object.SavedSettingsObject):
happen, so calling this method will just waste time (unless
something else has been messing with your stored files).
"""
- assert self.storage != None, "Can't save without storage"
- if self.bugdir != None:
+ assert self.storage is not None, "Can't save without storage"
+ if self.bugdir is not None:
parent = self.bugdir.id.storage()
else:
parent = None
@@ -799,12 +818,12 @@ class Bug (settings_object.SavedSettingsObject):
# methods for managing comments
def uuids(self):
- for comment in self.comments():
- yield comment.uuid
+ for cmt in self.comments():
+ yield cmt.uuid
def comments(self):
- for comment in self.comment_root.traverse():
- yield comment
+ for cmt in self.comment_root.traverse():
+ yield cmt
def new_comment(self, body=None):
comm = self.comment_root.new_reply(body=body)
@@ -816,7 +835,7 @@ class Bug (settings_object.SavedSettingsObject):
# methods for id generation
def sibling_uuids(self):
- if self.bugdir != None:
+ if self.bugdir is not None:
return self.bugdir.uuids()
return []
@@ -827,28 +846,31 @@ class Bug (settings_object.SavedSettingsObject):
# importance is unclear, the sorting follows some arbitrary convention
# (i.e. dictionary order).
-def cmp_severity(bug_1, bug_2):
+def idx_severity(bug):
"""
Compare the severity levels of two bugs, with more severe bugs
comparing as less.
>>> bugA = Bug()
>>> bugB = Bug()
- >>> bugA.severity = bugB.severity = "wishlist"
- >>> cmp_severity(bugA, bugB) == 0
- True
+ >>> bugC = Bug()
+ >>> bugA.severity = bugB.severity = "serious"
+ >>> idx1 = idx_severity(bugA)
+ >>> idx1
+ 3
>>> bugB.severity = "minor"
- >>> cmp_severity(bugA, bugB) > 0
+ >>> idx_severity(bugB) < idx1
True
- >>> bugA.severity = "critical"
- >>> cmp_severity(bugA, bugB) < 0
+ >>> bugC.severity = "critical"
+ >>> idx_severity(bugC) > idx1
True
"""
- if not hasattr(bug_2, "severity") :
- return 1
- return -cmp(severity_index[bug_1.severity], severity_index[bug_2.severity])
+ if not hasattr(bug, "severity"):
+ return None
+ return severity_index[bug.severity]
+
-def cmp_status(bug_1, bug_2):
+def idx_status(bug):
"""
Compare the status levels of two bugs, with more "open" bugs
comparing as less.
@@ -856,116 +878,110 @@ def cmp_status(bug_1, bug_2):
>>> bugA = Bug()
>>> bugB = Bug()
>>> bugA.status = bugB.status = "open"
- >>> cmp_status(bugA, bugB) == 0
+ >>> idx_status(bugA) == idx_status(bugB)
True
>>> bugB.status = "closed"
- >>> cmp_status(bugA, bugB) < 0
+ >>> idx_status(bugA) < idx_status(bugB)
True
>>> bugA.status = "fixed"
- >>> cmp_status(bugA, bugB) > 0
+ >>> idx_status(bugA) > idx_status(bugB)
True
"""
- if not hasattr(bug_2, "status") :
- return 1
- val_2 = status_index[bug_2.status]
- return cmp(status_index[bug_1.status], status_index[bug_2.status])
+ if not hasattr(bug, "status"):
+ return None
+ return status_index[bug.status]
-def cmp_attr(bug_1, bug_2, attr, invert=False):
- """
- Compare a general attribute between two bugs using the
- conventional comparison rule for that attribute type. If
- ``invert==True``, sort *against* that convention.
- >>> attr="severity"
- >>> bugA = Bug()
- >>> bugB = Bug()
- >>> bugA.severity = "critical"
- >>> bugB.severity = "wishlist"
- >>> cmp_attr(bugA, bugB, attr) < 0
- True
- >>> cmp_attr(bugA, bugB, attr, invert=True) > 0
- True
- >>> bugB.severity = "critical"
- >>> cmp_attr(bugA, bugB, attr) == 0
+def idx_attr(comment, attr):
+ """
+ Generate an index for sorting of comments based on the given
+ attribute.
+
+ >>> attr="author"
+ >>> commentA = comment.Comment()
+ >>> commentA.author = 'John Doe'
+ >>> idx_attr(commentA, attr)
+ 'John Doe'
+ >>> commentB = comment.Comment()
+ >>> commentB.author = 'John Doe'
+ >>> idx_attr(commentB, attr)
+ 'John Doe'
+ >>> idx_attr(commentA, attr)
+ 'John Doe'
+ >>> idx_attr(commentA, attr) == idx_attr(commentB, attr)
True
"""
- if not hasattr(bug_2, attr) :
- return 1
- val_1 = getattr(bug_1, attr)
- val_2 = getattr(bug_2, attr)
- if val_1 is None: val_1 = None
- if val_2 is None: val_2 = None
-
- if invert == True :
- return -cmp(val_1, val_2)
- else :
- return cmp(val_1, val_2)
+ if not hasattr(comment, attr):
+ return None
+ return getattr(comment, attr)
+
# 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_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")
-cmp_extra_strings = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "extra_strings")
+def idx_uuid(bug):
+ return idx_attr(bug, "uuid")
+
+
+def idx_creator(bug):
+ return idx_attr(bug, "creator")
+
+
+def idx_assigned(bug):
+ return idx_attr(bug, "assigned")
+
+
+def idx_reporter(bug):
+ return idx_attr(bug, "reporter")
+
+
+def idx_summary(bug):
+ return idx_attr(bug, "summary")
+
+
+def idx_extra_strings(bug):
+ return idx_attr(bug, "extra_strings")
+
+
# chronological rankings (newer < older)
-cmp_time = lambda bug_1, bug_2 : cmp_attr(bug_1, bug_2, "time", invert=True)
+def idx_time(bug):
+ idx_attr(bug, "time")
+
+
+# FIXME This is probably wrong
+def idx_mine(bug):
+ user_id = libbe.ui.util.user.get_user_id(bug.storage)
+ mine_1 = bug.assigned != user_id
+ return mine_1
-def cmp_mine(bug_1, bug_2):
- user_id = libbe.ui.util.user.get_user_id(bug_1.storage)
- mine_1 = bug_1.assigned != user_id
- mine_2 = bug_2.assigned != user_id
- return cmp(mine_1, mine_2)
-def cmp_comments(bug_1, bug_2):
+def idx_comments(bug):
"""
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_reporter, cmp_comments, cmp_summary, cmp_uuid, cmp_extra_strings)
-
-class BugCompoundComparator (object):
- def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST):
- self.cmp_list = cmp_list
- def __call__(self, bug_1, bug_2):
- for comparison in self.cmp_list :
- val = comparison(bug_1, bug_2)
- if val != 0 :
- return val
- return 0
-
-cmp_full = BugCompoundComparator()
-
-
-# define some bonus cmp_* functions
-def cmp_last_modified(bug_1, bug_2):
+ comms_1 = sorted(bug.comments(), key=lambda comm: comm.uuid)
+ raw_txt = ""
+ for c_1 in comms_1:
+ raw_txt += c_1
+ return len(comms_1), raw_txt
+
+
+DEFAULT_IDX_FULL_IDX_LIST = \
+ (idx_status, idx_severity, idx_assigned, idx_time, idx_creator,
+ idx_reporter, idx_comments, idx_summary, idx_uuid, idx_extra_strings)
+
+
+# define some bonus idx_* functions
+def idx_last_modified(bug):
"""
- Like cmp_time(), but use most recent comment instead of bug
+ Like idx_time(), but use most recent comment instead of bug
creation for the timestamp.
"""
- def last_modified(bug):
- time = bug.time
- for comment in bug.comment_root.traverse():
- if comment.time > time:
- time = comment.time
- return time
- val_1 = last_modified(bug_1)
- val_2 = last_modified(bug_2)
- return -cmp(val_1, val_2)
+ time = bug.time
+ for cmt in bug.comment_root.traverse():
+ if cmt.time > time:
+ time = cmt.time
+ return time
if libbe.TESTING: