Merged Anton Batenev's report of Nicolas Alvarez' unicode-in-be-new bug
[be.git] / libbe / comment.py
index f5a630989d11a46b8ee5eccdcced071172e9f129..d8632a4dc670a10604710f0e083488375bc58577 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2008-2009 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it>
 #                         Thomas Habets <thomas@habets.pp.se>
 #                         W. Trevor King <wking@drexel.edu>
 #
@@ -16,8 +16,7 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-"""
-Define the Comment class for representing bug comments.
+"""Define the :class:`Comment` class for representing bug comments.
 """
 
 import base64
@@ -26,6 +25,13 @@ import os.path
 import sys
 import time
 import types
+try:
+    from email.mime.base import MIMEBase
+    from email.encoders import encode_base64
+except ImportError:
+    # adjust to old python 2.4
+    from email.MIMEBase import MIMEBase
+    from email.Encoders import encode_base64
 try: # import core module, Python >= 2.5
     from xml.etree import ElementTree
 except ImportError: # look for non-core module
@@ -37,7 +43,7 @@ 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
-import libbe.storage.settings_object as settings_object
+import libbe.storage.util.settings_object as settings_object
 import libbe.storage.util.mapfile as mapfile
 from libbe.util.tree import Tree
 import libbe.util.utility as utility
@@ -73,10 +79,10 @@ def load_comments(bug, load_full=False):
     from disk *now*, rather than waiting and lazy loading as required.
     """
     uuids = []
-    for id in bug.storage.children():
-        parsed = libbe.util.id.parse_id(id)
-        if parsed['type'] == 'comment':
-            uuids.append(parsed['comment'])
+    for id in libbe.util.id.child_uuids(
+                  bug.storage.children(
+                      bug.id.storage())):
+        uuids.append(id)
     comments = []
     for uuid in uuids:
         comm = Comment(bug, uuid, from_storage=True)
@@ -85,7 +91,7 @@ def load_comments(bug, load_full=False):
             dummy = comm.body # force the body to load
         comments.append(comm)
     bug.comment_root = Comment(bug, uuid=INVALID_UUID)
-    bug.add_comments(comments)
+    bug.add_comments(comments, ignore_missing_references=True)
     return bug.comment_root
 
 def save_comments(bug):
@@ -93,8 +99,11 @@ def save_comments(bug):
         comment.save()
 
 
-class Comment(Tree, settings_object.SavedSettingsObject):
-    """
+class Comment (Tree, settings_object.SavedSettingsObject):
+    """Comments are a notes that attach to :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
     True
@@ -120,14 +129,8 @@ class Comment(Tree, settings_object.SavedSettingsObject):
                          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 {}
 
-    def _get_user_id(self):
-        if self.bug != None:
-            return self.bug._get_user_id()
-        return None
-
     @_versioned_property(name="Author",
-                         doc="The author of the comment",
-                         generator=_get_user_id)
+                         doc="The author of the comment")
     def author(): return {}
 
     @_versioned_property(name="In-reply-to",
@@ -157,14 +160,17 @@ class Comment(Tree, settings_object.SavedSettingsObject):
     def _get_comment_body(self):
         if self.storage != None and self.storage.is_readable() \
                 and self.uuid != INVALID_UUID:
-            return self.storage.get(self.id("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:
+            new = libbe.util.id.short_to_long_text([self.bug.bugdir], new)
         if (self.storage != None and self.storage.writeable == True) \
                 or force==True:
             assert new != None, "Can't save empty comment"
-            self.storage.set(self.id("body"), new)
+            self.storage.set(self.id.storage("body"), new)
 
     @Property
     @change_hook_property(hook=_set_comment_body)
@@ -173,16 +179,6 @@ class Comment(Tree, settings_object.SavedSettingsObject):
     @doc_property(doc="The meat of the comment")
     def body(): return {}
 
-    def _get_storage(self):
-        if hasattr(self.bug, "storage"):
-            return self.bug.storage
-
-    @Property
-    @cached_property(generator=_get_storage)
-    @local_property("storage")
-    @doc_property(doc="A revision control system instance.")
-    def storage(): return {}
-
     def _extra_strings_check_fn(value):
         return utility.iterable_full_of_strings(value, \
                          alternative=settings_object.EMPTY)
@@ -198,38 +194,40 @@ class Comment(Tree, settings_object.SavedSettingsObject):
     def extra_strings(): return {}
 
     def __init__(self, bug=None, uuid=None, from_storage=False,
-                 in_reply_to=None, body=None):
+                 in_reply_to=None, body=None, content_type=None):
         """
-        Set from_storage=True to load an old comment.
-        Set from_storage=False to create a new comment.
-
-        The uuid option is required when from_storage==True.
-        
-        The in_reply_to and body options are only used if
-        from_storage==False (the default).  When from_storage==True,
-        they are loaded from the bug database.
-        
-        in_reply_to should be the uuid string of the parent comment.
+        Set ``from_storage=True`` to load an old comment.
+        Set ``from_storage=False`` to create a new comment.
+
+        The ``uuid`` option is required when ``from_storage==True``.
+
+        The in_reply_to, body, and content_type options are only used
+        if ``from_storage==False`` (the default).  When
+        ``from_storage==True``, they are loaded from the bug database.
+        ``content_type`` decides if the body should be run through
+        :func:`util.id.short_to_long_text` before saving.  See
+        :meth:`_set_comment_body` for details.
+
+        ``in_reply_to`` should be the uuid string of the parent comment.
         """
         Tree.__init__(self)
         settings_object.SavedSettingsObject.__init__(self)
         self.bug = bug
-        self.uuid = uuid 
+        self.storage = None
+        self.uuid = uuid
+        self.id = libbe.util.id.ID(self, 'comment')
         if from_storage == False:
             if uuid == None:
                 self.uuid = libbe.util.id.uuid_gen()
-            self.settings = {}
-            self._setup_saved_settings()
-            if self.storage != None and self.storage.is_writeable():
-                self.storage.writeable = False
-                set_writeable = True
-            else:
-                set_writeable = False
             self.time = int(time.time()) # only save to second precision
             self.in_reply_to = in_reply_to
+            if content_type != None:
+                self.content_type = content_type
             self.body = body
-            if set_writeable == True:
-                self.storage.writeable = True
+        if self.bug != None:
+            self.storage = self.bug.storage
+        if from_storage == False:
+            if self.storage != None and self.storage.is_writeable():
                 self.save()
 
     def __cmp__(self, other):
@@ -243,7 +241,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         >>> comm.author = "Jane Doe <jdoe@example.com>"
         >>> print comm
         --------- Comment ---------
-        Name: com-1
+        Name: //com
         From: Jane Doe <jdoe@example.com>
         Date: Thu, 20 Nov 2008 15:55:11 +0000
         <BLANKLINE>
@@ -268,15 +266,33 @@ class Comment(Tree, settings_object.SavedSettingsObject):
             return str(value)
         return value
 
-    def xml(self, indent=0, shortname=None):
+    def safe_in_reply_to(self):
+        """
+        Return self.in_reply_to, except...
+
+          * if no comment matches that id, in which case return None.
+          * if that id matches another comments .alt_id, in which case
+            return the matching comments .uuid.
+        """
+        if self.in_reply_to == None:
+            return None
+        else:
+            try:
+                irt_comment = self.bug.comment_from_uuid(
+                    self.in_reply_to, match_alt_id=True)
+                return irt_comment.uuid
+            except KeyError:
+                return None
+
+    def xml(self, indent=0):
         """
         >>> 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, shortname="com-1")
+        >>> print comm.xml(indent=2)
           <comment>
             <uuid>0123</uuid>
-            <short-name>com-1</short-name>
+            <short-name>//012</short-name>
             <author></author>
             <date>Thu, 01 Jan 1970 00:00:00 +0000</date>
             <content-type>text/plain</content-type>
@@ -284,21 +300,30 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         insightful
         remarks</body>
           </comment>
+        >>> comm.content_type = 'image/png'
+        >>> print comm.xml()
+        <comment>
+          <uuid>0123</uuid>
+          <short-name>//012</short-name>
+          <author></author>
+          <date>Thu, 01 Jan 1970 00:00:00 +0000</date>
+          <content-type>image/png</content-type>
+          <body>U29tZQppbnNpZ2h0ZnVsCnJlbWFya3MK
+        </body>
+        </comment>
         """
-        if shortname == None:
-            shortname = self.uuid
         if self.content_type.startswith('text/'):
             body = (self.body or '').rstrip('\n')
         else:
             maintype,subtype = self.content_type.split('/',1)
-            msg = email.mime.base.MIMEBase(maintype, subtype)
+            msg = MIMEBase(maintype, subtype)
             msg.set_payload(self.body or '')
-            email.encoders.encode_base64(msg)
+            encode_base64(msg)
             body = base64.encodestring(self.body or '')
         info = [('uuid', self.uuid),
                 ('alt-id', self.alt_id),
-                ('short-name', shortname),
-                ('in-reply-to', self.in_reply_to),
+                ('short-name', self.id.user()),
+                ('in-reply-to', self.safe_in_reply_to()),
                 ('author', self._setting_attr_string('author')),
                 ('date', self.date),
                 ('content-type', self.content_type),
@@ -315,7 +340,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         return istring + sep.join(lines).rstrip('\n')
 
     def from_xml(self, xml_string, verbose=True):
-        """
+        u"""
         Note: If alt-id is not given, translates any <uuid> fields to
         <alt-id> fields.
         >>> commA = Comment(bug=None, body="Some\\ninsightful\\nremarks\\n")
@@ -323,16 +348,16 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         >>> commA.date = "Thu, 01 Jan 1970 00:00:00 +0000"
         >>> commA.author = u'Fran\xe7ois'
         >>> commA.extra_strings += ['TAG: very helpful']
-        >>> xml = commA.xml(shortname="com-1")
+        >>> xml = commA.xml()
         >>> commB = Comment()
         >>> commB.from_xml(xml, verbose=True)
         >>> commB.explicit_attrs
         ['author', 'date', 'content_type', 'body', 'alt_id']
-        >>> commB.xml(shortname="com-1") == xml
+        >>> commB.xml() == xml
         False
         >>> commB.uuid = commB.alt_id
         >>> commB.alt_id = None
-        >>> commB.xml(shortname="com-1") == xml
+        >>> commB.xml() == xml
         True
         """
         if type(xml_string) == types.UnicodeType:
@@ -385,11 +410,12 @@ class Comment(Tree, settings_object.SavedSettingsObject):
                 self.body = base64.decodestring(body)
         self.extra_strings = estrs
 
-    def merge(self, other, accept_changes=True, 
+    def merge(self, other, accept_changes=True,
               accept_extra_strings=True, change_exception=False):
         """
         Merge info from other into this comment.  Overrides any
         attributes in self that are listed in other.explicit_attrs.
+
         >>> commA = Comment(bug=None, body='Some insightful remarks')
         >>> commA.uuid = '0123'
         >>> commA.date = 'Thu, 01 Jan 1970 00:00:00 +0000'
@@ -426,7 +452,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         >>> print commA.xml()
         <comment>
           <uuid>0123</uuid>
-          <short-name>0123</short-name>
+          <short-name>//012</short-name>
           <author>John</author>
           <date>Thu, 01 Jan 1970 00:00:00 +0000</date>
           <content-type>text/plain</content-type>
@@ -457,13 +483,14 @@ class Comment(Tree, settings_object.SavedSettingsObject):
                         'Merge would add extra string "%s" to comment %s' \
                         % (estr, self.uuid)
 
-    def string(self, indent=0, shortname=None):
+    def string(self, indent=0):
         """
         >>> 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, shortname="com-1")
+        >>> print comm.string(indent=2)
           --------- Comment ---------
-          Name: com-1
+          Name: //abc
           From: 
           Date: Thu, 01 Jan 1970 00:00:00 +0000
         <BLANKLINE>
@@ -471,42 +498,34 @@ class Comment(Tree, settings_object.SavedSettingsObject):
           insightful
           remarks
         """
-        if shortname == None:
-            shortname = self.uuid
         lines = []
         lines.append("--------- Comment ---------")
-        lines.append("Name: %s" % shortname)
+        lines.append("Name: %s" % self.id.user())
         lines.append("From: %s" % (self._setting_attr_string("author")))
         lines.append("Date: %s" % self.date)
         lines.append("")
         if self.content_type.startswith("text/"):
-            lines.extend((self.body or "").splitlines())
+            body = (self.body or "")
+            if self.bug != None and self.bug.bugdir != None:
+                body = libbe.util.id.long_to_short_text([self.bug.bugdir], body)
+            lines.extend(body.splitlines())
         else:
             lines.append("Content type %s not printable.  Try XML output instead" % self.content_type)
-        
+
         istring = ' '*indent
         sep = '\n' + istring
         return istring + sep.join(lines).rstrip('\n')
 
-    def string_thread(self, string_method_name="string", name_map={},
-                      indent=0, flatten=True,
-                      auto_name_map=False, bug_shortname=None):
+    def string_thread(self, string_method_name="string",
+                      indent=0, flatten=True):
         """
         Return a string displaying a thread of comments.
         bug_shortname is only used if auto_name_map == True.
-        
+
         string_method_name (defaults to "string") is the name of the
         Comment method used to generate the output string for each
         Comment in the thread.  The method must take the arguments
         indent and shortname.
-        
-        SIDE-EFFECT: if auto_name_map==True, calls comment_shortnames()
-        which will sort the tree by comment.time.  Avoid by calling
-          name_map = {}
-          for shortname,comment in comm.comment_shortnames(bug_shortname):
-              name_map[comment.uuid] = shortname
-          comm.sort(key=lambda c : c.author) # your sort
-          comm.string_thread(name_map=name_map)
 
         >>> a = Comment(bug=None, uuid="a", body="Insightful remarks")
         >>> a.time = utility.str_to_time("Thu, 20 Nov 2008 01:00:00 +0000")
@@ -522,100 +541,91 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         >>> a.sort(key=lambda comm : comm.time)
         >>> print a.string_thread(flatten=True)
         --------- Comment ---------
-        Name: a
+        Name: //a
         From: 
         Date: Thu, 20 Nov 2008 01:00:00 +0000
         <BLANKLINE>
         Insightful remarks
           --------- Comment ---------
-          Name: b
+          Name: //b
           From: 
           Date: Thu, 20 Nov 2008 02:00:00 +0000
         <BLANKLINE>
           Critique original comment
           --------- Comment ---------
-          Name: c
+          Name: //c
           From: 
           Date: Thu, 20 Nov 2008 03:00:00 +0000
         <BLANKLINE>
           Begin flamewar :p
         --------- Comment ---------
-        Name: d
+        Name: //d
         From: 
         Date: Thu, 20 Nov 2008 04:00:00 +0000
         <BLANKLINE>
         Useful examples
-        >>> print a.string_thread(auto_name_map=True, bug_shortname="bug-1")
+        >>> print a.string_thread()
         --------- Comment ---------
-        Name: bug-1:1
+        Name: //a
         From: 
         Date: Thu, 20 Nov 2008 01:00:00 +0000
         <BLANKLINE>
         Insightful remarks
           --------- Comment ---------
-          Name: bug-1:2
+          Name: //b
           From: 
           Date: Thu, 20 Nov 2008 02:00:00 +0000
         <BLANKLINE>
           Critique original comment
           --------- Comment ---------
-          Name: bug-1:3
+          Name: //c
           From: 
           Date: Thu, 20 Nov 2008 03:00:00 +0000
         <BLANKLINE>
           Begin flamewar :p
         --------- Comment ---------
-        Name: bug-1:4
+        Name: //d
         From: 
         Date: Thu, 20 Nov 2008 04:00:00 +0000
         <BLANKLINE>
         Useful examples
         """
-        if auto_name_map == True:
-            name_map = {}
-            for shortname,comment in self.comment_shortnames(bug_shortname):
-                name_map[comment.uuid] = shortname
         stringlist = []
         for depth,comment in self.thread(flatten=flatten):
             ind = 2*depth+indent
-            if comment.uuid in name_map:
-                sname = name_map[comment.uuid]
-            else:
-                sname = None
             string_fn = getattr(comment, string_method_name)
-            stringlist.append(string_fn(indent=ind, shortname=sname))
+            stringlist.append(string_fn(indent=ind))
         return '\n'.join(stringlist)
 
-    def xml_thread(self, name_map={}, indent=0,
-                   auto_name_map=False, bug_shortname=None):
-        return self.string_thread(string_method_name="xml", name_map=name_map,
-                                  indent=indent, auto_name_map=auto_name_map,
-                                  bug_shortname=bug_shortname)
+    def xml_thread(self, indent=0):
+        return self.string_thread(string_method_name="xml", indent=indent)
 
     # methods for saving/loading/acessing settings and properties.
 
-    def id(self, *args):
-        assert len(args) <= 1, str(args)
-        if len(args) == 1:
-            assert args[0] in ["values", "body"], str(args)
-        return libbe.util.id.comment_id(self, *args)
-
     def load_settings(self, settings_mapfile=None):
+        if self.uuid == INVALID_UUID:
+            return
         if settings_mapfile == None:
             settings_mapfile = \
-                self.storage.get(self.id("values"), default="\n")
-        self.settings = mapfile.parse(settings_mapfile)
-        self._setup_saved_settings()
+                self.storage.get(self.id.storage("values"), default="\n")
+        try:
+            settings = mapfile.parse(settings_mapfile)
+        except mapfile.InvalidMapfileContents, e:
+            raise Exception('Invalid settings file for comment %s\n'
+                            '(BE version missmatch?)' % self.id.user())
+        self._setup_saved_settings(settings)
 
     def save_settings(self):
+        if self.uuid == INVALID_UUID:
+            return
         mf = mapfile.generate(self._get_saved_settings())
-        self.storage.set(self.id("values"), mf)
+        self.storage.set(self.id.storage("values"), mf)
 
     def save(self):
         """
         Save any loaded contents to storage.
-        
-        However, if self.storage.is_writeable() == True, then any
+
+        However, if ``self.storage.is_writeable() == True``, then any
         changes are automatically written to storage as soon as they
         happen, so calling this method will just waste time (unless
         something else has been messing with your stored files).
@@ -625,12 +635,14 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         assert self.storage != None, "Can't save without storage"
         assert self.body != None, "Can't save blank comment"
         if self.bug != None:
-            parent = self.bug.id()
+            parent = self.bug.id.storage()
         else:
             parent = None
-        self.storage.add(self.id(), parent=parent)
-        self.storage.add(self.id('values'), parent=self.id())
-        self.storage.add(self.id('body'), parent=self.id())
+        self.storage.add(self.id.storage(), parent=parent, directory=True)
+        self.storage.add(self.id.storage('values'), parent=self.id.storage(),
+                         directory=False)
+        self.storage.add(self.id.storage('body'), parent=self.id.storage(),
+                         directory=False)
         self.save_settings()
         self._set_comment_body(new=self.body, force=True)
 
@@ -638,7 +650,7 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         for comment in self:
             comment.remove()
         if self.uuid != INVALID_UUID:
-            self.storage.recursive_remove(self.id())
+            self.storage.recursive_remove(self.id.storage())
 
     def add_reply(self, reply, allow_time_inversion=False):
         if self.uuid != INVALID_UUID:
@@ -653,70 +665,13 @@ class Comment(Tree, settings_object.SavedSettingsObject):
         >>> repB.in_reply_to == repA.uuid
         True
         """
-        reply = Comment(self.bug, body=body)
-        if content_type != None: # set before saving body to decide binary format
-            reply.content_type = content_type
-        if reply.storage != None and reply.storage.is_writeable():
-            reply.save()
+        reply = Comment(self.bug, body=body, content_type=content_type)
         self.add_reply(reply)
         return reply
 
-    def comment_shortnames(self, bug_shortname=None):
-        """
-        Iterate through (id, comment) pairs, in time order.
-        (This is a user-friendly id, not the comment uuid).
-
-        SIDE-EFFECT : will sort the comment tree by comment.time
-
-        >>> a = Comment(bug=None, uuid="a")
-        >>> b = a.new_reply()
-        >>> b.uuid = "b"
-        >>> c = b.new_reply()
-        >>> c.uuid = "c"
-        >>> d = a.new_reply()
-        >>> d.uuid = "d"
-        >>> for id,name in a.comment_shortnames("bug-1"):
-        ...     print id, name.uuid
-        bug-1:1 a
-        bug-1:2 b
-        bug-1:3 c
-        bug-1:4 d
-        >>> for id,name in a.comment_shortnames():
-        ...     print id, name.uuid
-        :1 a
-        :2 b
-        :3 c
-        :4 d
-        """
-        if bug_shortname == None:
-            bug_shortname = ""
-        self.sort(key=lambda comm : comm.time)
-        for num,comment in enumerate(self.traverse()):
-            yield ("%s:%d" % (bug_shortname, num+1), comment)
-
-    def comment_from_shortname(self, comment_shortname, *args, **kwargs):
-        """
-        Use a comment shortname to look up a comment.
-        >>> a = Comment(bug=None, uuid="a")
-        >>> b = a.new_reply()
-        >>> b.uuid = "b"
-        >>> c = b.new_reply()
-        >>> c.uuid = "c"
-        >>> d = a.new_reply()
-        >>> d.uuid = "d"
-        >>> comm = a.comment_from_shortname("bug-1:3", bug_shortname="bug-1")
-        >>> id(comm) == id(c)
-        True
-        """
-        for cur_name, comment in self.comment_shortnames(*args, **kwargs):
-            if comment_shortname == cur_name:
-                return comment
-        raise InvalidShortname(comment_shortname,
-                               list(self.comment_shortnames(*args, **kwargs)))
-
     def comment_from_uuid(self, uuid, match_alt_id=True):
-        """
-        Use a comment shortname to look up a comment.
+        """Use a uuid to look up a comment.
+
         >>> a = Comment(bug=None, uuid="a")
         >>> b = a.new_reply()
         >>> b.uuid = "b"
@@ -744,11 +699,20 @@ class Comment(Tree, settings_object.SavedSettingsObject):
                 return comment
         raise KeyError(uuid)
 
+    # methods for id generation
+
+    def sibling_uuids(self):
+        if self.bug != None:
+            return self.bug.uuids()
+        return []
+
+
 def cmp_attr(comment_1, comment_2, attr, invert=False):
     """
     Compare a general attribute between two comments using the conventional
     comparison rule for that attribute type.  If invert == True, sort
     *against* that convention.
+
     >>> attr="author"
     >>> commentA = Comment()
     >>> commentB = Comment()
@@ -768,7 +732,7 @@ def cmp_attr(comment_1, comment_2, attr, invert=False):
     val_2 = getattr(comment_2, attr)
     if val_1 == None: val_1 = None
     if val_2 == None: val_2 = None
-    
+
     if invert == True :
         return -cmp(val_1, val_2)
     else :
@@ -798,7 +762,7 @@ class CommentCompoundComparator (object):
             if val != 0 :
                 return val
         return 0
-        
+
 cmp_full = CommentCompoundComparator()
 
 if libbe.TESTING == True: