[diffoscope] 01/01: Add a reader for the json format

Ximin Luo infinity0 at debian.org
Mon May 29 22:05:16 CEST 2017


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch experimental
in repository diffoscope.

commit 4cd861e4fb8cced920c9f9eb1897d48b59819b20
Author: Ximin Luo <infinity0 at debian.org>
Date:   Mon May 29 22:04:46 2017 +0200

    Add a reader for the json format
---
 debian/diffoscope.1.rst                            | 11 +++--
 diffoscope/difference.py                           | 13 +++++-
 diffoscope/main.py                                 | 29 +++++++++----
 .../{comparators/ipk.py => readers/__init__.py}    | 13 +++---
 diffoscope/readers/json.py                         | 45 +++++++++++++++++++
 diffoscope/{__init__.py => readers/utils.py}       |  6 ++-
 tests/comparators/utils/data.py                    | 13 ++++++
 tests/test_readers.py                              | 50 ++++++++++++++++++++++
 8 files changed, 158 insertions(+), 22 deletions(-)

diff --git a/debian/diffoscope.1.rst b/debian/diffoscope.1.rst
index 396975d..23c7e1a 100644
--- a/debian/diffoscope.1.rst
+++ b/debian/diffoscope.1.rst
@@ -14,7 +14,9 @@ in-depth comparison of files, archives, and directories
 SYNOPSIS
 ========
 
-  diffoscope [-h] [--version] [--debug] [--html output] [--text output] [--max-report-size bytes] [--css url] file1 file2
+  diffoscope [-h] [--json output] [OPTIONS] file1 file2
+  diffoscope [-h] [OPTIONS] diff_file
+  diffoscope [-h] [OPTIONS] < diff_file
 
 DESCRIPTION
 ===========
@@ -36,13 +38,16 @@ project and was formerly known as “debbindiff”.
 OPTIONS
 =======
 
--h, --help               show this help message and exit
+-h, --help               show a detailed help message and exit
 --version                show program's version number and exit
 --debug                  display debug messages
---html output            write HTML report to given file
+--json output            write JSON report to given file
                          (use - for standard output)
 --text output            write plain text report to given file
                          (use - for standard output)
+--html output            write HTML report to given file
+                         (use - for standard output)
+--html-dir output        write multi-HTML report to given directory
 --max-report-size BYTES
                          maximum bytes written in report (default: 2048000)
 --max-diff-block-lines MAX_DIFF_BLOCK_LINES
diff --git a/diffoscope/difference.py b/diffoscope/difference.py
index 53e1dda..38853f1 100644
--- a/diffoscope/difference.py
+++ b/diffoscope/difference.py
@@ -52,7 +52,7 @@ class VisualDifference(object):
 
 
 class Difference(object):
-    def __init__(self, unified_diff, path1, path2, source=None, comment=None, has_internal_linenos=False):
+    def __init__(self, unified_diff, path1, path2, source=None, comment=None, has_internal_linenos=False, details=None):
         self._comments = []
         if comment:
             if type(comment) is list:
@@ -78,12 +78,21 @@ class Difference(object):
             raise TypeError("path2/source[1] is not a string")
         # Whether the unified_diff already contains line numbers inside itself
         self._has_internal_linenos = has_internal_linenos
-        self._details = []
+        self._details = details or []
         self._visuals = []
 
     def __repr__(self):
         return '<Difference %s -- %s %s>' % (self._source1, self._source2, self._details)
 
+    def equals(self, other):
+        return self == other or (
+            self.unified_diff == other.unified_diff and
+            self.source1 == other.source1 and
+            self.source2 == other.source2 and
+            self.comments == other.comments and
+            self.has_internal_linenos == other.has_internal_linenos and
+            all(x.equals(y) for x, y in zip(self.details, other.details)))
+
     @staticmethod
     def from_feeder(feeder1, feeder2, path1, path2, source=None, comment=None, **kwargs):
         try:
diff --git a/diffoscope/main.py b/diffoscope/main.py
index f97607d..74e26ba 100644
--- a/diffoscope/main.py
+++ b/diffoscope/main.py
@@ -42,6 +42,7 @@ from .external_tools import EXTERNAL_TOOLS
 from .presenters.html import JQUERY_SYSTEM_LOCATIONS
 from .presenters.formats import PresenterManager
 from .comparators.utils.compare import compare_root_paths
+from .readers import load_diff, load_diff_from_path
 
 logger = logging.getLogger(__name__)
 
@@ -61,8 +62,12 @@ def create_parser():
     parser = argparse.ArgumentParser(
         description='Calculate differences between two files or directories',
         add_help=False)
-    parser.add_argument('path1', help='First file or directory to compare')
-    parser.add_argument('path2', help='Second file or directory to compare')
+    parser.add_argument('path1', nargs='?', help='First file or directory to '
+        'compare. If omitted, tries to read a diffoscope diff from stdin.')
+    parser.add_argument('path2', nargs='?', help='Second file or directory to '
+        'compare. If omitted, no comparison is done but instead we read a '
+        'diffoscope diff from path1 and will output this in the formats '
+        'specified by the rest of the command line.')
     parser.add_argument('--debug', action='store_true',
                         default=False, help='Display debug messages')
     parser.add_argument('--debugger', action='store_true',
@@ -289,16 +294,22 @@ def run_diffoscope(parsed_args):
     Config().compute_visual_diffs = PresenterManager().compute_visual_diffs()
     set_path()
     set_locale()
-    logger.debug('Starting comparison')
-    with Progress():
-        with profile('main', 'outputs'):
-            difference = compare_root_paths(
-                parsed_args.path1, parsed_args.path2)
-    ProgressManager().finish()
+    path1, path2 = parsed_args.path1, parsed_args.path2
+    if path2 is None:
+        if path1 is None or path1 == '-':
+            difference = load_diff(sys.stdin, "stdin")
+        else:
+            difference = load_diff_from_path(path1)
+    else:
+        logger.debug('Starting comparison')
+        with Progress():
+            with profile('main', 'outputs'):
+                difference = compare_root_paths(path1, path2)
+        ProgressManager().finish()
     # Generate an empty, dummy diff to write, saving the exit code first.
     has_differences = bool(difference is not None)
     if difference is None and parsed_args.output_empty:
-        difference = Difference(None, parsed_args.path1, parsed_args.path2)
+        difference = Difference(None, path1, path2)
     with profile('main', 'outputs'):
         PresenterManager().output(difference, parsed_args, has_differences)
     return 1 if has_differences else 0
diff --git a/diffoscope/comparators/ipk.py b/diffoscope/readers/__init__.py
similarity index 74%
copy from diffoscope/comparators/ipk.py
copy to diffoscope/readers/__init__.py
index 3131dcc..391be39 100644
--- a/diffoscope/comparators/ipk.py
+++ b/diffoscope/readers/__init__.py
@@ -2,8 +2,7 @@
 #
 # diffoscope: in-depth comparison of files, archives, and directories
 #
-# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
-#             2015 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2017 Ximin Luo <infinity0 at debian.org>
 #
 # diffoscope is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,10 +17,12 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
-import re
+from .json import JSONReaderV1
 
-from .gzip import GzipFile
 
+def load_diff_from_path(path):
+    with open(path, 'rb') as fp:
+        return load_diff(fp, path)
 
-class IpkFile(GzipFile):
-    RE_FILE_EXTENSION = re.compile('\.ipk$')
+def load_diff(fp, path):
+    return JSONReaderV1().load(fp, "stdin")
diff --git a/diffoscope/readers/json.py b/diffoscope/readers/json.py
new file mode 100644
index 0000000..0891e52
--- /dev/null
+++ b/diffoscope/readers/json.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+#
+# diffoscope: in-depth comparison of files, archives, and directories
+#
+# Copyright © 2017 Ximin Luo <infinity0 at debian.org>
+#
+# diffoscope is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffoscope is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
+
+import codecs
+import json
+from collections import OrderedDict
+
+from ..difference import Difference
+from ..presenters.json import JSON_FORMAT_MAGIC
+from .utils import UnrecognizedFormatError
+
+
+class JSONReaderV1(object):
+
+    def load(self, fp, fn):
+        raw = json.load(codecs.getreader("utf-8")(fp))
+        if JSON_FORMAT_MAGIC not in raw:
+            raise UnrecognizedFormatError("magic not found in json: %s" % JSON_FORMAT_MAGIC)
+        return self.load_rec(raw)
+
+    def load_rec(self, raw):
+        source1 = raw["source1"]
+        source2 = raw["source2"]
+        unified_diff = raw["unified_diff"]
+        has_internal_linenos = raw.get("has_internal_linenos", False)
+        comments = raw.get("comments", [])
+        details = [self.load_rec(child) for child in raw.get("details", [])]
+
+        return Difference(unified_diff, source1, source2, comment=comments, details=details)
diff --git a/diffoscope/__init__.py b/diffoscope/readers/utils.py
similarity index 87%
copy from diffoscope/__init__.py
copy to diffoscope/readers/utils.py
index f510f82..ca54da4 100644
--- a/diffoscope/__init__.py
+++ b/diffoscope/readers/utils.py
@@ -2,7 +2,7 @@
 #
 # diffoscope: in-depth comparison of files, archives, and directories
 #
-# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2017 Ximin Luo <infinity0 at debian.org>
 #
 # diffoscope is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -17,4 +17,6 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
-VERSION = "82"
+
+class UnrecognizedFormatError(Exception):
+    pass
diff --git a/tests/comparators/utils/data.py b/tests/comparators/utils/data.py
index fb6a1f5..d2420d1 100644
--- a/tests/comparators/utils/data.py
+++ b/tests/comparators/utils/data.py
@@ -18,6 +18,7 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+import contextlib
 import os
 import re
 import pytest
@@ -50,6 +51,18 @@ def get_data(filename):
         return f.read()
 
 
+ at contextlib.contextmanager
+def cwd_data():
+    """A context manager which changes the working directory to the given
+    path, and then changes it back to its previous value on exit.
+
+    """
+    prev_cwd = os.getcwd()
+    os.chdir(data(""))
+    yield
+    os.chdir(prev_cwd)
+
+
 def load_fixture(filename):
     return init_fixture(data(filename))
 
diff --git a/tests/test_readers.py b/tests/test_readers.py
new file mode 100644
index 0000000..a765ecd
--- /dev/null
+++ b/tests/test_readers.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+#
+# diffoscope: in-depth comparison of files, archives, and directories
+#
+# Copyright © 2017 Ximin Luo <infinity0 at debian.org>
+#
+# diffoscope is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# diffoscope is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
+
+import os
+import re
+import pytest
+
+from diffoscope.main import main
+from diffoscope.comparators.utils.compare import compare_root_paths
+from diffoscope.readers import load_diff_from_path
+
+from comparators.utils.data import cwd_data, get_data
+
+
+def run_read_write(capsys, diff, *args):
+    with pytest.raises(SystemExit) as exc, cwd_data():
+        main(args + (diff,))
+
+    out, err = capsys.readouterr()
+
+    assert err == ''
+    assert exc.value.code == 1
+    assert out == get_data(diff) # presented-output is same as parsed-input
+    return out
+
+def run_diff_read(diffpath):
+    with cwd_data():
+        diff = compare_root_paths('test1.tar', 'test2.tar')
+        read = load_diff_from_path(diffpath)
+    assert diff.equals(read)
+
+def test_json(capsys):
+    run_read_write(capsys, 'output.json', '--json', '-')
+    run_diff_read('output.json')

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/diffoscope.git


More information about the diffoscope mailing list