Skip to content
Commits on Source (6)
......@@ -19,8 +19,13 @@
# You should have received a copy of the GNU General Public License
# along with diffoscope. If not, see <https://www.gnu.org/licenses/>.
import sys
import logging
import importlib
import traceback
from ..logging import line_ereser
logger = logging.getLogger(__name__)
......@@ -108,6 +113,7 @@ class ComparatorManager(object):
self.classes = []
for xs in self.COMPARATORS:
errors = []
for x in xs:
package, klass_name = x.rsplit('.', 1)
......@@ -115,16 +121,22 @@ class ComparatorManager(object):
mod = importlib.import_module(
'diffoscope.comparators.{}'.format(package)
)
except ImportError:
except ImportError as e:
errors.append((x, e))
continue
self.classes.append(getattr(mod, klass_name))
break
else: # noqa
raise ImportError("Could not import {}{}".format(
"any of" if len(xs) > 1 else '',
logger.error("Could not import {}{}".format(
"any of " if len(xs) > 1 else '',
', '.join(xs)
))
for x in errors:
logger.error("Original error for %s:", x[0])
sys.stderr.buffer.write(line_ereser())
traceback.print_exception(None, x[1], x[1].__traceback__)
sys.exit(2)
logger.debug("Loaded %d comparator classes", len(self.classes))
......
......@@ -80,6 +80,14 @@ if not hasattr(libarchive.ffi, 'entry_gname'):
'entry_gname', [libarchive.ffi.c_archive_entry_p], ctypes.c_char_p)
libarchive.ArchiveEntry.gname = property(
lambda self: libarchive.ffi.entry_gname(self._entry_p))
# Monkeypatch libarchive-c (>= 2.8)
# Wire mtime_nsec attribute as some libarchive versions (>=2.8) don't expose it
# for ArchiveEntry. Doing this allows a unified API no matter which version is
# available.
if not hasattr(libarchive.ArchiveEntry, 'mtime_nsec') and hasattr(libarchive.ffi, 'entry_mtime_nsec'):
libarchive.ArchiveEntry.mtime_nsec = property(
lambda self: libarchive.ffi.entry_mtime_nsec(self._entry_p))
# Monkeypatch libarchive-c so we always get pathname as (Unicode) str
# Otherwise, we'll get sometimes str and sometimes bytes and always pain.
......
......@@ -17,10 +17,27 @@
# You should have received a copy of the GNU General Public License
# along with diffoscope. If not, see <https://www.gnu.org/licenses/>.
import sys
import contextlib
import logging
def line_ereser(fd=sys.stderr) -> bytes:
ereser = b'' # avoid None to avoid 'NoneType + str/bytes' failures
if fd.isatty():
from curses import tigetstr, setupterm
setupterm(fd=fd.fileno())
ereser = tigetstr('el')
if not ereser and fd.isatty():
# is a tty, but doesn't support the proper scape code, so let's fake it
from shutil import get_terminal_size
width = get_terminal_size().columns
ereser = b'\r{}\r'.format(b' ' * width)
return ereser
@contextlib.contextmanager
def setup_logging(debug, log_handler):
logger = logging.getLogger()
......@@ -32,6 +49,7 @@ def setup_logging(debug, log_handler):
logger.addHandler(ch)
formatter = logging.Formatter(
line_ereser().decode('ascii') +
'%(asctime)s %(levelname).1s: %(name)s: %(message)s',
'%Y-%m-%d %H:%M:%S',
)
......
......@@ -33,7 +33,7 @@ from .path import set_path
from .tools import tool_prepend_prefix, tool_required, OS_NAMES, get_current_os
from .config import Config
from .locale import set_locale
from .logging import setup_logging
from .logging import line_ereser, setup_logging
from .progress import ProgressManager, Progress
from .profiling import ProfileManager, profile
from .tempfiles import clean_all_temp_files
......@@ -459,13 +459,12 @@ def main(args=None):
post_parse(parsed_args)
sys.exit(run_diffoscope(parsed_args))
except KeyboardInterrupt:
if log_handler:
log_handler.progressbar.bar.erase_line()
logger.info('Keyboard Interrupt')
sys.exit(2)
except BrokenPipeError:
sys.exit(2)
except Exception:
sys.stderr.buffer.write(line_ereser())
traceback.print_exc()
if parsed_args and parsed_args.debugger:
import pdb
......
......@@ -24,6 +24,9 @@ import json
import signal
import logging
from .logging import line_ereser
logger = logging.getLogger(__name__)
......@@ -215,12 +218,7 @@ class ProgressBar(object):
kwargs.setdefault('fd', sys.stderr)
super().__init__(*args, **kwargs)
# Terminal handling after parent init since that sets self.fd
if self.fd.isatty():
from curses import tigetstr, setupterm
setupterm()
self.erase_to_eol = tigetstr('el')
else:
self.erase_to_eol = None
self.erase_to_eol = line_ereser(self.fd)
def _need_update(self):
return True
......@@ -228,13 +226,7 @@ class ProgressBar(object):
def erase_line(self):
if self.erase_to_eol:
self.fd.buffer.write(self.erase_to_eol)
elif self.fd.isatty():
print(end='\r', file=self.fd)
print(' ' * self.term_width, end='', file=self.fd)
else:
# Do not flush if nothing was written
return
self.fd.flush()
self.fd.flush()
def finish(self):
self.finished = True
......