src/pyams_zodbbrowser/diff.py
changeset 0 a02202f95e2c
equal deleted inserted replaced
-1:000000000000 0:a02202f95e2c
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 from html import escape
       
    18 
       
    19 # import interfaces
       
    20 from pyams_zodbbrowser.interfaces import IValueRenderer
       
    21 
       
    22 # import packages
       
    23 
       
    24 
       
    25 ADDED = 'added'
       
    26 REMOVED = 'removed'
       
    27 CHANGED = 'changed to'
       
    28 
       
    29 
       
    30 def compareDicts(new, old):
       
    31     """Compare two state dictionaries, return dict."""
       
    32     diffs = {}
       
    33     for key, value in list(new.items()):
       
    34         if key not in old:
       
    35             diffs[key] = (ADDED, value, None)
       
    36         elif old[key] != value:
       
    37             diffs[key] = (CHANGED, value, old[key])
       
    38     for key, value in list(old.items()):
       
    39         if key not in new:
       
    40             diffs[key] = (REMOVED, None, value)
       
    41     return diffs
       
    42 
       
    43 
       
    44 def isascii(s):
       
    45     """See if the string can be safely converted to unicode."""
       
    46     try:
       
    47         s.encode('ascii')
       
    48     except UnicodeError:
       
    49         return False
       
    50     else:
       
    51         return True
       
    52 
       
    53 
       
    54 def compareTuples(new, old):
       
    55     """Compare two tuples.
       
    56 
       
    57     Return (common_prefix, middle_of_old, middle_of_new, common_suffix)
       
    58     """
       
    59     first = 0
       
    60     for oldval, newval in zip(old, new):
       
    61         if oldval != newval:
       
    62             break
       
    63         first += 1
       
    64     last = 0
       
    65     for oldval, newval in zip(reversed(old[first:]), reversed(new[first:])):
       
    66         if oldval != newval:
       
    67             break
       
    68         last += 1
       
    69     return (old[:first],
       
    70             old[first:len(old) - last],
       
    71             new[first:len(new) - last],
       
    72             old[len(old) - last:])
       
    73 
       
    74 
       
    75 def compareTuplesHTML(new, old, tid=None, indent=''):
       
    76     """Compare two tuples, return HTML."""
       
    77     html = [indent + '<div class="diff">\n']
       
    78     prefix, removed, added, suffix = compareTuples(new, old)
       
    79     if len(prefix) > 0:
       
    80         html.append(indent + '  <div class="diffitem %s">\n' % 'same')
       
    81         if len(prefix) == 1:
       
    82             html.append(indent + '    first item kept the same\n')
       
    83         else:
       
    84             html.append(indent + '    first %d items kept the same\n' % len(prefix))
       
    85         html.append(indent + '  </div>\n')
       
    86     for oldval in removed:
       
    87         html.append(indent + '  <div class="diffitem %s">\n' % REMOVED)
       
    88         html.append(indent + '    %s %s\n' % (
       
    89             REMOVED, IValueRenderer(oldval).render(tid)))
       
    90         html.append(indent + '  </div>\n')
       
    91     for newval in added:
       
    92         html.append(indent + '  <div class="diffitem %s">\n' % ADDED)
       
    93         html.append(indent + '    %s %s\n' % (
       
    94             ADDED, IValueRenderer(newval).render(tid)))
       
    95         html.append(indent + '  </div>\n')
       
    96     if len(suffix) > 0:
       
    97         html.append(indent + '  <div class="diffitem %s">\n' % 'same')
       
    98         if len(suffix) == 1:
       
    99             html.append(indent + '    last item kept the same\n')
       
   100         else:
       
   101             html.append(indent + '    last %d items kept the same\n' % len(suffix))
       
   102         html.append(indent + '  </div>\n')
       
   103     html.append(indent + '</div>\n')
       
   104     return ''.join(html)
       
   105 
       
   106 
       
   107 def compareDictsHTML(new, old, tid=None, indent=''):
       
   108     """Compare two state dictionaries, return HTML."""
       
   109     html = [indent + '<div class="diff">\n']
       
   110     diff = compareDicts(new, old)
       
   111     for key, (action, newvalue, oldvalue) in sorted(list(diff.items()),
       
   112                                                     key=lambda k_v: (str(type(k_v[0])), k_v[0])):
       
   113         what = action.split()[0]
       
   114         html.append(indent + '  <div class="diffitem %s">\n' % escape(what))
       
   115         if isinstance(key, str) and isascii(key):
       
   116             html.append(indent + '    <strong>%s</strong>: ' % escape(key))
       
   117         else:
       
   118             html.append(indent + '    <strong>%s</strong>: ' % IValueRenderer(key).render(tid))
       
   119         if (action == CHANGED and isinstance(oldvalue, dict) and isinstance(newvalue, dict)):
       
   120             html.append('dictionary changed:\n')
       
   121             html.append(compareDictsHTML(newvalue, oldvalue, tid, indent=indent + '    '))
       
   122         elif (action == CHANGED and isinstance(oldvalue, tuple) and isinstance(newvalue, tuple)):
       
   123             html.append('tuple changed:\n')
       
   124             html.append(compareTuplesHTML(newvalue, oldvalue, tid, indent=indent + '    '))
       
   125         else:
       
   126             html.append(action)
       
   127             html.append(' ')
       
   128             if action == REMOVED:
       
   129                 value = oldvalue
       
   130             else:
       
   131                 value = newvalue
       
   132             html.append(IValueRenderer(value).render(tid))
       
   133             html.append('\n')
       
   134         html.append(indent + '  </div>\n')
       
   135     html.append(indent + '</div>\n')
       
   136     return ''.join(html)