]> git.cworth.org Git - apitrace/blob - scripts/jsondiff.py
Script to run glretrace in parallel, comparing generated snapshots.
[apitrace] / scripts / jsondiff.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2011 Jose Fonseca
5 # All Rights Reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24 #
25 ##########################################################################/
26
27
28 import json
29 import sys
30
31
32 def object_hook(obj):
33     if '__class__' in obj:
34         return None
35     for name in obj.keys():
36         if name.startswith('__') and name.endswith('__'):
37             del obj[name]
38     return obj
39
40
41 class Visitor:
42
43     def visit(self, node, *args, **kwargs):
44         if isinstance(node, dict):
45             return self.visit_object(node, *args, **kwargs)
46         elif isinstance(node, list):
47             return self.visit_array(node, *args, **kwargs)
48         else:
49             return self.visit_value(node, *args, **kwargs)
50
51     def visit_object(self, node, *args, **kwargs):
52         pass
53
54     def visit_array(self, node, *args, **kwargs):
55         pass
56
57     def visit_value(self, node, *args, **kwargs):
58         pass
59
60
61 class Dumper(Visitor):
62
63     def __init__(self, stream = sys.stdout):
64         self.stream = stream
65         self.level = 0;
66
67     def _write(self, s):
68         self.stream.write(s)
69
70     def _indent(self):
71         self._write('  '*self.level)
72
73     def _newline(self):
74         self._write('\n')
75
76     def visit_object(self, node):
77         self.enter_object()
78
79         members = node.keys()
80         members.sort()
81         for i in range(len(members)):
82             name = members[i]
83             value = node[name]
84             self.enter_member(name)
85             self.visit(value)
86             if i:
87                 self._write(',')
88             self.leave_member()
89         self.leave_object()
90
91     def enter_object(self):
92         self._write('{')
93         self._newline()
94         self.level += 1
95
96     def enter_member(self, name):
97         self._indent()
98         self._write('%s: ' % name)
99
100     def leave_member(self):
101         self._newline()
102
103     def leave_object(self):
104         self.level -= 1
105         self._indent()
106         self._write('}')
107
108     def visit_array(self, node):
109         self.enter_array()
110         for i in range(len(node)):
111             value = node[i]
112             self._indent()
113             self.visit(value)
114             if i:
115                 self._write(',')
116             self._newline()
117         self.leave_array()
118
119     def enter_array(self):
120         self._write('[')
121         self._newline()
122         self.level += 1
123
124     def leave_array(self):
125         self.level -= 1
126         self._indent()
127         self._write(']')
128
129     def visit_value(self, node):
130         self._write(repr(node))
131
132
133
134 class Comparer(Visitor):
135
136     def visit_object(self, a, b):
137         if not isinstance(b, dict):
138             return False
139         if len(a) != len(b):
140             return False
141         ak = a.keys()
142         bk = b.keys()
143         ak.sort()
144         bk.sort()
145         if ak != bk:
146             return False
147         for k in ak:
148             if not self.visit(a[k], b[k]):
149                 return False
150         return True
151
152     def visit_array(self, a, b):
153         if not isinstance(b, list):
154             return False
155         if len(a) != len(b):
156             return False
157         for ae, be in zip(a, b):
158             if not self.visit(ae, be):
159                 return False
160         return True
161
162     def visit_value(self, a, b):
163         return a == b
164
165 comparer = Comparer()
166
167
168 class Differ(Visitor):
169
170     def __init__(self, stream = sys.stdout):
171         self.dumper = Dumper(stream)
172
173     def visit(self, a, b):
174         if comparer.visit(a, b):
175             return
176         Visitor.visit(self, a, b)
177
178     def visit_object(self, a, b):
179         if not isinstance(b, dict):
180             self.replace(a, b)
181         else:
182             self.dumper.enter_object()
183             names = set(a.keys())
184             names.update(b.keys())
185             names = list(names)
186             names.sort()
187
188             for name in names:
189                 ae = a.get(name, None)
190                 be = b.get(name, None)
191                 if not comparer.visit(ae, be):
192                     self.dumper.enter_member(name)
193                     self.visit(ae, be)
194                     self.dumper.leave_member()
195
196             self.dumper.leave_object()
197
198     def visit_array(self, a, b):
199         if not isinstance(b, list):
200             self.replace(a, b)
201         else:
202             self.dumper.enter_array()
203             for i in range(max(len(a), len(b))):
204                 try:
205                     ae = a[i]
206                 except IndexError:
207                     ae = None
208                 try:
209                     be = b[i]
210                 except IndexError:
211                     be = None
212                 self.dumper._indent()
213                 if comparer.visit(ae, be):
214                     self.dumper.visit(ae)
215                 else:
216                     self.visit(ae, be)
217                 self.dumper._newline()
218
219             self.dumper.leave_array()
220
221     def visit_value(self, a, b):
222         if a != b:
223             self.replace(a, b)
224
225     def replace(self, a, b):
226         self.dumper.visit(a)
227         self.dumper._write(' -> ')
228         self.dumper.visit(b)
229
230
231 def load(stream):
232     return json.load(stream, strict=False, object_hook = object_hook)
233
234
235 def main():
236     a = load(open(sys.argv[1], 'rt'))
237     b = load(open(sys.argv[2], 'rt'))
238
239     #dumper = Dumper()
240     #dumper.visit(a)
241
242     differ = Differ()
243     differ.visit(a, b)
244
245
246 if __name__ == '__main__':
247     main()