Merge pull request #1846 from Abogical/master

Improve extract progress display, for #1721
This commit is contained in:
enkore 2016-11-14 21:43:35 +01:00 committed by GitHub
commit cf8f8fb746
5 changed files with 58 additions and 17 deletions

View File

@ -31,7 +31,7 @@ from .helpers import format_time, format_timedelta, format_file_size, file_statu
from .helpers import safe_encode, safe_decode, make_path_safe, remove_surrogates, swidth_slice
from .helpers import decode_dict, StableDict
from .helpers import int_to_bigint, bigint_to_int, bin_to_hex
from .helpers import ProgressIndicatorPercent, log_multi
from .helpers import ellipsis_truncate, ProgressIndicatorPercent, log_multi
from .helpers import PathPrefixPattern, FnmatchPattern
from .helpers import consume, chunkit
from .helpers import CompressionDecider1, CompressionDecider2, CompressionSpec
@ -93,11 +93,7 @@ class Statistics:
msg = ''
space = columns - swidth(msg)
if space >= 8:
if space < swidth('...') + swidth(path):
path = '%s...%s' % (swidth_slice(path, space // 2 - swidth('...')),
swidth_slice(path, -space // 2))
space -= swidth(path)
msg += path + ' ' * space
msg += ellipsis_truncate(path, space)
else:
msg = ' ' * columns
print(msg, file=stream or sys.stderr, end="\r", flush=True)
@ -448,7 +444,7 @@ Number of files: {0.stats.nfiles}'''.format(
if 'chunks' in item:
for _, data in self.pipeline.fetch_many([c.id for c in item.chunks], is_preloaded=True):
if pi:
pi.show(increase=len(data))
pi.show(increase=len(data), info=[remove_surrogates(item.path)])
if stdout:
sys.stdout.buffer.write(data)
if stdout:
@ -501,7 +497,7 @@ Number of files: {0.stats.nfiles}'''.format(
ids = [c.id for c in item.chunks]
for _, data in self.pipeline.fetch_many(ids, is_preloaded=True):
if pi:
pi.show(increase=len(data))
pi.show(increase=len(data), info=[remove_surrogates(item.path)])
with backup_io():
if sparse and self.zeros.startswith(data):
# all-zero chunk: create a hole in a sparse file

View File

@ -501,7 +501,7 @@ class Archiver:
filter = self.build_filter(matcher, peek_and_store_hardlink_masters, strip_components)
if progress:
pi = ProgressIndicatorPercent(msg='Extracting files %5.1f%%', step=0.1)
pi = ProgressIndicatorPercent(msg='%5.1f%% Extracting: %s', step=0.1)
pi.output('Calculating size')
extracted_size = sum(item.file_size(hardlink_masters) for item in archive.iter_items(filter))
pi.total = extracted_size
@ -546,6 +546,9 @@ class Archiver:
for pattern in include_patterns:
if pattern.match_count == 0:
self.print_warning("Include pattern '%s' never matched.", pattern)
if pi:
# clear progress output
pi.finish()
return self.exit_code
@with_repository()

View File

@ -26,6 +26,7 @@ from functools import wraps, partial, lru_cache
from itertools import islice
from operator import attrgetter
from string import Formatter
from shutil import get_terminal_size
import msgpack
import msgpack.fallback
@ -1191,6 +1192,23 @@ def yes(msg=None, false_msg=None, true_msg=None, default_msg=None,
env_var_override = None
def ellipsis_truncate(msg, space):
"""
shorten a long string by adding ellipsis between it and return it, example:
this_is_a_very_long_string -------> this_is..._string
"""
from .platform import swidth
ellipsis_width = swidth('...')
msg_width = swidth(msg)
if space < 8:
# if there is very little space, just show ...
return '...' + ' ' * (space - ellipsis_width)
if space < ellipsis_width + msg_width:
return '%s...%s' % (swidth_slice(msg, space // 2 - ellipsis_width),
swidth_slice(msg, -space // 2))
return msg + ' ' * (space - msg_width)
class ProgressIndicatorPercent:
LOGGER = 'borg.output.progress'
@ -1208,7 +1226,6 @@ class ProgressIndicatorPercent:
self.trigger_at = start # output next percentage value when reaching (at least) this
self.step = step
self.msg = msg
self.output_len = len(self.msg % 100.0)
self.handler = None
self.logger = logging.getLogger(self.LOGGER)
@ -1239,14 +1256,33 @@ class ProgressIndicatorPercent:
self.trigger_at += self.step
return pct
def show(self, current=None, increase=1):
def show(self, current=None, increase=1, info=None):
"""
Show and output the progress message
:param current: set the current percentage [None]
:param increase: increase the current percentage [None]
:param info: array of strings to be formatted with msg [None]
"""
pct = self.progress(current, increase)
if pct is not None:
# truncate the last argument, if no space is available
if info is not None:
# no need to truncate if we're not outputing to a terminal
terminal_space = get_terminal_size(fallback=(-1, -1))[0]
if terminal_space != -1:
space = terminal_space - len(self.msg % tuple([pct] + info[:-1] + ['']))
info[-1] = ellipsis_truncate(info[-1], space)
return self.output(self.msg % tuple([pct] + info), justify=False)
return self.output(self.msg % pct)
def output(self, message):
self.output_len = max(len(message), self.output_len)
message = message.ljust(self.output_len)
def output(self, message, justify=True):
if justify:
terminal_space = get_terminal_size(fallback=(-1, -1))[0]
# no need to ljust if we're not outputing to a terminal
if terminal_space != -1:
message = message.ljust(terminal_space)
self.logger.info(message)
def finish(self):

View File

@ -774,7 +774,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
with changedir('output'):
output = self.cmd('extract', self.repository_location + '::test', '--progress')
assert 'Extracting files' in output
assert 'Extracting:' in output
def _create_test_caches(self):
self.cmd('init', self.repository_location)

View File

@ -903,7 +903,10 @@ def test_yes_env_output(capfd, monkeypatch):
assert 'yes' in err
def test_progress_percentage_sameline(capfd):
def test_progress_percentage_sameline(capfd, monkeypatch):
# run the test as if it was in a 4x1 terminal
monkeypatch.setenv('COLUMNS', '4')
monkeypatch.setenv('LINES', '1')
pi = ProgressIndicatorPercent(1000, step=5, start=0, msg="%3.0f%%")
pi.logger.setLevel('INFO')
pi.show(0)
@ -921,7 +924,10 @@ def test_progress_percentage_sameline(capfd):
assert err == ' ' * 4 + '\r'
def test_progress_percentage_step(capfd):
def test_progress_percentage_step(capfd, monkeypatch):
# run the test as if it was in a 4x1 terminal
monkeypatch.setenv('COLUMNS', '4')
monkeypatch.setenv('LINES', '1')
pi = ProgressIndicatorPercent(100, step=2, start=0, msg="%3.0f%%")
pi.logger.setLevel('INFO')
pi.show()