|
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) |