Skip to content

Commit

Permalink
First working (w/unit tests) version.
Browse files Browse the repository at this point in the history
Includes also tests for results of piglit run.
  • Loading branch information
Matěj Cepl committed Oct 24, 2011
1 parent 11144b8 commit 2b225c8
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 172 deletions.
15 changes: 13 additions & 2 deletions .ditz/issue-9465e033d968b2c64f02b71bce62d5a0323a60be.yaml
Expand Up @@ -7,8 +7,8 @@ type: :bugfix
component: json_diff
release:
reporter: Matej Cepl <mcepl@redhat.com>
status: :unstarted
disposition:
status: :closed
disposition: :fixed
creation_time: 2011-10-10 21:31:46.437278 Z
references: []

Expand All @@ -18,3 +18,14 @@ log_events:
- Matej Cepl <mcepl@redhat.com>
- created
- ""
- - 2011-10-24 21:25:12.273479 Z
- Matej Cepl <mcepl@redhat.com>
- closed with disposition fixed
- |-
Fixed as part of larger first working version.
Programmatically fill excuded_attributes tuple, or add (even repeatedly)
-x parameter to the command line. So,
json_diff.py -x spam old.json new.json
Ignores all mentions of the horrendous stuff.
2 changes: 1 addition & 1 deletion hinnerup
Submodule hinnerup updated from 99e414 to 5dd7de
169 changes: 0 additions & 169 deletions json_diff.js

This file was deleted.

154 changes: 154 additions & 0 deletions json_diff.py
@@ -0,0 +1,154 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Script for comparing two objects
"""
import json
from optparse import OptionParser
import logging

logging.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=logging.INFO)

class Comparator(object):
"""
Main workhorse, the object itself
"""
def __init__(self, fn1=None, fn2=None, excluded_attrs=()):
if fn1:
self.obj1 = json.load(fn1)
if fn2:
self.obj2 = json.load(fn2)
self.excluded_attributes = excluded_attrs
if (fn1 and fn2):
logging.debug("self.obj1 = %s\nself.obj2 = %s\nself.excluded_attrs = %s", \
(self.obj1, self.obj2, self.excluded_attributes))

@staticmethod
def _get_keys(obj):
"""
Getter for the current object's keys.
"""
out = set()
for key in obj.keys():
out.add(key)
return out

@staticmethod
def _is_scalar(value):
"""
Primitive version, relying on the fact that JSON cannot
contain any more complicated data structures.
"""
return not isinstance(value, (list, tuple, dict))

def _compare_arrays(self, old_arr, new_arr):
inters = min(old_arr, new_arr)

result = {
u"append": {},
u"remove": {},
u"update": {}
}
for idx in range(len(inters)):
# changed objects, new value is new_arr
if (type(old_arr[idx]) != type(new_arr[idx])):
result['update'][idx] = new_arr[idx]
# another simple variant ... scalars
elif (self._is_scalar(old_arr)):
if old_arr[idx] != new_arr[idx]:
result['update'][idx] = new_arr[idx]
# recursive arrays
elif (isinstance(old_arr[idx], list)):
res_arr = self._compare_arrays(old_arr[idx], \
new_arr[idx])
if (len(res_arr) > 0):
result['update'][idx] = res_arr
# and now nested dicts
elif isinstance(old_arr[idx], dict):
res_dict = self.compare_dicts(old_arr[idx], new_arr[idx])
if (len(res_dict) > 0):
result['update'][idx] = res_dict

# Clear out unused inters in result
out_result = {}
for key in result:
if len(result[key]) > 0:
out_result[key] = result[key]

return out_result

def compare_dicts(self, old_obj=None, new_obj=None):
"""
The real workhorse
"""
if not old_obj and hasattr(self, "obj1"):
old_obj = self.obj1
if not new_obj and hasattr(self, "obj2"):
new_obj = self.obj2

old_keys = set()
new_keys = set()
if old_obj and len(old_obj) > 0:
old_keys = self._get_keys(old_obj)
if new_obj and len(new_obj) > 0:
new_keys = self._get_keys(new_obj)

keys = old_keys | new_keys

result = {
u"append": {},
u"remove": {},
u"update": {}
}
for name in keys:
# Explicitly excluded arguments
if (name in self.excluded_attributes):
continue
# old_obj is missing
if name not in old_obj:
result['append'][name] = new_obj[name]
# new_obj is missing
elif name not in new_obj:
result['remove'][name] = old_obj[name]
# changed objects, new value is new_obj
elif (type(old_obj[name]) != type(new_obj[name])):
result['update'][name] = new_obj[name]
# last simple variant ... scalars
elif (self._is_scalar(old_obj[name])):
if old_obj[name] != new_obj[name]:
result['update'][name] = new_obj[name]
# now arrays
elif (isinstance(old_obj[name], list)):
res_arr = self._compare_arrays(old_obj[name], \
new_obj[name])
if (len(res_arr) > 0):
result['update'][name] = res_arr
# and now nested dicts
elif isinstance(old_obj[name], dict):
res_dict = self.compare_dicts(old_obj[name], new_obj[name])
if (len(res_dict) > 0):
result['update'][name] = res_dict

# Clear out unused keys in result
out_result = {}
for key in result:
if len(result[key]) > 0:
out_result[key] = result[key]

return out_result


if __name__ == "__main__":
usage = "usage: %prog [options] old.json new.json"
parser = OptionParser(usage=usage)
parser.add_option("-x", "--exclude",
action="append", dest="exclude", metavar="ATTR", default=[],
help="attributes which should be ignored when comparing")
(options, args) = parser.parse_args()
logging.debug("options = %s", str(options))
logging.debug("args = %s", str(args))
if len(args) != 2:
parser.error("Script requires two positional arguments, names for old and new JSON file.")

diff = Comparator(file(args[0]), file(args[1]), options.exclude)
print json.dumps(diff.compare_dicts(), indent=4, ensure_ascii=False)

0 comments on commit 2b225c8

Please sign in to comment.