]> git.cworth.org Git - apitrace/blob - scripts/jsondiff.py
Add option for jsondiff to ignore new state.
[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 strip_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 __init__(self, ignore_added = False):
137         self.ignore_added = ignore_added
138
139     def visit_object(self, a, b):
140         if not isinstance(b, dict):
141             return False
142         if len(a) != len(b) and not self.ignore_added:
143             return False
144         ak = a.keys()
145         bk = b.keys()
146         ak.sort()
147         bk.sort()
148         if ak != bk and not self.ignore_added:
149             return False
150         for k in ak:
151             ae = a[k]
152             try:
153                 be = b[k]
154             except KeyError:
155                 return False
156             if not self.visit(ae, be):
157                 return False
158         return True
159
160     def visit_array(self, a, b):
161         if not isinstance(b, list):
162             return False
163         if len(a) != len(b):
164             return False
165         for ae, be in zip(a, b):
166             if not self.visit(ae, be):
167                 return False
168         return True
169
170     def visit_value(self, a, b):
171         return a == b
172
173
174
175 class Differ(Visitor):
176
177     def __init__(self, stream = sys.stdout, ignore_added = False):
178         self.dumper = Dumper(stream)
179         self.comparer = Comparer(ignore_added = ignore_added)
180
181     def visit(self, a, b):
182         if self.comparer.visit(a, b):
183             return
184         Visitor.visit(self, a, b)
185
186     def visit_object(self, a, b):
187         if not isinstance(b, dict):
188             self.replace(a, b)
189         else:
190             self.dumper.enter_object()
191             names = set(a.keys())
192             names.update(b.keys())
193             names = list(names)
194             names.sort()
195
196             for name in names:
197                 try:
198                     ae = a[name]
199                 except KeyError:
200                     if self.comparer.ignore_added:
201                         continue
202                     else:
203                         ae = None
204                 be = b.get(name, None)
205                 if not self.comparer.visit(ae, be):
206                     self.dumper.enter_member(name)
207                     self.visit(ae, be)
208                     self.dumper.leave_member()
209
210             self.dumper.leave_object()
211
212     def visit_array(self, a, b):
213         if not isinstance(b, list):
214             self.replace(a, b)
215         else:
216             self.dumper.enter_array()
217             for i in range(max(len(a), len(b))):
218                 try:
219                     ae = a[i]
220                 except IndexError:
221                     ae = None
222                 try:
223                     be = b[i]
224                 except IndexError:
225                     be = None
226                 self.dumper._indent()
227                 if self.comparer.visit(ae, be):
228                     self.dumper.visit(ae)
229                 else:
230                     self.visit(ae, be)
231                 self.dumper._newline()
232
233             self.dumper.leave_array()
234
235     def visit_value(self, a, b):
236         if a != b:
237             self.replace(a, b)
238
239     def replace(self, a, b):
240         self.dumper.visit(a)
241         self.dumper._write(' -> ')
242         self.dumper.visit(b)
243
244
245 def load(stream, strip = True):
246     if strip:
247         object_hook = strip_object_hook
248     else:
249         object_hook = None
250     return json.load(stream, strict=False, object_hook = object_hook)
251
252
253 def main():
254     a = load(open(sys.argv[1], 'rt'))
255     b = load(open(sys.argv[2], 'rt'))
256
257     #dumper = Dumper()
258     #dumper.visit(a)
259
260     differ = Differ()
261     differ.visit(a, b)
262
263
264 if __name__ == '__main__':
265     main()