X-Git-Url: https://git.notmuchmail.org/git?a=blobdiff_plain;f=scripts%2Fjsondiff.py;h=470aeda059b8f2723ddf6796394507f8e347ee2d;hb=1c3144b7f0d37ae3909bd1a854ede6a0aa3ea20d;hp=063cd6160cabd4e36788497ea0d5bd23069a70de;hpb=3bb5dd41c854fdbe0dff49d7e6f732bb71fc2d4b;p=apitrace diff --git a/scripts/jsondiff.py b/scripts/jsondiff.py index 063cd61..470aeda 100755 --- a/scripts/jsondiff.py +++ b/scripts/jsondiff.py @@ -26,6 +26,8 @@ import json +import optparse +import re import sys @@ -42,19 +44,19 @@ class Visitor: def visit(self, node, *args, **kwargs): if isinstance(node, dict): - return self.visit_object(node, *args, **kwargs) + return self.visitObject(node, *args, **kwargs) elif isinstance(node, list): - return self.visit_array(node, *args, **kwargs) + return self.visitArray(node, *args, **kwargs) else: - return self.visit_value(node, *args, **kwargs) + return self.visitValue(node, *args, **kwargs) - def visit_object(self, node, *args, **kwargs): + def visitObject(self, node, *args, **kwargs): pass - def visit_array(self, node, *args, **kwargs): + def visitArray(self, node, *args, **kwargs): pass - def visit_value(self, node, *args, **kwargs): + def visitValue(self, node, *args, **kwargs): pass @@ -73,7 +75,7 @@ class Dumper(Visitor): def _newline(self): self._write('\n') - def visit_object(self, node): + def visitObject(self, node): self.enter_object() members = node.keys() @@ -107,7 +109,7 @@ class Dumper(Visitor): if self.level <= 0: self._newline() - def visit_array(self, node): + def visitArray(self, node): self.enter_array() for i in range(len(node)): value = node[i] @@ -128,17 +130,18 @@ class Dumper(Visitor): self._indent() self._write(']') - def visit_value(self, node): + def visitValue(self, node): self._write(json.dumps(node)) class Comparer(Visitor): - def __init__(self, ignore_added = False): + def __init__(self, ignore_added = False, tolerance = 2.0 ** -24): self.ignore_added = ignore_added + self.tolerance = tolerance - def visit_object(self, a, b): + def visitObject(self, a, b): if not isinstance(b, dict): return False if len(a) != len(b) and not self.ignore_added: @@ -159,7 +162,7 @@ class Comparer(Visitor): return False return True - def visit_array(self, a, b): + def visitArray(self, a, b): if not isinstance(b, list): return False if len(a) != len(b): @@ -169,9 +172,14 @@ class Comparer(Visitor): return False return True - def visit_value(self, a, b): - return a == b - + def visitValue(self, a, b): + if isinstance(a, float) or isinstance(b, float): + if a == 0: + return abs(b) < self.tolerance + else: + return abs((b - a)/a) < self.tolerance + else: + return a == b class Differ(Visitor): @@ -185,7 +193,7 @@ class Differ(Visitor): return Visitor.visit(self, a, b) - def visit_object(self, a, b): + def visitObject(self, a, b): if not isinstance(b, dict): self.replace(a, b) else: @@ -207,7 +215,7 @@ class Differ(Visitor): self.dumper.leave_object() - def visit_array(self, a, b): + def visitArray(self, a, b): if not isinstance(b, list): self.replace(a, b) else: @@ -233,7 +241,7 @@ class Differ(Visitor): self.dumper.leave_array() - def visit_value(self, a, b): + def visitValue(self, a, b): if a != b: self.replace(a, b) @@ -243,17 +251,66 @@ class Differ(Visitor): self.dumper.visit(b) -def load(stream, strip = True): - if strip: +# +# Unfortunately JSON standard does not include comments, but this is a quite +# useful feature to have on regressions tests +# + +_token_res = [ + r'//[^\r\n]*', # comment + r'"[^"\\]*(\\.[^"\\]*)*"', # string +] + +_tokens_re = re.compile(r'|'.join(['(' + token_re + ')' for token_re in _token_res]), re.DOTALL) + + +def _strip_comment(mo): + if mo.group(1): + return '' + else: + return mo.group(0) + + +def _strip_comments(data): + '''Strip (non-standard) JSON comments.''' + return _tokens_re.sub(_strip_comment, data) + + +assert _strip_comments('''// a comment +"// a comment in a string +"''') == ''' +"// a comment in a string +"''' + + +def load(stream, strip_images = True, strip_comments = True): + if strip_images: object_hook = strip_object_hook else: object_hook = None - return json.load(stream, strict=False, object_hook = object_hook) + if strip_comments: + data = stream.read() + data = _strip_comments(data) + return json.loads(data, strict=False, object_hook = object_hook) + else: + return json.load(stream, strict=False, object_hook = object_hook) def main(): - a = load(open(sys.argv[1], 'rt')) - b = load(open(sys.argv[2], 'rt')) + optparser = optparse.OptionParser( + usage="\n\t%prog [options] ") + optparser.add_option( + '--keep-images', + action="store_false", dest="strip_images", default=True, + help="compare images") + + (options, args) = optparser.parse_args(sys.argv[1:]) + + if len(args) != 2: + optparser.error('incorrect number of arguments') + + a = load(open(sys.argv[1], 'rt'), options.strip_images) + b = load(open(sys.argv[2], 'rt'), options.strip_images) if False: dumper = Dumper()