[diffoscope] 01/01: WIP py2/pypy support

Ximin Luo infinity0 at debian.org
Thu Jun 1 19:01:16 CEST 2017


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

infinity0 pushed a commit to branch WIP/py2-pypy
in repository diffoscope.

commit 5bad26d0095c5324d8656a4c4aa40b03687307bc
Author: Ximin Luo <infinity0 at debian.org>
Date:   Thu Jun 1 18:59:36 2017 +0200

    WIP py2/pypy support
    
    $ PYTHONPATH=$PWD:/usr/lib/python2.7/dist-packages pypy -m diffoscope.main --debug tests/data/test?.iso
    $ PYTHONPATH=$PWD:/usr/lib/python2.7/dist-packages pypy -m diffoscope.main --debug tests/data/test?.deb
    $ PYTHONPATH=$PWD:/usr/lib/python2.7/dist-packages pypy -m diffoscope.main --debug tests/data/test?.tar
    $ PYTHONPATH=$PWD:/usr/lib/python2.7/dist-packages pypy -m diffoscope.main --debug tests/data/test?.xz
    $ PYTHONPATH=$PWD:/usr/lib/python2.7/dist-packages pypy -m diffoscope.main --debug tests/data/test?.png
---
 diffoscope/comparators/__init__.py         |  2 +
 diffoscope/comparators/apk.py              |  2 +-
 diffoscope/comparators/ar.py               |  2 +-
 diffoscope/comparators/binary.py           |  2 +-
 diffoscope/comparators/cbfs.py             |  2 +-
 diffoscope/comparators/deb.py              |  9 +--
 diffoscope/comparators/debian.py           |  6 +-
 diffoscope/comparators/device.py           |  2 +-
 diffoscope/comparators/elf.py              | 18 +++---
 diffoscope/comparators/gettext.py          |  4 +-
 diffoscope/comparators/iso9660.py          |  2 +-
 diffoscope/comparators/java.py             |  2 +-
 diffoscope/comparators/ps.py               |  2 +-
 diffoscope/comparators/symlink.py          |  2 +-
 diffoscope/comparators/utils/archive.py    |  8 +--
 diffoscope/comparators/utils/command.py    | 25 +++++++--
 diffoscope/comparators/utils/container.py  |  2 +-
 diffoscope/comparators/utils/file.py       | 11 +++-
 diffoscope/comparators/utils/libarchive.py |  8 ++-
 diffoscope/diff.py                         | 13 +++--
 diffoscope/difference.py                   |  6 +-
 diffoscope/logging.py                      |  5 ++
 diffoscope/main.py                         | 11 +++-
 diffoscope/presenters/html/html.py         |  4 +-
 diffoscope/presenters/json.py              |  4 +-
 diffoscope/presenters/text.py              |  8 ++-
 diffoscope/presenters/utils.py             |  2 +
 diffoscope/profiling.py                    | 10 ++--
 diffoscope/progress.py                     |  6 +-
 diffoscope/tempfiles.py                    | 89 +++++++++++++++++++++++++++++-
 diffoscope/tools.py                        |  2 +-
 31 files changed, 208 insertions(+), 63 deletions(-)

diff --git a/diffoscope/comparators/__init__.py b/diffoscope/comparators/__init__.py
index 38d9e58..5947f12 100644
--- a/diffoscope/comparators/__init__.py
+++ b/diffoscope/comparators/__init__.py
@@ -108,6 +108,8 @@ class ComparatorManager(object):
                         'diffoscope.comparators.{}'.format(package)
                     )
                 except ImportError:
+                    import traceback
+                    traceback.print_exc()
                     continue
 
                 self.classes.append(getattr(mod, klass_name))
diff --git a/diffoscope/comparators/apk.py b/diffoscope/comparators/apk.py
index 1d4aa0d..98580c7 100644
--- a/diffoscope/comparators/apk.py
+++ b/diffoscope/comparators/apk.py
@@ -142,7 +142,7 @@ class ApkContainer(Archive):
             differences.append(self.compare_manifests(other))
         except AttributeError:  # no apk-specific methods, e.g. MissingArchive
             pass
-        differences.extend(super().compare(other, *args, **kwargs))
+        differences.extend(super(ApkContainer, self).compare(other, *args, **kwargs))
         return differences
 
 class ApkFile(File):
diff --git a/diffoscope/comparators/ar.py b/diffoscope/comparators/ar.py
index 0de0bff..e54eb52 100644
--- a/diffoscope/comparators/ar.py
+++ b/diffoscope/comparators/ar.py
@@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
 
 class ArContainer(LibarchiveContainer):
     def get_adjusted_members(self):
-        members = list(super().get_adjusted_members())
+        members = list(super(ArContainer, self).get_adjusted_members())
         known_ignores = {
             "/" : "this is the symbol table, already accounted for in other output",
             "//" : "this is the table for GNU long names, already accounted for in the archive filelist",
diff --git a/diffoscope/comparators/binary.py b/diffoscope/comparators/binary.py
index 47352d0..30e50d2 100644
--- a/diffoscope/comparators/binary.py
+++ b/diffoscope/comparators/binary.py
@@ -25,7 +25,7 @@ from .utils.file import File
 
 class FilesystemFile(File):
     def __init__(self, path, container=None):
-        super().__init__(container=container)
+        super(FilesystemFile, self).__init__(container=container)
         self._name = path
 
     @property
diff --git a/diffoscope/comparators/cbfs.py b/diffoscope/comparators/cbfs.py
index 0002bbb..6a702ed 100644
--- a/diffoscope/comparators/cbfs.py
+++ b/diffoscope/comparators/cbfs.py
@@ -36,7 +36,7 @@ logger = logging.getLogger(__name__)
 
 class CbfsListing(Command):
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(CbfsListing, self).__init__(*args, **kwargs)
         self._header_re = re.compile(r'^.*: ([^,]+, bootblocksize [0-9]+, romsize [0-9]+, offset 0x[0-9A-Fa-f]+)$')
 
     @tool_required('cbfstool')
diff --git a/diffoscope/comparators/deb.py b/diffoscope/comparators/deb.py
index 66a6d05..c28f8c6 100644
--- a/diffoscope/comparators/deb.py
+++ b/diffoscope/comparators/deb.py
@@ -17,6 +17,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 codecs
 import re
 import logging
 
@@ -101,7 +102,7 @@ class DebFile(File):
         if not hasattr(self, '_control'):
             control_file = self.as_container.control_tar.as_container.lookup_file('./control')
             if control_file:
-                with open(control_file.path, 'rb') as f:
+                with codecs.open(control_file.path, 'rb') as f:
                     self._control = deb822.Deb822(f)
         return self._control
 
@@ -124,7 +125,7 @@ class Md5sumsFile(File):
     def parse(self):
         try:
             md5sums = {}
-            with open(self.path, 'r', encoding='utf-8') as f:
+            with codecs.open(self.path, 'r', encoding='utf-8') as f:
                 for line in f:
                     md5sum, path = re.split(r'\s+', line.strip(), maxsplit=1)
                     md5sums['./%s' % path] = md5sum
@@ -134,7 +135,7 @@ class Md5sumsFile(File):
             return {}
 
     def strip_checksum(self, path):
-        with open(path, 'r', encoding='utf-8') as f:
+        with codecs.open(path, 'r', encoding='utf-8') as f:
             for line in f:
                 yield " ".join(line.split(" ")[2:])
 
@@ -155,7 +156,7 @@ class DebTarContainer(TarContainer):
             other_md5sums = other.source.container.source.container.source.md5sums
         else:
             other_md5sums = {}
-        for my_member, other_member, comment in super().comparisons(other):
+        for my_member, other_member, comment in super(DebTarContainer, self).comparisons(other):
             if my_member.name == other_member.name and \
                my_md5sums.get(my_member.name, 'my') == other_md5sums.get(other_member.name, 'other'):
                 logger.debug('Skip %s: identical md5sum', my_member.name)
diff --git a/diffoscope/comparators/debian.py b/diffoscope/comparators/debian.py
index a5e7900..78baa83 100644
--- a/diffoscope/comparators/debian.py
+++ b/diffoscope/comparators/debian.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import
+
 import re
 import os.path
 import hashlib
@@ -67,7 +69,7 @@ class DebControlMember(File):
 
 class DebControlContainer(Container):
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(DebControlContainer, self).__init__(*args, **kwargs)
         self._version_re = DebControlContainer.get_version_trimming_re(self)
 
     @staticmethod
@@ -179,7 +181,7 @@ class DotChangesFile(DebControlFile):
         return True
 
     def compare(self, other, *args, **kwargs):
-        differences = super().compare(other, *args, **kwargs)
+        differences = super(DotChangesFile, self).compare(other, *args, **kwargs)
 
         if differences is None:
             return None
diff --git a/diffoscope/comparators/device.py b/diffoscope/comparators/device.py
index 9743bda..8434669 100644
--- a/diffoscope/comparators/device.py
+++ b/diffoscope/comparators/device.py
@@ -65,7 +65,7 @@ class Device(File):
         if hasattr(self, '_placeholder'):
             os.remove(self._placeholder)
             del self._placeholder
-        super().cleanup()
+        super(Device, self).cleanup()
 
     def compare(self, other, source=None):
         with open(self.path) as my_content, \
diff --git a/diffoscope/comparators/elf.py b/diffoscope/comparators/elf.py
index 17b0f26..a74e14c 100644
--- a/diffoscope/comparators/elf.py
+++ b/diffoscope/comparators/elf.py
@@ -56,7 +56,7 @@ logger = logging.getLogger(__name__)
 
 class Readelf(Command):
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(Readelf, self).__init__(*args, **kwargs)
         # we don't care about the name of the archive
         self._archive_re = re.compile(r'^File: %s\(' % re.escape(self.path))
 
@@ -151,7 +151,7 @@ class ReadelfDebugDump(Readelf):
 
     def __init__(self, debug_section_group, *args, **kwargs):
         self._debug_section_group = debug_section_group
-        super().__init__(*args, **kwargs)
+        super(ReadelfDebugDump, self).__init__(*args, **kwargs)
 
     def readelf_options(self):
         return ['--debug-dump=%s' % self._debug_section_group]
@@ -182,7 +182,7 @@ class ReadElfSection(Readelf):
     def __init__(self, path, section_name, *args, **kwargs):
         self._path = path
         self._section_name = section_name
-        super().__init__(path, *args, **kwargs)
+        super(ReadElfSection, self).__init__(path, *args, **kwargs)
 
     @property
     def section_name(self):
@@ -200,7 +200,7 @@ class ObjdumpSection(Command):
         self._path = path
         self._path_bin = path.encode('utf-8')
         self._section_name = section_name
-        super().__init__(path, *args, **kwargs)
+        super(ObjdumpSection, self).__init__(path, *args, **kwargs)
 
     def objdump_options(self):
         return []
@@ -224,7 +224,7 @@ class ObjdumpSection(Command):
         return line
 
 class ObjdumpDisassembleSection(ObjdumpSection):
-    RE_SYMBOL_COMMENT = re.compile(rb'^( +[0-9a-f]+:[^#]+)# [0-9a-f]+ <[^>]+>$')
+    RE_SYMBOL_COMMENT = re.compile(r'^( +[0-9a-f]+:[^#]+)# [0-9a-f]+ <[^>]+>$')
 
     def objdump_options(self):
         # With '--line-numbers' we get the source filename and line within the
@@ -234,7 +234,7 @@ class ObjdumpDisassembleSection(ObjdumpSection):
         return ['--line-numbers', '--disassemble', '--demangle']
 
     def filter(self, line):
-        line = super().filter(line)
+        line = super(ObjdumpDisassembleSection, self).filter(line)
         return ObjdumpDisassembleSection.RE_SYMBOL_COMMENT.sub(r'\1', line)
 
 
@@ -270,7 +270,7 @@ def _should_skip_section(name, type):
 
 class ElfSection(File):
     def __init__(self, elf_container, member_name):
-        super().__init__(container=elf_container)
+        super(ElfSection, self).__init__(container=elf_container)
         self._name = member_name
 
     @property
@@ -281,7 +281,7 @@ class ElfSection(File):
     def progress_name(self):
         return "{} [{}]".format(
             self.container.source.progress_name,
-            super().progress_name,
+            super(ElfSection, self).progress_name,
         )
 
     @property
@@ -385,7 +385,7 @@ class ElfContainer(Container):
 
     @tool_required('readelf')
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(ElfContainer, self).__init__(*args, **kwargs)
         logger.debug("Creating ElfContainer for %s", self.source.path)
 
         cmd = ['readelf', '--wide', '--section-headers', self.source.path]
diff --git a/diffoscope/comparators/gettext.py b/diffoscope/comparators/gettext.py
index 5801e91..3bb7610 100644
--- a/diffoscope/comparators/gettext.py
+++ b/diffoscope/comparators/gettext.py
@@ -31,10 +31,10 @@ logger = logging.getLogger(__name__)
 
 
 class Msgunfmt(Command):
-    CHARSET_RE = re.compile(rb'^"Content-Type: [^;]+; charset=([^\\]+)\\n"$')
+    CHARSET_RE = re.compile(r'^"Content-Type: [^;]+; charset=([^\\]+)\\n"$')
 
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(Msgunfmt, self).__init__(*args, **kwargs)
         self._header = io.BytesIO()
         self._encoding = None
 
diff --git a/diffoscope/comparators/iso9660.py b/diffoscope/comparators/iso9660.py
index b354c74..8c38db1 100644
--- a/diffoscope/comparators/iso9660.py
+++ b/diffoscope/comparators/iso9660.py
@@ -48,7 +48,7 @@ class ISO9660PVD(Command):
 class ISO9660Listing(Command):
     def __init__(self, path, extension=None, *args, **kwargs):
         self._extension = extension
-        super().__init__(path, *args, **kwargs)
+        super(ISO9660Listing, self).__init__(path, *args, **kwargs)
 
     @tool_required('isoinfo')
     def cmdline(self):
diff --git a/diffoscope/comparators/java.py b/diffoscope/comparators/java.py
index 4b3e724..7291de5 100644
--- a/diffoscope/comparators/java.py
+++ b/diffoscope/comparators/java.py
@@ -30,7 +30,7 @@ from .utils.command import Command
 
 class Javap(Command):
     def __init__(self, path, *args, **kwargs):
-        super().__init__(path, *args, **kwargs)
+        super(Javap, self).__init__(path, *args, **kwargs)
         self.real_path = os.path.realpath(path)
 
     @tool_required('javap')
diff --git a/diffoscope/comparators/ps.py b/diffoscope/comparators/ps.py
index 650da45..4bd2d0f 100644
--- a/diffoscope/comparators/ps.py
+++ b/diffoscope/comparators/ps.py
@@ -40,7 +40,7 @@ class PsFile(TextFile):
     RE_FILE_TYPE = re.compile(r'^PostScript document\b')
 
     def compare(self, other, *args, **kwargs):
-        differences = super().compare(other, *args, **kwargs)
+        differences = super(PsFile, self).compare(other, *args, **kwargs)
         details = None
         try:
             details = Difference.from_command(Pstotext, self.path, other.path)
diff --git a/diffoscope/comparators/symlink.py b/diffoscope/comparators/symlink.py
index 400297e..a4b6bd5 100644
--- a/diffoscope/comparators/symlink.py
+++ b/diffoscope/comparators/symlink.py
@@ -53,7 +53,7 @@ class Symlink(File):
         if hasattr(self, '_placeholder'):
             os.remove(self._placeholder)
             del self._placeholder
-        super().cleanup()
+        super(Symlink, self).cleanup()
 
     def compare(self, other, source=None):
         with open(self.path) as my_content, \
diff --git a/diffoscope/comparators/utils/archive.py b/diffoscope/comparators/utils/archive.py
index 510dff0..7616f48 100644
--- a/diffoscope/comparators/utils/archive.py
+++ b/diffoscope/comparators/utils/archive.py
@@ -32,7 +32,7 @@ from .container import Container
 logger = logging.getLogger(__name__)
 
 
-class Archive(Container, metaclass=abc.ABCMeta):
+class Archive(Container):
     def __new__(cls, source, *args, **kwargs):
         if isinstance(source, MissingFile):
             return super(Container, MissingArchive).__new__(MissingArchive)
@@ -40,7 +40,7 @@ class Archive(Container, metaclass=abc.ABCMeta):
             return super(Container, cls).__new__(cls)
 
     def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
+        super(Archive, self).__init__(*args, **kwargs)
         with profile('open_archive', self):
             self._archive = self.open_archive()
 
@@ -82,7 +82,7 @@ class Archive(Container, metaclass=abc.ABCMeta):
 
 class ArchiveMember(File):
     def __init__(self, container, member_name):
-        super().__init__(container=container)
+        super(ArchiveMember, self).__init__(container=container)
         self._name = member_name
         self._temp_dir = None
         self._path = None
@@ -103,7 +103,7 @@ class ArchiveMember(File):
         if self._temp_dir is not None:
             self._temp_dir.cleanup()
             self._temp_dir = None
-        super().cleanup()
+        super(ArchiveMember, self).cleanup()
 
     def is_directory(self):
         return False
diff --git a/diffoscope/comparators/utils/command.py b/diffoscope/comparators/utils/command.py
index 5014140..1767006 100644
--- a/diffoscope/comparators/utils/command.py
+++ b/diffoscope/comparators/utils/command.py
@@ -20,19 +20,34 @@
 import io
 import abc
 import logging
-import shlex
 import subprocess
 import threading
 
+try:
+    from shlex import quote as shlex_quote
+except ImportError:
+    import re
+    _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
+    def shlex_quote(s):
+        """Return a shell-escaped version of the string *s*."""
+        if not s:
+            return "''"
+        if _find_unsafe(s) is None:
+            return s
+
+        # use single quotes, and put single quotes into double quotes
+        # the string $'b is then quoted as '$'"'"'b'
+        return "'" + s.replace("'", "'\"'\"'") + "'"
+
 logger = logging.getLogger(__name__)
 
 
-class Command(object, metaclass=abc.ABCMeta):
+class Command(object):
     def __init__(self, path):
         self._path = path
 
     def start(self):
-        logger.debug("Executing %s", ' '.join([shlex.quote(x) for x in self.cmdline()]))
+        logger.debug("Executing %s", ' '.join([shlex_quote(x) for x in self.cmdline()]))
         self._process = subprocess.Popen(self.cmdline(),
                                          shell=False, close_fds=True,
                                          env=self.env(),
@@ -61,7 +76,7 @@ class Command(object, metaclass=abc.ABCMeta):
         raise NotImplementedError()
 
     def shell_cmdline(self):
-        return ' '.join(map(lambda x: '{}' if x == self.path else shlex.quote(x), self.cmdline()))
+        return ' '.join(map(lambda x: '{}' if x == self.path else shlex_quote(x), self.cmdline()))
 
     def env(self):
         return None # inherit parent environment by default
@@ -92,7 +107,7 @@ class Command(object, metaclass=abc.ABCMeta):
         returncode = self._process.wait()
         logger.debug(
             "%s returned (exit code: %d)",
-            ' '.join([shlex.quote(x) for x in self.cmdline()]),
+            ' '.join([shlex_quote(x) for x in self.cmdline()]),
             returncode,
         )
         return returncode
diff --git a/diffoscope/comparators/utils/container.py b/diffoscope/comparators/utils/container.py
index b35cfa3..8652033 100644
--- a/diffoscope/comparators/utils/container.py
+++ b/diffoscope/comparators/utils/container.py
@@ -37,7 +37,7 @@ NO_COMMENT = None
 logger = logging.getLogger(__name__)
 
 
-class Container(object, metaclass=abc.ABCMeta):
+class Container(object):
     def __new__(cls, source):
         if isinstance(source, MissingFile):
             new = super(Container, MissingContainer).__new__(MissingContainer)
diff --git a/diffoscope/comparators/utils/file.py b/diffoscope/comparators/utils/file.py
index f38cead..b608f6e 100644
--- a/diffoscope/comparators/utils/file.py
+++ b/diffoscope/comparators/utils/file.py
@@ -36,6 +36,11 @@ try:
 except ImportError:  # noqa
     tlsh = None
 
+try:
+    from os import scandir
+except ImportError:
+    from scandir import scandir
+
 SMALL_FILE_THRESHOLD = 65536 # 64 kiB
 
 logger = logging.getLogger(__name__)
@@ -44,10 +49,10 @@ logger = logging.getLogger(__name__)
 def path_apparent_size(path=".", visited=None):
     # should output the same as `du --apparent-size -bs "$path"`
     if not visited:
-        stat = os.stat(path, follow_symlinks=False)
+        stat = os.lstat(path)
         visited = { stat.st_ino: stat.st_size }
     if os.path.isdir(path) and not os.path.islink(path):
-        for entry in os.scandir(path):
+        for entry in scandir(path):
             inode = entry.inode()
             if inode in visited:
                 continue
@@ -57,7 +62,7 @@ def path_apparent_size(path=".", visited=None):
     return sum(visited.values())
 
 
-class File(object, metaclass=abc.ABCMeta):
+class File(object):
     RE_FILE_TYPE = None
     RE_FILE_EXTENSION = None
 
diff --git a/diffoscope/comparators/utils/libarchive.py b/diffoscope/comparators/utils/libarchive.py
index f427ede..50aafc8 100644
--- a/diffoscope/comparators/utils/libarchive.py
+++ b/diffoscope/comparators/utils/libarchive.py
@@ -17,6 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import
 
 import time
 import os.path
@@ -95,7 +96,7 @@ def list_libarchive(path):
 
 class LibarchiveMember(ArchiveMember):
     def __init__(self, archive, entry):
-        super().__init__(archive, entry.pathname)
+        super(LibarchiveMember, self).__init__(archive, entry.pathname)
 
     def is_directory(self):
         return False
@@ -229,7 +230,8 @@ class LibarchiveContainer(Archive):
 
                 logger.debug("Extracting %s to %s", entry.pathname, dst)
 
-                os.makedirs(os.path.dirname(dst), exist_ok=True)
+                if not os.path.exists(os.path.dirname(dst)):
+                    os.makedirs(os.path.dirname(dst))
                 try:
                     with open(dst, 'wb') as f:
                         for block in entry.get_blocks():
@@ -246,4 +248,4 @@ class LibarchiveContainer(Archive):
         def hide_trivial_dirs(item):
             file1, file2, comment = item
             return not (isinstance(file1, Directory) and isinstance(file2, Directory) and comment is None)
-        return filter(hide_trivial_dirs, super().comparisons(other))
+        return filter(hide_trivial_dirs, super(LibarchiveContainer, self).comparisons(other))
diff --git a/diffoscope/diff.py b/diffoscope/diff.py
index a300217..edbcd72 100644
--- a/diffoscope/diff.py
+++ b/diffoscope/diff.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import re
 import io
 import os
@@ -187,9 +189,10 @@ def run_diff(fifo1, fifo2, end_nl_q1, end_nl_q2):
     return parser.diff
 
 class FIFOFeeder(threading.Thread):
-    def __init__(self, feeder, fifo_path, end_nl_q=None, *, daemon=True):
+    def __init__(self, feeder, fifo_path, end_nl_q=None, daemon=True):
         os.mkfifo(fifo_path)
-        super().__init__(daemon=daemon)
+        super(FIFOFeeder, self).__init__()
+        self.setDaemon(daemon)
         self.feeder = feeder
         self.fifo_path = fifo_path
         self.end_nl_q = Queue() if end_nl_q is None else end_nl_q
@@ -221,17 +224,19 @@ class FIFOFeeder(threading.Thread):
 
             # Now clear the fd's nonblocking flag to let writes block normally.
             fcntl.fcntl(fifo_fd, fcntl.F_SETFL, 0)
-            with open(fifo_fd, 'wb') as fifo:
+            with os.fdopen(fifo_fd, 'wb') as fifo:
                 # The queue works around a unified diff limitation: if there's
                 # no newlines in both don't make it a difference
                 end_nl = self.feeder(fifo)
                 self.end_nl_q.put(end_nl)
         except Exception as error:
+            import traceback
+            traceback.print_exc()
             self._exception = error
 
     def join(self):
         self._want_join.set()
-        super().join()
+        super(FIFOFeeder, self).join()
         if self._exception is not None:
             raise self._exception
 
diff --git a/diffoscope/difference.py b/diffoscope/difference.py
index ca45041..5f2e454 100644
--- a/diffoscope/difference.py
+++ b/diffoscope/difference.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import signal
 import hashlib
 import logging
@@ -76,9 +78,9 @@ class Difference(object):
             self._source1 = path1
             self._source2 = path2
         # Ensure renderable types
-        if not isinstance(self._source1, str):
+        if not isinstance(self._source1, (str, unicode)):
             raise TypeError("path1/source[0] is not a string")
-        if not isinstance(self._source2, str):
+        if not isinstance(self._source2, (str, unicode)):
             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
diff --git a/diffoscope/logging.py b/diffoscope/logging.py
index 38bb9bf..a59c447 100644
--- a/diffoscope/logging.py
+++ b/diffoscope/logging.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import absolute_import
+
 import logging
 
 
@@ -33,3 +35,6 @@ def setup_logging(debug, log_handler):
         '%Y-%m-%d %H:%M:%S',
     )
     ch.setFormatter(formatter)
+
+# helps us avoid "absolute_import" in all modules that "import logging"
+getLogger = logging.getLogger
diff --git a/diffoscope/main.py b/diffoscope/main.py
index 8ce6e1c..6483776 100644
--- a/diffoscope/main.py
+++ b/diffoscope/main.py
@@ -20,6 +20,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import os
 import sys
 import signal
@@ -27,6 +29,13 @@ import logging
 import argparse
 import traceback
 
+try:
+    from subprocess import DEVNULL # py3k
+except ImportError:
+    DEVNULL = open(os.devnull, 'wb')
+    import subprocess
+    subprocess.DEVNULL = DEVNULL
+
 from . import VERSION
 from .path import set_path
 from .tools import tool_required, OS_NAMES, get_current_os
@@ -340,7 +349,7 @@ def main(args=None):
     except KeyboardInterrupt:
         logger.info('Keyboard Interrupt')
         sys.exit(2)
-    except BrokenPipeError:
+    except OSError:
         sys.exit(2)
     except Exception:
         traceback.print_exc()
diff --git a/diffoscope/presenters/html/html.py b/diffoscope/presenters/html/html.py
index c502fa2..2188a9a 100644
--- a/diffoscope/presenters/html/html.py
+++ b/diffoscope/presenters/html/html.py
@@ -31,6 +31,8 @@
 # Dave Burt <dave (at) burt.id.au> (mainly for html theme)
 #
 
+from __future__ import unicode_literals
+
 import io
 import os
 import re
@@ -537,7 +539,7 @@ def output_html(difference, css_url=None, print_func=None):
     Default presenter, all in one HTML file
     """
     if print_func is None:
-        print_func = print
+        print_func = getattr(__builtins__, "print")
     print_func = create_limited_print_func(print_func, Config().max_report_size)
     try:
         output_header(css_url, print_func)
diff --git a/diffoscope/presenters/json.py b/diffoscope/presenters/json.py
index 18d8f56..edf7fd2 100644
--- a/diffoscope/presenters/json.py
+++ b/diffoscope/presenters/json.py
@@ -31,12 +31,12 @@ class JSONPresenter(Presenter):
         self.stack = []
         self.print_func = print_func
 
-        super().__init__()
+        super(JSONPresenter, self).__init__()
 
     def start(self, difference):
         root = []
         self.stack = [root]
-        super().start(difference)
+        super(JSONPresenter, self).start(difference)
 
         root[0][JSON_FORMAT_MAGIC] = JSON_FORMAT_VERSION
         root[0].move_to_end(JSON_FORMAT_MAGIC, last=False)
diff --git a/diffoscope/presenters/text.py b/diffoscope/presenters/text.py
index 7a16385..63d729a 100644
--- a/diffoscope/presenters/text.py
+++ b/diffoscope/presenters/text.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+
 import re
 import sys
 import logging
@@ -41,7 +43,7 @@ class TextPresenter(Presenter):
         )
         self.color = color
 
-        super().__init__()
+        super(TextPresenter, self).__init__()
 
     @classmethod
     def run(cls, data, difference, parsed_args):
@@ -57,6 +59,8 @@ class TextPresenter(Presenter):
             try:
                 presenter.start(difference)
             except UnicodeEncodeError:
+                import traceback
+                traceback.print_exc()
                 logger.critical(
                     "Console is unable to print Unicode characters. Set e.g. "
                     "PYTHONIOENCODING=utf-8"
@@ -65,7 +69,7 @@ class TextPresenter(Presenter):
 
     def start(self, difference):
         try:
-            super().start(difference)
+            super(TextPresenter, self).start(difference)
         except PrintLimitReached:
             self.print_func("Max output size reached.", force=True)
 
diff --git a/diffoscope/presenters/utils.py b/diffoscope/presenters/utils.py
index 09f1b74..02d0af0 100644
--- a/diffoscope/presenters/utils.py
+++ b/diffoscope/presenters/utils.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function, unicode_literals
+
 import sys
 import codecs
 import contextlib
diff --git a/diffoscope/profiling.py b/diffoscope/profiling.py
index e381b78..c23727c 100644
--- a/diffoscope/profiling.py
+++ b/diffoscope/profiling.py
@@ -70,11 +70,11 @@ class ProfileManager(object):
         with make_printer(parsed_args.profile_output) as fn:
             self.output(fn)
 
-    def output(self, print):
+    def output(self, printf):
         title = "Profiling output for: {}".format(' '.join(sys.argv))
 
-        print(title)
-        print("=" * len(title))
+        printf(title)
+        printf("=" * len(title))
 
         for namespace, keys in sorted(self.data.items(), key=lambda x: x[0]):
             subtitle = "{} (total time: {:.3f}s)".format(
@@ -82,10 +82,10 @@ class ProfileManager(object):
                 sum(x['time'] for x in keys.values()),
             )
 
-            print("\n{}\n{}\n".format(subtitle, "-" * len(subtitle)))
+            printf("\n{}\n{}\n".format(subtitle, "-" * len(subtitle)))
 
             for value, totals in sorted(keys.items(), key=lambda x: x[1]['time'], reverse=True):
-                print("  {:10.3f}s {:5d} call{}    {}".format(
+                printf("  {:10.3f}s {:5d} call{}    {}".format(
                     totals['time'],
                     totals['count'],
                     ' ' if totals['count'] == 1 else 's',
diff --git a/diffoscope/progress.py b/diffoscope/progress.py
index b00dd48..e020e36 100644
--- a/diffoscope/progress.py
+++ b/diffoscope/progress.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function, absolute_import, unicode_literals
+
 import os
 import sys
 import json
@@ -29,14 +31,14 @@ class ProgressLoggingHandler(logging.StreamHandler):
 
     def __init__(self, progressbar):
         self.progressbar = progressbar
-        super().__init__()
+        super(ProgressLoggingHandler, self).__init__()
 
     def emit(self, record):
         try:
             # delete the current line (i.e. the progress bar)
             self.stream.write("\r\033[K")
             self.flush()
-            super().emit(record)
+            super(ProgressLoggingHandler, self).emit(record)
             if not self.progressbar.bar.finished:
                 self.progressbar.bar.update()
         except Exception:
diff --git a/diffoscope/tempfiles.py b/diffoscope/tempfiles.py
index b63bb16..054f781 100644
--- a/diffoscope/tempfiles.py
+++ b/diffoscope/tempfiles.py
@@ -17,6 +17,8 @@
 # You should have received a copy of the GNU General Public License
 # along with diffoscope.  If not, see <https://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import os
 import logging
 import tempfile
@@ -37,7 +39,7 @@ def get_named_temporary_file(*args, **kwargs):
 def get_temporary_directory(*args, **kwargs):
     kwargs['suffix'] = kwargs.pop('suffix', '_diffoscope')
 
-    d = tempfile.TemporaryDirectory(*args, **kwargs)
+    d = TemporaryDirectory(*args, **kwargs)
     _DIRS.append(d)
 
     return d
@@ -60,3 +62,88 @@ def clean_all_temp_files():
             x.cleanup()
         except:
             logger.exception("Unable to delete %s", x)
+
+
+import warnings
+from tempfile import mkdtemp
+
+class TemporaryDirectory(object):
+    """Create and return a temporary directory.  This has the same
+    behavior as mkdtemp but can be used as a context manager.  For
+    example:
+
+        with TemporaryDirectory() as tmpdir:
+            ...
+
+    Upon exiting the context, the directory and everything contained
+    in it are removed.
+    """
+
+    def __init__(self, suffix="", prefix="tmp", dir=None):
+        self._closed = False
+        self.name = None # Handle mkdtemp raising an exception
+        self.name = mkdtemp(suffix, prefix, dir)
+
+    def __repr__(self):
+        return "<{} {!r}>".format(self.__class__.__name__, self.name)
+
+    def __enter__(self):
+        return self.name
+
+    def cleanup(self, _warn=False):
+        if self.name and not self._closed:
+            try:
+                self._rmtree(self.name)
+            except (TypeError, AttributeError) as ex:
+                # Issue #10188: Emit a warning on stderr
+                # if the directory could not be cleaned
+                # up due to missing globals
+                if "None" not in str(ex):
+                    raise
+                print("ERROR: {!r} while cleaning up {!r}".format(ex, self,),
+                      file=_sys.stderr)
+                return
+            self._closed = True
+            if _warn:
+                self._warn("Implicitly cleaning up {!r}".format(self),
+                           ResourceWarning)
+
+    def __exit__(self, exc, value, tb):
+        self.cleanup()
+
+    def __del__(self):
+        # Issue a ResourceWarning if implicit cleanup needed
+        self.cleanup(_warn=True)
+
+    # XXX (ncoghlan): The following code attempts to make
+    # this class tolerant of the module nulling out process
+    # that happens during CPython interpreter shutdown
+    # Alas, it doesn't actually manage it. See issue #10188
+    _listdir = staticmethod(os.listdir)
+    _path_join = staticmethod(os.path.join)
+    _isdir = staticmethod(os.path.isdir)
+    _islink = staticmethod(os.path.islink)
+    _remove = staticmethod(os.remove)
+    _rmdir = staticmethod(os.rmdir)
+    _warn = warnings.warn
+
+    def _rmtree(self, path):
+        # Essentially a stripped down version of shutil.rmtree.  We can't
+        # use globals because they may be None'ed out at shutdown.
+        for name in self._listdir(path):
+            fullname = self._path_join(path, name)
+            try:
+                isdir = self._isdir(fullname) and not self._islink(fullname)
+            except OSError:
+                isdir = False
+            if isdir:
+                self._rmtree(fullname)
+            else:
+                try:
+                    self._remove(fullname)
+                except OSError:
+                    pass
+        try:
+            self._rmdir(path)
+        except OSError:
+            pass
diff --git a/diffoscope/tools.py b/diffoscope/tools.py
index 2882b01..fbe617f 100644
--- a/diffoscope/tools.py
+++ b/diffoscope/tools.py
@@ -28,7 +28,7 @@ from .profiling import profile
 
 # Memoize calls to ``distutils.spawn.find_executable`` to avoid excessive stat
 # calls
-find_executable = functools.lru_cache()(find_executable)
+#find_executable = functools.lru_cache()(find_executable)
 
 # The output of --help and --list-tools will use the order of this dict.
 # Please keep it alphabetized.

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


More information about the diffoscope mailing list