--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_zodbbrowser/diff.py Wed Mar 11 12:27:00 2015 +0100
@@ -0,0 +1,136 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+from html import escape
+
+# import interfaces
+from pyams_zodbbrowser.interfaces import IValueRenderer
+
+# import packages
+
+
+ADDED = 'added'
+REMOVED = 'removed'
+CHANGED = 'changed to'
+
+
+def compareDicts(new, old):
+ """Compare two state dictionaries, return dict."""
+ diffs = {}
+ for key, value in list(new.items()):
+ if key not in old:
+ diffs[key] = (ADDED, value, None)
+ elif old[key] != value:
+ diffs[key] = (CHANGED, value, old[key])
+ for key, value in list(old.items()):
+ if key not in new:
+ diffs[key] = (REMOVED, None, value)
+ return diffs
+
+
+def isascii(s):
+ """See if the string can be safely converted to unicode."""
+ try:
+ s.encode('ascii')
+ except UnicodeError:
+ return False
+ else:
+ return True
+
+
+def compareTuples(new, old):
+ """Compare two tuples.
+
+ Return (common_prefix, middle_of_old, middle_of_new, common_suffix)
+ """
+ first = 0
+ for oldval, newval in zip(old, new):
+ if oldval != newval:
+ break
+ first += 1
+ last = 0
+ for oldval, newval in zip(reversed(old[first:]), reversed(new[first:])):
+ if oldval != newval:
+ break
+ last += 1
+ return (old[:first],
+ old[first:len(old) - last],
+ new[first:len(new) - last],
+ old[len(old) - last:])
+
+
+def compareTuplesHTML(new, old, tid=None, indent=''):
+ """Compare two tuples, return HTML."""
+ html = [indent + '<div class="diff">\n']
+ prefix, removed, added, suffix = compareTuples(new, old)
+ if len(prefix) > 0:
+ html.append(indent + ' <div class="diffitem %s">\n' % 'same')
+ if len(prefix) == 1:
+ html.append(indent + ' first item kept the same\n')
+ else:
+ html.append(indent + ' first %d items kept the same\n' % len(prefix))
+ html.append(indent + ' </div>\n')
+ for oldval in removed:
+ html.append(indent + ' <div class="diffitem %s">\n' % REMOVED)
+ html.append(indent + ' %s %s\n' % (
+ REMOVED, IValueRenderer(oldval).render(tid)))
+ html.append(indent + ' </div>\n')
+ for newval in added:
+ html.append(indent + ' <div class="diffitem %s">\n' % ADDED)
+ html.append(indent + ' %s %s\n' % (
+ ADDED, IValueRenderer(newval).render(tid)))
+ html.append(indent + ' </div>\n')
+ if len(suffix) > 0:
+ html.append(indent + ' <div class="diffitem %s">\n' % 'same')
+ if len(suffix) == 1:
+ html.append(indent + ' last item kept the same\n')
+ else:
+ html.append(indent + ' last %d items kept the same\n' % len(suffix))
+ html.append(indent + ' </div>\n')
+ html.append(indent + '</div>\n')
+ return ''.join(html)
+
+
+def compareDictsHTML(new, old, tid=None, indent=''):
+ """Compare two state dictionaries, return HTML."""
+ html = [indent + '<div class="diff">\n']
+ diff = compareDicts(new, old)
+ for key, (action, newvalue, oldvalue) in sorted(list(diff.items()),
+ key=lambda k_v: (str(type(k_v[0])), k_v[0])):
+ what = action.split()[0]
+ html.append(indent + ' <div class="diffitem %s">\n' % escape(what))
+ if isinstance(key, str) and isascii(key):
+ html.append(indent + ' <strong>%s</strong>: ' % escape(key))
+ else:
+ html.append(indent + ' <strong>%s</strong>: ' % IValueRenderer(key).render(tid))
+ if (action == CHANGED and isinstance(oldvalue, dict) and isinstance(newvalue, dict)):
+ html.append('dictionary changed:\n')
+ html.append(compareDictsHTML(newvalue, oldvalue, tid, indent=indent + ' '))
+ elif (action == CHANGED and isinstance(oldvalue, tuple) and isinstance(newvalue, tuple)):
+ html.append('tuple changed:\n')
+ html.append(compareTuplesHTML(newvalue, oldvalue, tid, indent=indent + ' '))
+ else:
+ html.append(action)
+ html.append(' ')
+ if action == REMOVED:
+ value = oldvalue
+ else:
+ value = newvalue
+ html.append(IValueRenderer(value).render(tid))
+ html.append('\n')
+ html.append(indent + ' </div>\n')
+ html.append(indent + '</div>\n')
+ return ''.join(html)