aboutsummaryrefslogtreecommitdiffstats
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
parentb11b63156666589ec9749fa318fe7ecd9d1f136d (diff)
downloadbugseverywhere-b638466e6a6ada7478758cf740c89650d0f70f59.tar.gz
WIP plenty of clean-ups and porting to Python 3.
-rw-r--r--interfaces/email/interactive/send_pgp_mime.py14
-rw-r--r--libbe/bug.py330
-rw-r--r--libbe/bugdir.py14
-rw-r--r--libbe/command/base.py10
-rw-r--r--libbe/command/comment.py4
-rw-r--r--libbe/command/import_xml.py6
-rw-r--r--libbe/command/list.py37
-rw-r--r--libbe/command/merge.py6
-rw-r--r--libbe/command/new.py8
-rw-r--r--libbe/command/remove.py4
-rw-r--r--libbe/command/subscribe.py4
-rw-r--r--libbe/command/tag.py10
-rw-r--r--libbe/command/target.py4
-rw-r--r--libbe/comment.py250
-rw-r--r--libbe/diff.py66
-rw-r--r--libbe/storage/base.py72
-rw-r--r--libbe/storage/util/properties.py15
-rw-r--r--libbe/storage/util/settings_object.py6
-rw-r--r--libbe/storage/vcs/base.py5
-rw-r--r--libbe/storage/vcs/darcs.py10
-rw-r--r--libbe/storage/vcs/monotone.py15
-rw-r--r--libbe/ui/command_line.py2
-rw-r--r--libbe/util/tree.py16
-rw-r--r--libbe/util/wsgi.py14
24 files changed, 493 insertions, 429 deletions
diff --git a/interfaces/email/interactive/send_pgp_mime.py b/interfaces/email/interactive/send_pgp_mime.py
index e0ffbd5..42a0a8c 100644
--- a/interfaces/email/interactive/send_pgp_mime.py
+++ b/interfaces/email/interactive/send_pgp_mime.py
@@ -142,7 +142,7 @@ def header_from_text(text, encoding="us-ascii"):
"""
Simple wrapper for instantiating an email.Message from text.
>>> header = header_from_text('\\n'.join(['From: me@big.edu','To: you@big.edu','Subject: testing']))
- >>> print flatten(header)
+ >>> print(flatten(header))
From: me@big.edu
To: you@big.edu
Subject: testing
@@ -192,7 +192,7 @@ def attach_root(header, root_part):
"""
for k,v in list(header.items()):
root_part[k] = v
- return root_part
+ return root_part
def execute(args, stdin=None, expect=(0,)):
"""
@@ -286,7 +286,7 @@ class PGPMimeMessageFactory (object):
>>> target_emails(header) == [to_addr]
True
>>> m = PGPMimeMessageFactory('check 1 2\\ncheck 1 2\\n')
- >>> print flatten(m.clearBodyPart())
+ >>> print(flatten(m.clearBodyPart()))
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
@@ -295,7 +295,7 @@ class PGPMimeMessageFactory (object):
check 1 2
check 1 2
<BLANKLINE>
- >>> print flatten(m.plain())
+ >>> print(flatten(m.plain()))
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
@@ -305,7 +305,7 @@ class PGPMimeMessageFactory (object):
<BLANKLINE>
>>> signed = m.sign(header)
>>> signed.set_boundary('boundsep')
- >>> print flatten(signed).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> print(flatten(signed).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE)
Content-Type: multipart/signed; protocol="application/pgp-signature";
micalg="pgp-sha1"; boundary="boundsep"
MIME-Version: 1.0
@@ -334,7 +334,7 @@ class PGPMimeMessageFactory (object):
--boundsep--
>>> encrypted = m.encrypt(header)
>>> encrypted.set_boundary('boundsep')
- >>> print flatten(encrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> print(flatten(encrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE)
Content-Type: multipart/encrypted;
protocol="application/pgp-encrypted";
micalg="pgp-sha1"; boundary="boundsep"
@@ -360,7 +360,7 @@ class PGPMimeMessageFactory (object):
--boundsep--
>>> signedAndEncrypted = m.signAndEncrypt(header)
>>> signedAndEncrypted.set_boundary('boundsep')
- >>> print flatten(signedAndEncrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> print(flatten(signedAndEncrypted).replace('\\t', ' '*4) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE)
Content-Type: multipart/encrypted;
protocol="application/pgp-encrypted";
micalg="pgp-sha1"; boundary="boundsep"
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:
diff --git a/libbe/bugdir.py b/libbe/bugdir.py
index 5738a80..9ab8d69 100644
--- a/libbe/bugdir.py
+++ b/libbe/bugdir.py
@@ -440,7 +440,7 @@ class BugDir (list, 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):
bugdir = xml_string
else:
bugdir = ElementTree.XML(xml_string)
@@ -452,7 +452,7 @@ class BugDir (list, settings_object.SavedSettingsObject):
self.explicit_attrs = []
uuid = None
estrs = []
- for child in bugdir.getchildren():
+ for child in bugdir:
if child.tag == 'short-name':
pass
elif child.tag == 'bug':
@@ -461,18 +461,18 @@ class BugDir (list, settings_object.SavedSettingsObject):
self.append(bg, update=True)
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
elif child.tag in ['severities', 'active-status',
'inactive-status']:
entries = []
- for entry in child.getchildren():
+ for entry in child:
if entry.tag != 'entry':
raise utility.InvalidXML(
'{} child element {} must be <entry>'.format(
child.tag, entry))
key = value = None
- for kv in entry.getchildren():
+ for kv in entry:
if kv.tag == 'key':
if key is not None:
raise utility.InvalidXML(
@@ -718,7 +718,7 @@ class RevisionedBugDir (BugDir):
self.revision = revision
def changed(self):
return self.storage.changed()
-
+
if libbe.TESTING == True:
class SimpleBugDir (BugDir):
@@ -728,7 +728,7 @@ if libbe.TESTING == True:
>>> bugdir = SimpleBugDir()
>>> uuids = list(bugdir.uuids())
>>> uuids.sort()
- >>> print uuids
+ >>> print(uuids)
['a', 'b']
>>> bugdir.cleanup()
"""
diff --git a/libbe/command/base.py b/libbe/command/base.py
index efba1b2..269141f 100644
--- a/libbe/command/base.py
+++ b/libbe/command/base.py
@@ -75,7 +75,7 @@ def get_command(command_name):
>>> try:
... get_command('asdf')
... except UnknownCommand, e:
- ... print e
+ ... print(e)
Unknown command 'asdf'
(No module named asdf)
>>> repr(get_command('list')).startswith("<module 'libbe.command.list' from ")
@@ -254,7 +254,7 @@ class Command (object):
"""One-line command description here.
>>> c = Command()
- >>> print c.help()
+ >>> print(c.help())
usage: be command [options]
<BLANKLINE>
Options:
@@ -415,7 +415,7 @@ class Command (object):
... c._check_restricted_access(s, os.path.expanduser('~/.ssh/id_rsa'))
... except UserError, e:
... assert str(e).startswith('file access restricted!'), str(e)
- ... print 'we got the expected error'
+ ... print('we got the expected error')
we got the expected error
>>> c._check_restricted_access(s, os.path.expanduser('~/x'))
>>> c._check_restricted_access(s, os.path.expanduser('~/x/y'))
@@ -527,7 +527,7 @@ class StorageCallbacks (object):
def get_unconnected_storage(self):
"""
Callback for use by commands that need it.
-
+
The returned Storage instance is may actually be connected,
but commands that make use of the returned value should only
make use of non-connected Storage methods. This is mainly
@@ -595,7 +595,7 @@ class UserInterface (object):
if self.io is not None:
self.io.setup_command(command)
if self.storage_callbacks is not None:
- self.storage_callbacks.setup_command(command)
+ self.storage_callbacks.setup_command(command)
command.restrict_file_access = self.restrict_file_access
command._get_user_id = self._get_user_id
diff --git a/libbe/command/comment.py b/libbe/command/comment.py
index e110c81..bfd24fe 100644
--- a/libbe/command/comment.py
+++ b/libbe/command/comment.py
@@ -56,7 +56,7 @@ class Comment (libbe.command.Command):
>>> comment = bug.comment_root[0]
>>> comment.id.storage() == comment.uuid
True
- >>> print comment.body
+ >>> print(comment.body)
This is a comment about a
<BLANKLINE>
>>> comment.author
@@ -84,7 +84,7 @@ class Comment (libbe.command.Command):
>>> bug = bd.bug_from_uuid('b')
>>> bug.load_comments(load_full=False)
>>> comment = bug.comment_root[0]
- >>> print comment.body
+ >>> print(comment.body)
I like cheese
<BLANKLINE>
>>> ui.cleanup()
diff --git a/libbe/command/import_xml.py b/libbe/command/import_xml.py
index 8699e70..391b2ed 100644
--- a/libbe/command/import_xml.py
+++ b/libbe/command/import_xml.py
@@ -62,7 +62,7 @@ class Import_XML (libbe.command.Command):
>>> bug = bd.bug_from_uuid('a')
>>> bug.load_comments(load_full=False)
>>> comment = bug.comment_root[0]
- >>> print comment.body
+ >>> print(comment.body)
This is a comment about a
<BLANKLINE>
>>> comment.time <= int(time.time())
@@ -183,7 +183,7 @@ class Import_XML (libbe.command.Command):
if be_xml.tag != 'be-xml':
raise libbe.util.utility.InvalidXML(
'import-xml', be_xml, 'root element must be <be-xml>')
- for child in be_xml.getchildren():
+ for child in be_xml:
if child.tag == 'bugdir':
new = libbe.bugdir.BugDir(storage=None)
new.from_xml(child, preserve_uuids=params['preserve-uuids'])
@@ -197,7 +197,7 @@ class Import_XML (libbe.command.Command):
new.from_xml(child, preserve_uuids=params['preserve-uuids'])
root_comments.append(new)
elif child.tag == 'version':
- for gchild in child.getchildren():
+ for gchild in child:
if child.tag in ['tag', 'nick', 'revision', 'revision-id']:
text = xml.sax.saxutils.unescape(child.text)
text = text.decode('unicode_escape').strip()
diff --git a/libbe/command/list.py b/libbe/command/list.py
index 6d4fcd8..41e1bed 100644
--- a/libbe/command/list.py
+++ b/libbe/command/list.py
@@ -32,9 +32,10 @@ import libbe.command.tag
import libbe.command.target
import libbe.command.util
-# get a list of * for cmp_*() comparing two bugs.
-AVAILABLE_CMPS = [fn[4:] for fn in dir(libbe.bug) if fn[:4] == 'cmp_']
-AVAILABLE_CMPS.remove('attr') # a cmp_* template.
+# get a list of * for idx_*() comparing two bugs.
+AVAILABLE_IDXS = [fn[4:] for fn in dir(libbe.bug) if fn[:4] == 'idx_']
+AVAILABLE_IDXS.remove('attr') # a idx_* template.
+
class List (libbe.command.Command):
"""List bugs
@@ -94,10 +95,10 @@ class List (libbe.command.Command):
libbe.command.Option(name='sort', short_name='S',
help='Adjust bug-sort criteria with comma-separated list '
'SORT. e.g. "--sort creator,time". '
- 'Available criteria: %s' % ','.join(AVAILABLE_CMPS),
+ 'Available criteria: %s' % ','.join(AVAILABLE_IDXS),
arg=libbe.command.Argument(
name='sort', metavar='SORT', default=None,
- completion_callback=libbe.command.util.Completer(AVAILABLE_CMPS))),
+ completion_callback=libbe.command.util.Completer(AVAILABLE_IDXS))),
libbe.command.Option(name='tags', short_name='t',
help='Add TAGS: field to standard listing format.'),
libbe.command.Option(name='ids', short_name='i',
@@ -106,7 +107,7 @@ class List (libbe.command.Command):
help='Dump output in XML format'),
])
# parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by",
-# help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None)
+# help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_IDXS), default=None)
# # boolean options. All but ids and xml are special cases of long forms
# ("w", "wishlist", "List bugs with 'wishlist' severity"),
# ("A", "active", "List all active bugs"),
@@ -129,20 +130,20 @@ class List (libbe.command.Command):
bugdirs = self._get_bugdirs()
writeable = storage.writeable
storage.writeable = False
- cmp_list, status, severity, assigned, extra_strings_regexps = \
+ idx_list, status, severity, assigned, extra_strings_regexps = \
self._parse_params(bugdirs, params)
filter = Filter(status, severity, assigned,
extra_strings_regexps=extra_strings_regexps)
bugs = list(itertools.chain(*list(
[bugdir.bug_from_uuid(uuid) for uuid in bugdir.uuids()]
for bugdir in list(bugdirs.values()))))
- bugs = [b for b in bugs if list(filter(bugdirs, b)) == True]
+ bugs = [b for b in bugs if list(filter(bugdirs, b))]
self.result = bugs
- if len(bugs) == 0 and params['xml'] == False:
+ if len(bugs) == 0 and not params['xml']:
print('No matching bugs found', file=self.stdout)
# sort bugs
- bugs = self._sort_bugs(bugs, cmp_list)
+ bugs = self._sort_bugs(bugs, idx_list)
# print list of bugs
if params['ids'] == True:
@@ -157,10 +158,10 @@ class List (libbe.command.Command):
cmp_list = []
if params['sort'] != None:
for cmp in params['sort'].split(','):
- if cmp not in AVAILABLE_CMPS:
+ if cmp not in AVAILABLE_IDXS:
raise libbe.command.UserError(
'Invalid sort on "%s".\nValid sorts:\n %s'
- % (cmp, '\n '.join(AVAILABLE_CMPS)))
+ % (cmp, '\n '.join(AVAILABLE_IDXS)))
cmp_list.append(getattr(libbe.bug, 'cmp_%s' % cmp))
status = parse_status(params['status'])
severity = parse_severity(params['severity'],
@@ -184,12 +185,12 @@ class List (libbe.command.Command):
for x in params['extra-strings'].split(',')]
return (cmp_list, status, severity, assigned, extra_strings_regexps)
- def _sort_bugs(self, bugs, cmp_list=None):
- if cmp_list is None:
- cmp_list = []
- cmp_list.extend(libbe.bug.DEFAULT_CMP_FULL_CMP_LIST)
- cmp_fn = libbe.bug.BugCompoundComparator(cmp_list=cmp_list)
- bugs.sort(cmp_fn)
+ def _sort_bugs(self, bugs, idx_list=None):
+ if idx_list is None:
+ idx_list = []
+ idx_list.extend(libbe.bug.DEFAULT_IDX_FULL_IDX_LIST)
+ # MCEPL idx_fn = libbe.bug.BugCompoundComparator(idx_list=idx_list)
+ bugs.sort(idx_fn)
return bugs
def _list_bugs(self, bugs, show_tags=False, xml=False):
diff --git a/libbe/command/merge.py b/libbe/command/merge.py
index cf9eb91..4ee67cf 100644
--- a/libbe/command/merge.py
+++ b/libbe/command/merge.py
@@ -60,7 +60,7 @@ class Merge (libbe.command.Command):
... cmp=libbe.comment.cmp_time)
>>> mergeA = a_comments[0]
>>> mergeA.time = 3
- >>> print a.string(show_comments=True)
+ >>> print(a.string(show_comments=True))
... # doctest: +ELLIPSIS, +REPORT_UDIFF
ID : a
Short name : abc/a
@@ -107,7 +107,7 @@ class Merge (libbe.command.Command):
... libbe.comment.cmp_time)
>>> mergeB = b_comments[0]
>>> mergeB.time = 3
- >>> print b.string(show_comments=True)
+ >>> print(b.string(show_comments=True))
... # doctest: +ELLIPSIS, +REPORT_UDIFF
ID : b
Short name : abc/b
@@ -136,7 +136,7 @@ class Merge (libbe.command.Command):
Date: ...
<BLANKLINE>
Merged into bug #abc/a#
- >>> print b.status
+ >>> print(b.status)
closed
>>> ui.cleanup()
>>> bd.cleanup()
diff --git a/libbe/command/new.py b/libbe/command/new.py
index b9a3147..4a0288b 100644
--- a/libbe/command/new.py
+++ b/libbe/command/new.py
@@ -51,7 +51,7 @@ class New (libbe.command.Command):
>>> libbe.util.id.uuid_gen = uuid_gen
>>> bd.flush_reload()
>>> bug = bd.bug_from_uuid('X')
- >>> print bug.summary
+ >>> print(bug.summary)
this is a test
>>> bug.creator
u'Fran\\xe7ois'
@@ -59,11 +59,11 @@ class New (libbe.command.Command):
u'Fran\\xe7ois'
>>> bug.time <= int(time.time())
True
- >>> print bug.severity
+ >>> print(bug.severity)
minor
- >>> print bug.status
+ >>> print(bug.status)
open
- >>> print bug.assigned
+ >>> print(bug.assigned)
None
>>> ui.cleanup()
>>> bd.cleanup()
diff --git a/libbe/command/remove.py b/libbe/command/remove.py
index 2ca2699..4f7ca5e 100644
--- a/libbe/command/remove.py
+++ b/libbe/command/remove.py
@@ -37,7 +37,7 @@ class Remove (libbe.command.Command):
>>> ui.storage_callbacks.set_storage(bd.storage)
>>> cmd = Remove(ui=ui)
- >>> print bd.bug_from_uuid('b').status
+ >>> print(bd.bug_from_uuid('b').status)
closed
>>> ret = ui.run(cmd, args=['/b'])
Removed bug abc/b
@@ -45,7 +45,7 @@ class Remove (libbe.command.Command):
>>> try:
... bd.bug_from_uuid('b')
... except libbe.bugdir.NoBugMatches:
- ... print 'Bug not found'
+ ... print('Bug not found')
Bug not found
>>> ui.cleanup()
>>> bd.cleanup()
diff --git a/libbe/command/subscribe.py b/libbe/command/subscribe.py
index 8541970..45cd0dd 100644
--- a/libbe/command/subscribe.py
+++ b/libbe/command/subscribe.py
@@ -45,14 +45,14 @@ class Subscribe (libbe.command.Command):
>>> cmd = Subscribe(ui=ui)
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
[]
>>> ret = ui.run(cmd, {'subscriber':'John Doe <j@doe.com>'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE
Subscriptions for abc/a:
John Doe <j@doe.com> all *
>>> bd.flush_reload()
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
['SUBSCRIBE:John Doe <j@doe.com>\\tall\\t*']
>>> ret = ui.run(cmd, {'subscriber':'Jane Doe <J@doe.com>', 'servers':'a.com,b.net'}, ['/a']) # doctest: +NORMALIZE_WHITESPACE
Subscriptions for abc/a:
diff --git a/libbe/command/tag.py b/libbe/command/tag.py
index 3b11e99..51e2abe 100644
--- a/libbe/command/tag.py
+++ b/libbe/command/tag.py
@@ -40,14 +40,14 @@ class Tag (libbe.command.Command):
>>> cmd = Tag(ui=ui)
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
[]
>>> ret = ui.run(cmd, args=['/a', 'GUI'])
Tags for abc/a:
GUI
>>> bd.flush_reload()
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
['%(tag_tag)sGUI']
>>> ret = ui.run(cmd, args=['/a', 'later'])
Tags for abc/a:
@@ -67,15 +67,15 @@ class Tag (libbe.command.Command):
later
>>> bd.flush_reload()
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
['%(tag_tag)sAlphabetically first', '%(tag_tag)sGUI', '%(tag_tag)slater']
>>> a.extra_strings = []
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
[]
>>> ret = ui.run(cmd, args=['/a'])
>>> bd.flush_reload()
>>> a = bd.bug_from_uuid('a')
- >>> print a.extra_strings
+ >>> print(a.extra_strings)
[]
>>> ret = ui.run(cmd, args=['/a', 'Alphabetically first'])
Tags for abc/a:
diff --git a/libbe/command/target.py b/libbe/command/target.py
index 0886fbf..b13647e 100644
--- a/libbe/command/target.py
+++ b/libbe/command/target.py
@@ -50,9 +50,9 @@ class Target (libbe.command.Command):
>>> output = ui.io.get_stdout().strip()
>>> bd.flush_reload()
>>> target = bd.bug_from_uuid(output)
- >>> print target.summary
+ >>> print(target.summary)
tomorrow
- >>> print target.severity
+ >>> print(target.severity)
target
>>> ui.io.stdout = sys.stdout
diff --git a/libbe/comment.py b/libbe/comment.py
index ca5f884..17dbab6 100644
--- a/libbe/comment.py
+++ b/libbe/comment.py
@@ -23,6 +23,7 @@
"""
import base64
+import functools
import time
try:
from email.mime.base import MIMEBase
@@ -37,8 +38,7 @@ import xml.sax.saxutils
import libbe
import libbe.util.id
from libbe.storage.util.properties import Property, doc_property, \
- local_property, defaulting_property, checked_property, cached_property, \
- primed_property, change_hook_property, settings_property
+ local_property, cached_property, change_hook_property
import libbe.storage.util.settings_object as settings_object
import libbe.storage.util.mapfile as mapfile
from libbe.util.tree import Tree
@@ -48,11 +48,6 @@ if libbe.TESTING:
import doctest
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
class MissingReference(ValueError):
def __init__(self, comment):
msg = "Missing reference to %s" % (comment.in_reply_to)
@@ -60,8 +55,12 @@ class MissingReference(ValueError):
self.reference = comment.in_reply_to
self.comment = comment
+
INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
+DEFAULT_IDX_FULL_IDX_LIST = None # To be redefined later
+
+
def load_comments(bug, load_full=False):
"""
Set load_full=True when you want to load the comment completely
@@ -77,12 +76,13 @@ def load_comments(bug, load_full=False):
comm = Comment(bug, uuid, from_storage=True)
if load_full:
comm.load_settings()
- dummy = comm.body # force the body to load
+ _ = comm.body # force the body to load
comments.append(comm)
bug.comment_root = Comment(bug, uuid=INVALID_UUID)
bug.add_comments(comments, ignore_missing_references=True)
return bug.comment_root
+
def save_comments(bug):
for comment in bug.comment_root.traverse():
comment.bug = bug
@@ -90,16 +90,17 @@ def save_comments(bug):
comment.save()
+@functools.total_ordering
class Comment (Tree, settings_object.SavedSettingsObject):
- """Comments are a notes that attach to :py:class:`~libbe.bug.Bug`\s in
+ """Comments are a notes that attach to :py:class:`~libbe.bug.Bug`\\s in
threaded trees. In mailing-list terms, a comment is analogous to
a single part of an email.
>>> c = Comment()
- >>> c.uuid != None
+ >>> c.uuid is not None
True
>>> c.uuid = "some-UUID"
- >>> print c.content_type
+ >>> print(c.content_type)
text/plain
"""
@@ -107,17 +108,22 @@ class Comment (Tree, settings_object.SavedSettingsObject):
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
+ kwargs["required_saved_properties"] = required_saved_properties
return settings_object.versioned_property(**kwargs)
@_versioned_property(name="Alt-id",
- doc="Alternate ID for linking imported comments. Internally comments are linked (via In-reply-to) to the parent's UUID. However, these UUIDs are generated internally, so Alt-id is provided as a user-controlled linking target.")
+ doc="Alternate ID for linking imported comments. " +
+ "Internally comments are linked (via In-reply-to) " +
+ "to the parent's UUID. However, these UUIDs are " +
+ "generated internally, so Alt-id is provided as " +
+ "a user-controlled linking target.")
def alt_id(): return {}
@_versioned_property(name="Author",
@@ -142,6 +148,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
if self.date is None:
return None
return utility.str_to_time(self.date)
+
def _set_time(self, value):
self.date = utility.time_to_str(value)
time = property(fget=_get_time,
@@ -149,19 +156,21 @@ class Comment (Tree, settings_object.SavedSettingsObject):
doc="An integer version of .date")
def _get_comment_body(self):
- if self.storage != None and self.storage.is_readable() \
+ if self.storage is not None and self.storage.is_readable() \
and self.uuid != INVALID_UUID:
- return self.storage.get(self.id.storage("body"),
+ return self.storage.get(
+ self.id.storage("body"),
decode=self.content_type.startswith("text/"))
+
def _set_comment_body(self, old=None, new=None, force=False):
assert self.uuid != INVALID_UUID, self
if self.content_type.startswith('text/') \
- and self.bug != None and self.bug.bugdir != None:
+ and self.bug is not None and self.bug.bugdir is not None:
new = libbe.util.id.short_to_long_text(
{self.bug.bugdir.uuid: self.bug.bugdir}, new)
- if (self.storage != None and self.storage.writeable == True) \
- or force==True:
- assert new != None, "Can't save empty comment"
+ if (self.storage is not None and self.storage.writeable) \
+ or force:
+ assert new is not None, "Can't save empty comment"
self.storage.set(self.id.storage("body"), new)
@Property
@@ -172,11 +181,13 @@ class Comment (Tree, settings_object.SavedSettingsObject):
def body(): return {}
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.",
default=[],
@@ -208,22 +219,35 @@ class Comment (Tree, settings_object.SavedSettingsObject):
self.storage = None
self.uuid = uuid
self.id = libbe.util.id.ID(self, 'comment')
- if from_storage == False:
+ if not from_storage:
if uuid is None:
self.uuid = libbe.util.id.uuid_gen()
- self.time = int(time.time()) # only save to second precision
+ self.time = int(time.time()) # only save to second precision
self.in_reply_to = in_reply_to
- if content_type != None:
+ if content_type is not None:
self.content_type = content_type
self.body = body
- if self.bug != None:
+ if self.bug is not None:
self.storage = self.bug.storage
- if from_storage == False:
- if self.storage != None and self.storage.is_writeable():
+ if not from_storage:
+ if self.storage is not None and self.storage.is_writeable():
self.save()
- def __cmp__(self, other):
- return cmp_full(self, other)
+ def __eq__(self, other, idx_list=None):
+ if idx_list is None:
+ idx_list = DEFAULT_IDX_FULL_IDX_LIST
+ for idx in idx_list:
+ if idx(self) != idx(other):
+ return False
+ return True
+
+ def __lt__(self, other, idx_list=None):
+ if idx_list is None:
+ idx_list = DEFAULT_IDX_FULL_IDX_LIST
+ for idx in idx_list:
+ if idx(self) != idx(other):
+ return idx(self) < idx(other)
+ return False
def __str__(self):
"""
@@ -231,7 +255,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
>>> comm.uuid = "com-1"
>>> comm.date = "Thu, 20 Nov 2008 15:55:11 +0000"
>>> comm.author = "Jane Doe <jdoe@example.com>"
- >>> print comm
+ >>> print(comm)
--------- Comment ---------
Name: //com
From: Jane Doe <jdoe@example.com>
@@ -281,7 +305,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
>>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
>>> comm.uuid = "0123"
>>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000"
- >>> print comm.xml(indent=2)
+ >>> print(comm.xml(indent=2))
<comment>
<uuid>0123</uuid>
<short-name>//012</short-name>
@@ -293,7 +317,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
remarks</body>
</comment>
>>> comm.content_type = 'image/png'
- >>> print comm.xml()
+ >>> print(comm.xml())
<comment>
<uuid>0123</uuid>
<short-name>//012</short-name>
@@ -307,11 +331,11 @@ class Comment (Tree, settings_object.SavedSettingsObject):
if self.content_type.startswith('text/'):
body = (self.body or '').rstrip('\n')
else:
- maintype,subtype = self.content_type.split('/',1)
+ maintype, subtype = self.content_type.split('/', 1)
msg = MIMEBase(maintype, subtype)
msg.set_payload(self.body or '')
encode_base64(msg)
- body = base64.encodestring(self.body or '')
+ body = base64.encodebytes(self.body.encode() or b'')
info = [('uuid', self.uuid),
('alt-id', self.alt_id),
('short-name', self.id.user()),
@@ -321,9 +345,10 @@ class Comment (Tree, settings_object.SavedSettingsObject):
('content-type', self.content_type),
('body', body)]
lines = ['<comment>']
- for (k,v) in info:
- if v != None:
- lines.append(' <%s>%s</%s>' % (k,xml.sax.saxutils.escape(v),k))
+ for (k, v) in info:
+ if v is not None:
+ 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)
lines.append('</comment>')
@@ -358,24 +383,24 @@ class Comment (Tree, 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):
comment = xml_string
else:
comment = ElementTree.XML(xml_string)
if comment.tag != 'comment':
- raise utility.InvalidXML( \
+ raise utility.InvalidXML(
'comment', comment, 'root element must be <comment>')
- tags=['uuid','alt-id','in-reply-to','author','date','content-type',
- 'body','extra-string']
+ tags = ['uuid', 'alt-id', 'in-reply-to', 'author', 'date',
+ 'content-type', 'body', 'extra-string']
self.explicit_attrs = []
uuid = None
body = None
estrs = []
- for child in comment.getchildren():
+ for child in comment:
if child.tag == 'short-name':
pass
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)
@@ -384,27 +409,27 @@ class Comment (Tree, settings_object.SavedSettingsObject):
text = text.strip()
if child.tag == 'uuid' and not preserve_uuids:
uuid = text
- continue # don't set the comment's uuid tag.
+ continue # don't set the comment's uuid tag.
elif child.tag == 'body':
body = text
self.explicit_attrs.append(child.tag)
- continue # don't set the comment's body yet.
+ continue # don't set the comment's body yet.
elif child.tag == 'extra-string':
estrs.append(text)
- continue # don't set the comment's extra_string yet.
- attr_name = child.tag.replace('-','_')
+ continue # don't set the comment's extra_string yet.
+ attr_name = child.tag.replace('-', '_')
self.explicit_attrs.append(attr_name)
setattr(self, attr_name, text)
else:
libbe.LOG.warning(
'ignoring unknown tag {0} in {1}'.format(
child.tag, comment.tag))
- if uuid != self.uuid and self.alt_id == None:
+ if uuid != self.uuid and self.alt_id is None:
self.explicit_attrs.append('alt_id')
self.alt_id = uuid
- if body != None:
+ if body is not None:
if self.content_type.startswith('text/'):
- self.body = body+'\n' # restore trailing newline
+ self.body = body+'\n' # restore trailing newline
else:
self.body = base64.decodestring(body)
self.extra_strings = estrs
@@ -440,15 +465,18 @@ class Comment (Tree, settings_object.SavedSettingsObject):
Traceback (most recent call last):
...
ValueError: Merge would add extra string "TAG: useful" to comment 0123
- >>> print commA.author
+ >>> print(commA.author)
John
- >>> print commA.extra_strings
+ >>> print(commA.extra_strings)
['TAG: favorite', 'TAG: very helpful']
>>> commA.merge(commB, accept_changes=True,
... accept_extra_strings=True, change_exception=True)
- >>> print commA.extra_strings
+ >>> print(commA.extra_strings)
['TAG: favorite', 'TAG: useful', 'TAG: very helpful']
- >>> print commA.xml()
+ >>> print(type(commA))
+ >>> print(dir(commA))
+ >>> print(commA.xml())
+ >>> print(commA.xml())
<comment>
<uuid>0123</uuid>
<short-name>//012</short-name>
@@ -475,11 +503,12 @@ class Comment (Tree, settings_object.SavedSettingsObject):
if self.alt_id == self.uuid:
self.alt_id = None
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.append(estr)
elif change_exception:
- raise ValueError('Merge would add extra string "%s" to comment %s' \
+ raise ValueError(
+ 'Merge would add extra string "%s" to comment %s'
% (estr, self.uuid))
def string(self, indent=0):
@@ -487,7 +516,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
>>> comm = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
>>> comm.uuid = 'abcdef'
>>> comm.date = "Thu, 01 Jan 1970 00:00:00 +0000"
- >>> print comm.string(indent=2)
+ >>> print(comm.string(indent=2))
--------- Comment ---------
Name: //abc
From:
@@ -505,12 +534,13 @@ class Comment (Tree, settings_object.SavedSettingsObject):
lines.append("")
if self.content_type.startswith("text/"):
body = (self.body or "")
- if self.bug != None and self.bug.bugdir != None:
+ if self.bug is not None and self.bug.bugdir is not None:
body = libbe.util.id.long_to_short_text(
{self.bug.bugdir.uuid: self.bug.bugdir}, body)
lines.extend(body.splitlines())
else:
- lines.append("Content type %s not printable. Try XML output instead" % self.content_type)
+ lines.append(("Content type %s not printable. " +
+ "Try XML output instead") % self.content_type)
istring = ' '*indent
sep = '\n' + istring
@@ -539,7 +569,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
>>> 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)
+ >>> print(a.string_thread(flatten=True))
--------- Comment ---------
Name: //a
From:
@@ -564,7 +594,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
Date: Thu, 20 Nov 2008 04:00:00 +0000
<BLANKLINE>
Useful examples
- >>> print a.string_thread()
+ >>> print(a.string_thread())
--------- Comment ---------
Name: //a
From:
@@ -591,7 +621,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
Useful examples
"""
stringlist = []
- for depth,comment in self.thread(flatten=flatten):
+ for depth, comment in self.thread(flatten=flatten):
ind = 2*depth+indent
string_fn = getattr(comment, string_method_name)
stringlist.append(string_fn(indent=ind))
@@ -610,7 +640,7 @@ class Comment (Tree, 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 comment %s\n'
'(BE version missmatch?)' % self.id.user())
self._setup_saved_settings(settings)
@@ -632,9 +662,9 @@ class Comment (Tree, settings_object.SavedSettingsObject):
"""
if self.uuid == INVALID_UUID:
return
- assert self.storage != None, "Can't save without storage"
- assert self.body != None, "Can't save blank comment"
- if self.bug != None:
+ assert self.storage is not None, "Can't save without storage"
+ assert self.body is not None, "Can't save blank comment"
+ if self.bug is not None:
parent = self.bug.id.storage()
else:
parent = None
@@ -694,7 +724,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
for comment in self.traverse():
if comment.uuid == uuid:
return comment
- if match_alt_id == True and uuid != None \
+ if match_alt_id and uuid is not None \
and comment.alt_id == uuid:
return comment
raise KeyError(uuid)
@@ -702,68 +732,64 @@ class Comment (Tree, settings_object.SavedSettingsObject):
# methods for id generation
def sibling_uuids(self):
- if self.bug != None:
+ if self.bug is not None:
return self.bug.uuids()
return []
-def cmp_attr(comment_1, comment_2, attr, invert=False):
+def idx_attr(comment, attr):
"""
- Compare a general attribute between two comments using the conventional
- comparison rule for that attribute type. If invert == True, sort
- *against* that convention.
+ Generate an index for sorting of comments based on the given
+ attribute.
>>> attr="author"
>>> commentA = Comment()
- >>> commentB = Comment()
>>> commentA.author = "John Doe"
- >>> commentB.author = "Jane Doe"
- >>> cmp_attr(commentA, commentB, attr) > 0
- True
- >>> cmp_attr(commentA, commentB, attr, invert=True) < 0
- True
+ >>> idx_attr(commentA, attr)
+ 'John Doe'
+ >>> commentB = Comment()
>>> commentB.author = "John Doe"
- >>> cmp_attr(commentA, commentB, attr) == 0
+ >>> idx_attr(commentA, attr) == idx_attr(commentB, attr)
True
"""
- if not hasattr(comment_2, attr) :
- return 1
- val_1 = getattr(comment_1, attr)
- val_2 = getattr(comment_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 comment_1, comment_2 : cmp_attr(comment_1, comment_2, "uuid")
-cmp_author = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "author")
-cmp_in_reply_to = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "in_reply_to")
-cmp_content_type = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "content_type")
-cmp_body = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "body")
-cmp_extra_strings = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "extra_strings")
-# chronological rankings (newer < older)
-cmp_time = lambda comment_1, comment_2 : cmp_attr(comment_1, comment_2, "time", invert=True)
+def idx_uuid(comment):
+ return idx_attr(comment, "uuid")
+
+
+def idx_author(comment):
+ return idx_attr(comment, "author")
+
+
+def idx_in_reply_to(comment):
+ return idx_attr(comment, "in_reply_to")
-DEFAULT_CMP_FULL_CMP_LIST = \
- (cmp_time, cmp_author, cmp_content_type, cmp_body, cmp_in_reply_to,
- cmp_uuid, cmp_extra_strings)
+def idx_content_type(comment):
+ return idx_attr(comment, "content_type")
+
+
+def idx_body(comment):
+ return idx_attr(comment, "body")
+
+
+def idx_extra_strings(comment):
+ return idx_attr(comment, "extra_strings")
+
+
+# chronological rankings (newer < older)
+def idx_time(comment):
+ return idx_attr(comment, "time")
-class CommentCompoundComparator (object):
- def __init__(self, cmp_list=DEFAULT_CMP_FULL_CMP_LIST):
- self.cmp_list = cmp_list
- def __call__(self, comment_1, comment_2):
- for comparison in self.cmp_list :
- val = comparison(comment_1, comment_2)
- if val != 0 :
- return val
- return 0
-cmp_full = CommentCompoundComparator()
+DEFAULT_IDX_FULL_IDX_LIST = \
+ (idx_uuid, idx_author, idx_in_reply_to, idx_content_type, idx_body,
+ idx_extra_strings, idx_time)
if libbe.TESTING:
suite = doctest.DocTestSuite()
diff --git a/libbe/diff.py b/libbe/diff.py
index fe83881..595d512 100644
--- a/libbe/diff.py
+++ b/libbe/diff.py
@@ -22,6 +22,7 @@
"""
import difflib
+import functools
import types
import libbe
@@ -32,12 +33,7 @@ from libbe.storage.util.settings_object import setting_name_to_attr_name
from libbe.util.utility import time_to_str
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
-
+@functools.total_ordering
class SubscriptionType (libbe.util.tree.Tree):
"""Trees of subscription types to allow users to select exactly what
notifications they want to subscribe to.
@@ -47,8 +43,13 @@ class SubscriptionType (libbe.util.tree.Tree):
self.type = type_name
def __str__(self):
return self.type
- def __cmp__(self, other):
- return cmp(self.type, other.type)
+
+ def __eq__(self, other):
+ return self.type == other.type
+
+ def __lt__(self, other):
+ return self.type < other.type
+
def __repr__(self):
return '<SubscriptionType: %s>' % str(self)
def string_tree(self, indent=0):
@@ -87,6 +88,7 @@ def type_from_name(name, type_root, default=None, default_ok=False):
return default
raise InvalidType(name, type_root)
+@functools.total_ordering
class Subscription (object):
"""A user subscription.
@@ -96,7 +98,7 @@ class Subscription (object):
>>> subscriptions = [Subscription('XYZ', 'all'),
... Subscription('DIR', 'new'),
... Subscription('ABC', BUG_TYPE_ALL),]
- >>> print sorted(subscriptions)
+ >>> print(sorted(subscriptions))
[<Subscription: DIR (new)>, <Subscription: ABC (all)>, <Subscription: XYZ (all)>]
"""
def __init__(self, id, subscription_type, **kwargs):
@@ -109,17 +111,26 @@ class Subscription (object):
subscription_type = type_from_name(subscription_type, **kwargs)
self.id = id
self.type = subscription_type
- def __cmp__(self, other):
+
+ def __eq__(self, other):
+ return isinstance(other, Subscription) and \
+ (self.id == other.id) and \
+ (self.type == self.type)
+
+ def __lt__(self, other):
for attr in 'id', 'type':
- value = cmp(getattr(self, attr), getattr(other, attr))
- if value != 0:
+ left = getattr(self, attr)
+ right = getattr(other, attr)
+ if left != right:
if self.id == BUGDIR_ID:
- return -1
+ return True
elif other.id == BUGDIR_ID:
- return 1
- return value
+ return False
+ return left < right
+
def __str__(self):
return str(self.type)
+
def __repr__(self):
return '<Subscription: %s (%s)>' % (self.id, self.type)
@@ -165,12 +176,12 @@ class DiffTree (libbe.util.tree.Tree):
>>> bugs.append(new)
>>> rem = DiffTree('rem', 'removed bugs: RST, UVW')
>>> bugs.append(rem)
- >>> print bugdir.report_string()
+ >>> print(bugdir.report_string())
target: None -> 1.0
bug-count: 5 -> 6
new bugs: ABC, DEF
removed bugs: RST, UVW
- >>> print '\\n'.join(bugdir.paths())
+ >>> print('\\n'.join(bugdir.paths()))
bugdir
bugdir/settings
bugdir/bugs
@@ -189,7 +200,7 @@ class DiffTree (libbe.util.tree.Tree):
>>> bugdir.child_by_path('bugdir/bugs') == bugs
True
>>> bugdir.child_by_path('/bugs').masked = True
- >>> print bugdir.report_string()
+ >>> print(bugdir.report_string())
target: None -> 1.0
"""
def __init__(self, name, data=None, data_part_fn=str,
@@ -290,7 +301,7 @@ class Diff (object):
>>> c = bd_new.new_bug('Bug C', _uuid='c')
>>> d = Diff(bd, bd_new)
>>> r = d.report_tree()
- >>> print '\\n'.join(r.paths())
+ >>> print('\\n'.join(r.paths()))
bugdir
bugdir/settings
bugdir/bugs
@@ -306,7 +317,7 @@ class Diff (object):
bugdir/bugs/mod/a/comments/new/acom
bugdir/bugs/mod/a/comments/rem
bugdir/bugs/mod/a/comments/mod
- >>> print r.report_string()
+ >>> print(r.report_string())
Changed bug directory settings:
target: None -> 1.0
New bugs:
@@ -327,7 +338,7 @@ class Diff (object):
>>> subscriptions = [Subscription('DIR', BUGDIR_TYPE_NEW),
... Subscription('b', BUG_TYPE_ALL)]
>>> r = d.report_tree(subscriptions)
- >>> print r.report_string()
+ >>> print(r.report_string())
New bugs:
abc/c:om: Bug C
Removed bugs:
@@ -340,10 +351,10 @@ class Diff (object):
first, and then use report_tree() to avoid redundant comparisons.
>>> d.full_report()
- >>> print d.report_tree([subscriptions[0]]).report_string()
+ >>> print(d.report_tree([subscriptions[0]]).report_string())
New bugs:
abc/c:om: Bug C
- >>> print d.report_tree([subscriptions[1]]).report_string()
+ >>> print(d.report_tree([subscriptions[1]]).report_string())
Removed bugs:
abc/b:cm: Bug B
@@ -455,8 +466,15 @@ class Diff (object):
removed.sort()
modified.sort(self._bug_modified_cmp)
return (added, modified, removed)
+
def _bug_modified_cmp(self, left, right):
- return cmp(left[1], right[1])
+ l = left[1]
+ r = right[1]
+
+ if l == r: return 0
+ if l > r: return 1
+ if l < r: return -1
+
def _changed_comments(self, old, new):
"""
Search for differences in all loaded comments between the bugs
diff --git a/libbe/storage/base.py b/libbe/storage/base.py
index 7020c10..09e176b 100644
--- a/libbe/storage/base.py
+++ b/libbe/storage/base.py
@@ -21,6 +21,7 @@ Abstract bug repository data storage to easily support multiple backends.
"""
import copy
+import functools
import os
import pickle
@@ -39,14 +40,18 @@ if TESTING:
from libbe.util.utility import Dir
+
# https://stackoverflow.com/a/56719588/164233
def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
+ if a == b:
+ return 0
+ return (a > b) - (a < b)
class ConnectionError (Exception):
pass
+
class InvalidStorageVersion(ConnectionError):
def __init__(self, active_version, expected_version=None):
if expected_version is None:
@@ -57,12 +62,14 @@ class InvalidStorageVersion(ConnectionError):
self.active_version = active_version
self.expected_version = expected_version
+
class InvalidID (KeyError):
def __init__(self, id=None, revision=None, msg=None):
KeyError.__init__(self, id)
self.msg = msg
self.id = id
self.revision = revision
+
def __str__(self):
if self.msg is None:
return '%s in revision %s' % (self.id, self.revision)
@@ -94,6 +101,7 @@ class _EMPTY (object):
"""Entry has been added but has no user-set value."""
pass
+@functools.total_ordering
class Entry (Tree):
def __init__(self, id, value=_EMPTY, parent=None, directory=False,
children=None):
@@ -104,7 +112,7 @@ class Entry (Tree):
self.id = id
self.value = value
self.parent = parent
- if self.parent != None:
+ if self.parent is not None:
if self.parent.directory == False:
raise InvalidDirectory(
'Non-directory %s cannot have children' % self.parent)
@@ -117,33 +125,49 @@ class Entry (Tree):
def __repr__(self):
return str(self)
- def __cmp__(self, other, local=False):
+ def __eq__(self, other, local=False):
+ if other is None:
+ return False
+ if (self.id != other.id) or (self.value != other.value):
+ return False
+ if not local:
+ if self.parent is None:
+ if self.parent != other.parent:
+ return False
+ elif not self.parent.__eq__(other.parent, local=True):
+ return False
+ for sc, oc in zip(self, other):
+ if not sc.__eq__(oc, local=True):
+ return False
+ return True
+
+ def __lt__(self, other, local=False):
if other is None:
- return cmp(1, None)
- if cmp(self.id, other.id) != 0:
- return cmp(self.id, other.id)
- if cmp(self.value, other.value) != 0:
- return cmp(self.value, other.value)
- if local == False:
+ return False
+ if self.id != other.id:
+ return self.id < other.id
+ if self.value != other.value:
+ return self.value < other.value
+ if not local:
if self.parent is None:
- if cmp(self.parent, other.parent) != 0:
- return cmp(self.parent, other.parent)
- elif self.parent.__cmp__(other.parent, local=True) != 0:
- return self.parent.__cmp__(other.parent, local=True)
+ if self.parent != other.parent:
+ return self.parent < other.parent
+ elif not self.parent.__eq__(other.parent, local=True):
+ return self.parent.__lt__(other.parent, local=True)
for sc,oc in zip(self, other):
- if sc.__cmp__(oc, local=True) != 0:
- return sc.__cmp__(oc, local=True)
- return 0
+ if not sc.__eq__(oc, local=True):
+ return sc.__lt__(oc, local=True)
+ return False
def _objects_to_ids(self):
- if self.parent != None:
+ if self.parent is not None:
self.parent = self.parent.id
for i,c in enumerate(self):
self[i] = c.id
return self
def _ids_to_objects(self, dict):
- if self.parent != None:
+ if self.parent is not None:
self.parent = dict[self.parent]
for i,c in enumerate(self):
self[i] = dict[c]
@@ -276,7 +300,7 @@ class Storage (object):
self._remove(*args, **kwargs)
def _remove(self, id):
- if self._data[id].directory == True \
+ if self._data[id].directory \
and len(self.children(id)) > 0:
raise DirectoryNotEmpty(id)
e = self._data.pop(id)
@@ -307,7 +331,7 @@ class Storage (object):
while len(stack) > 0:
id = stack.pop(0)
parent = self._data[id].parent
- if parent != None and not parent.id.startswith('__'):
+ if parent is not None and not parent.id.startswith('__'):
ancestor = parent.id
ancestors.append(ancestor)
stack.append(ancestor)
@@ -339,8 +363,8 @@ class Storage (object):
else:
decode = False
value = self._get(*args, **kwargs)
- if value != None:
- if decode == True and type(value) != str:
+ if value is not None:
+ if decode and type(value) != str:
return str(value, self.encoding)
elif decode == False and type(value) != bytes:
return value.encode(self.encoding)
@@ -425,7 +449,7 @@ class VersionedStorage (Storage):
return id in self._data[revision]
def _remove(self, id):
- if self._data[-1][id].directory == True \
+ if self._data[-1][id].directory \
and len(self.children(id)) > 0:
raise DirectoryNotEmpty(id)
e = self._data[-1].pop(id)
@@ -447,7 +471,7 @@ class VersionedStorage (Storage):
while len(stack) > 0:
id = stack.pop(0)
parent = self._data[revision][id].parent
- if parent != None and not parent.id.startswith('__'):
+ if parent is not None and not parent.id.startswith('__'):
ancestor = parent.id
ancestors.append(ancestor)
stack.append(ancestor)
diff --git a/libbe/storage/util/properties.py b/libbe/storage/util/properties.py
index ad9d06c..6a2b964 100644
--- a/libbe/storage/util/properties.py
+++ b/libbe/storage/util/properties.py
@@ -46,11 +46,6 @@ if libbe.TESTING:
import unittest
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
class ValueCheckError (ValueError):
def __init__(self, name, value, allowed):
action = "in" # some list of allowed values
@@ -72,8 +67,8 @@ def Property(funcs):
args["fdel"] = funcs.get("fdel", None)
args["doc"] = funcs.get("doc", None)
- #print "Creating a property with"
- #for key, val in args.items(): print key, value
+ #print("Creating a property with")
+ #for key, val in args.items(): print(key, value)
return property(**args)
def doc_property(doc=None):
@@ -179,12 +174,12 @@ def _get_cached_mutable_property(self, cacher_name, property_name, default=None)
if (cacher_name, property_name) not in self._mutable_property_cache_copy:
return default
return self._mutable_property_cache_copy[(cacher_name, property_name)]
-def _cmp_cached_mutable_property(self, cacher_name, property_name, value, default=None):
+def _eq_cached_mutable_property(self, cacher_name, property_name, value, default=None):
_init_mutable_property_cache(self)
if (cacher_name, property_name) not in self._mutable_property_cache_hash:
_set_cached_mutable_property(self, cacher_name, property_name, default)
old_hash = self._mutable_property_cache_hash[(cacher_name, property_name)]
- return cmp(_hash_mutable_value(value), old_hash)
+ return _hash_mutable_value(value) == old_hash
def defaulting_property(default=None, null=None,
@@ -389,7 +384,7 @@ def change_hook_property(hook, mutable=False, default=None):
value = new_value # compare new value with cached
else:
value = fget(self) # compare current value with cached
- if _cmp_cached_mutable_property(self, "change hook property", name, value, default) != 0:
+ if _eq_cached_mutable_property(self, "change hook property", name, value, default) != 0:
# there has been a change, cache new value
old_value = _get_cached_mutable_property(self, "change hook property", name, default)
_set_cached_mutable_property(self, "change hook property", name, value)
diff --git a/libbe/storage/util/settings_object.py b/libbe/storage/util/settings_object.py
index 323d6e9..d872f49 100644
--- a/libbe/storage/util/settings_object.py
+++ b/libbe/storage/util/settings_object.py
@@ -79,7 +79,7 @@ def setting_name_to_attr_name(self, name):
Examples
--------
- >>> print setting_name_to_attr_name(None,"User-id")
+ >>> print(setting_name_to_attr_name(None,"User-id"))
user_id
See Also
@@ -94,7 +94,7 @@ def attr_name_to_setting_name(self, name):
Examples:
- >>> print attr_name_to_setting_name(None, "user_id")
+ >>> print(attr_name_to_setting_name(None, "user_id"))
User-id
See Also
@@ -294,7 +294,7 @@ if libbe.TESTING == True:
return self.readable
def is_writeable(self):
return self.writeable
-
+
class TestObject (SavedSettingsObject):
def load_settings(self):
self.load_count += 1
diff --git a/libbe/storage/vcs/base.py b/libbe/storage/vcs/base.py
index 579935e..0521e90 100644
--- a/libbe/storage/vcs/base.py
+++ b/libbe/storage/vcs/base.py
@@ -51,11 +51,6 @@ if libbe.TESTING:
import libbe.ui.util.user
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
VCS_ORDER = ['bzr', 'darcs', 'git', 'hg', 'monotone']
"""List VCS modules in order of preference.
diff --git a/libbe/storage/vcs/darcs.py b/libbe/storage/vcs/darcs.py
index 5ec9a26..12eabf4 100644
--- a/libbe/storage/vcs/darcs.py
+++ b/libbe/storage/vcs/darcs.py
@@ -44,12 +44,6 @@ if libbe.TESTING:
import unittest
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
-
def new():
return Darcs()
@@ -286,9 +280,9 @@ class Darcs(base.VCS):
xml_str = output.encode('unicode_escape').replace(r'\n', '\n')
element = ElementTree.XML(xml_str)
assert element.tag == 'changelog', element.tag
- for patch in element.getchildren():
+ for patch in element:
assert patch.tag == 'patch', patch.tag
- for child in patch.getchildren():
+ for child in patch:
if child.tag == 'name':
text = unescape(str(child.text).decode('unicode_escape').strip())
revisions.append(text)
diff --git a/libbe/storage/vcs/monotone.py b/libbe/storage/vcs/monotone.py
index add7be9..1d74c5b 100644
--- a/libbe/storage/vcs/monotone.py
+++ b/libbe/storage/vcs/monotone.py
@@ -33,16 +33,11 @@ import libbe.ui.util.user
from ...util.subproc import CommandError
from . import base
-if libbe.TESTING == True:
+if libbe.TESTING:
import doctest
import sys
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
def new():
return Monotone()
@@ -127,7 +122,7 @@ class Monotone (base.VCS):
private = False
for line in output.splitlines():
line = line.strip()
- if private == True: # HACK. Just pick the first key.
+ if private: # HACK. Just pick the first key.
return line.split(' ', 1)[1]
if line == '[private keys]':
private = True
@@ -149,7 +144,7 @@ class Monotone (base.VCS):
'automate', 'get_workspace_root', cwd=dirname)
else:
mtn_dir = self._u_search_parent_directories(path, '_MTN')
- if mtn_dir == None:
+ if mtn_dir is None:
return None
return os.path.dirname(mtn_dir)
return output.strip()
@@ -206,7 +201,7 @@ class Monotone (base.VCS):
pass
def _vcs_get_file_contents(self, path, revision=None):
- if revision == None:
+ if revision is None:
return base.VCS._vcs_get_file_contents(self, path, revision)
else:
self._require_version_ge(4, 0)
@@ -373,7 +368,7 @@ class Monotone (base.VCS):
return self._parse_diff(self._diff(revision))
-if libbe.TESTING == True:
+if libbe.TESTING:
base.make_vcs_testcase_subclasses(Monotone, sys.modules[__name__])
unitsuite =unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py
index be6f80a..b649e24 100644
--- a/libbe/ui/command_line.py
+++ b/libbe/ui/command_line.py
@@ -206,7 +206,7 @@ class BE (libbe.command.Command):
>>> try:
... options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS
... except CallbackExit:
- ... print ' got callback'
+ ... print(' got callback')
--help
--version
...
diff --git a/libbe/util/tree.py b/libbe/util/tree.py
index 5d0da49..61525aa 100644
--- a/libbe/util/tree.py
+++ b/libbe/util/tree.py
@@ -21,11 +21,13 @@
"""Define :py:class:`Tree`, a traversable tree structure.
"""
+import functools
import libbe
if libbe.TESTING:
import doctest
+@functools.total_ordering
class Tree(list):
"""A traversable tree structure.
@@ -78,7 +80,7 @@ class Tree(list):
Serialize the tree with depth marking branches.
>>> for depth,node in a.thread():
- ... print "%*s" % (2*depth+1, node.n)
+ ... print("%*s" % (2*depth+1, node.n))
a
b
d
@@ -93,7 +95,7 @@ class Tree(list):
branch splits.
>>> for depth,node in a.thread(flatten=True):
- ... print "%*s" % (2*depth+1, node.n)
+ ... print("%*s" % (2*depth+1, node.n))
a
b
d
@@ -115,14 +117,12 @@ class Tree(list):
>>> a.has_descendant(a, match_self=True)
True
"""
- def __cmp__(self, other):
- return (id(self) > id(other)) - (id(self) < id(other))
+ # TODO: is this correct? what has id() to do with the magnitude of an object?
+ def __lt__(self, other):
+ return id(self) < id(other)
def __eq__(self, other):
- return self.__cmp__(other) == 0
-
- def __ne__(self, other):
- return self.__cmp__(other) != 0
+ return id(self) == id(other)
def branch_len(self):
"""Return the largest number of nodes from root to leaf (inclusive).
diff --git a/libbe/util/wsgi.py b/libbe/util/wsgi.py
index 46e1b3c..cfa43be 100644
--- a/libbe/util/wsgi.py
+++ b/libbe/util/wsgi.py
@@ -24,6 +24,7 @@ See Also
"""
import copy
+import functools
import hashlib
import logging
import logging.handlers
@@ -66,11 +67,6 @@ import libbe.util.http
import libbe.util.id
-# https://stackoverflow.com/a/56719588/164233
-def cmp(a, b):
- return (int(a) > int(b)) - (int(a) < int(b))
-
-
if libbe.TESTING:
import doctest
import unittest
@@ -95,6 +91,7 @@ class Unauthorized (HandlerError):
super(Unauthorized, self).__init__(403, msg, headers)
+@functools.total_ordering
class User (object):
def __init__(self, uname=None, name=None, passhash=None, password=None):
self.uname = uname
@@ -120,8 +117,11 @@ class User (object):
def __str__(self):
return ':'.join([self.uname, self.name, self.passhash])
- def __cmp__(self, other):
- return cmp(self.uname, other.uname)
+ def __eq__(self, other):
+ return self.uname == other.uname
+
+ def __lt__(self, other):
+ return self.uname < other.uname
def hash(self, password):
return hashlib.sha1(password).hexdigest()