aboutsummaryrefslogtreecommitdiffstats
path: root/libbe/comment.py
diff options
context:
space:
mode:
Diffstat (limited to 'libbe/comment.py')
-rw-r--r--libbe/comment.py250
1 files changed, 138 insertions, 112 deletions
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()