mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 00:14:35 +03:00
2038822d9f
Before this patch, 'progress' extension applies 'len' on byte sequence to get column width of it, but it causes incorrect result, when length of byte sequence and columns in display are different from each other in multi-byte characters. This patch uses 'encoding.colwidth' to get column width of items in output line correctly, even if it contains multi-byte characters.
306 lines
11 KiB
Python
306 lines
11 KiB
Python
# progress.py show progress bars for some actions
|
|
#
|
|
# Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
"""show progress bars for some actions
|
|
|
|
This extension uses the progress information logged by hg commands
|
|
to draw progress bars that are as informative as possible. Some progress
|
|
bars only offer indeterminate information, while others have a definite
|
|
end point.
|
|
|
|
The following settings are available::
|
|
|
|
[progress]
|
|
delay = 3 # number of seconds (float) before showing the progress bar
|
|
changedelay = 1 # changedelay: minimum delay before showing a new topic.
|
|
# If set to less than 3 * refresh, that value will
|
|
# be used instead.
|
|
refresh = 0.1 # time in seconds between refreshes of the progress bar
|
|
format = topic bar number estimate # format of the progress bar
|
|
width = <none> # if set, the maximum width of the progress information
|
|
# (that is, min(width, term width) will be used)
|
|
clear-complete = True # clear the progress bar after it's done
|
|
disable = False # if true, don't show a progress bar
|
|
assume-tty = False # if true, ALWAYS show a progress bar, unless
|
|
# disable is given
|
|
|
|
Valid entries for the format field are topic, bar, number, unit,
|
|
estimate, speed, and item. item defaults to the last 20 characters of
|
|
the item, but this can be changed by adding either ``-<num>`` which
|
|
would take the last num characters, or ``+<num>`` for the first num
|
|
characters.
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
|
|
from mercurial.i18n import _
|
|
testedwith = 'internal'
|
|
|
|
from mercurial import encoding
|
|
|
|
def spacejoin(*args):
|
|
return ' '.join(s for s in args if s)
|
|
|
|
def shouldprint(ui):
|
|
return not ui.plain() and (ui._isatty(sys.stderr) or
|
|
ui.configbool('progress', 'assume-tty'))
|
|
|
|
def fmtremaining(seconds):
|
|
if seconds < 60:
|
|
# i18n: format XX seconds as "XXs"
|
|
return _("%02ds") % (seconds)
|
|
minutes = seconds // 60
|
|
if minutes < 60:
|
|
seconds -= minutes * 60
|
|
# i18n: format X minutes and YY seconds as "XmYYs"
|
|
return _("%dm%02ds") % (minutes, seconds)
|
|
# we're going to ignore seconds in this case
|
|
minutes += 1
|
|
hours = minutes // 60
|
|
minutes -= hours * 60
|
|
if hours < 30:
|
|
# i18n: format X hours and YY minutes as "XhYYm"
|
|
return _("%dh%02dm") % (hours, minutes)
|
|
# we're going to ignore minutes in this case
|
|
hours += 1
|
|
days = hours // 24
|
|
hours -= days * 24
|
|
if days < 15:
|
|
# i18n: format X days and YY hours as "XdYYh"
|
|
return _("%dd%02dh") % (days, hours)
|
|
# we're going to ignore hours in this case
|
|
days += 1
|
|
weeks = days // 7
|
|
days -= weeks * 7
|
|
if weeks < 55:
|
|
# i18n: format X weeks and YY days as "XwYYd"
|
|
return _("%dw%02dd") % (weeks, days)
|
|
# we're going to ignore days and treat a year as 52 weeks
|
|
weeks += 1
|
|
years = weeks // 52
|
|
weeks -= years * 52
|
|
# i18n: format X years and YY weeks as "XyYYw"
|
|
return _("%dy%02dw") % (years, weeks)
|
|
|
|
class progbar(object):
|
|
def __init__(self, ui):
|
|
self.ui = ui
|
|
self.resetstate()
|
|
|
|
def resetstate(self):
|
|
self.topics = []
|
|
self.topicstates = {}
|
|
self.starttimes = {}
|
|
self.startvals = {}
|
|
self.printed = False
|
|
self.lastprint = time.time() + float(self.ui.config(
|
|
'progress', 'delay', default=3))
|
|
self.lasttopic = None
|
|
self.indetcount = 0
|
|
self.refresh = float(self.ui.config(
|
|
'progress', 'refresh', default=0.1))
|
|
self.changedelay = max(3 * self.refresh,
|
|
float(self.ui.config(
|
|
'progress', 'changedelay', default=1)))
|
|
self.order = self.ui.configlist(
|
|
'progress', 'format',
|
|
default=['topic', 'bar', 'number', 'estimate'])
|
|
|
|
def show(self, now, topic, pos, item, unit, total):
|
|
if not shouldprint(self.ui):
|
|
return
|
|
termwidth = self.width()
|
|
self.printed = True
|
|
head = ''
|
|
needprogress = False
|
|
tail = ''
|
|
for indicator in self.order:
|
|
add = ''
|
|
if indicator == 'topic':
|
|
add = topic
|
|
elif indicator == 'number':
|
|
if total:
|
|
add = ('% ' + str(len(str(total))) +
|
|
's/%s') % (pos, total)
|
|
else:
|
|
add = str(pos)
|
|
elif indicator.startswith('item') and item:
|
|
slice = 'end'
|
|
if '-' in indicator:
|
|
wid = int(indicator.split('-')[1])
|
|
elif '+' in indicator:
|
|
slice = 'beginning'
|
|
wid = int(indicator.split('+')[1])
|
|
else:
|
|
wid = 20
|
|
if slice == 'end':
|
|
add = encoding.trim(item, wid, leftside=True)
|
|
else:
|
|
add = encoding.trim(item, wid)
|
|
add += (wid - encoding.colwidth(add)) * ' '
|
|
elif indicator == 'bar':
|
|
add = ''
|
|
needprogress = True
|
|
elif indicator == 'unit' and unit:
|
|
add = unit
|
|
elif indicator == 'estimate':
|
|
add = self.estimate(topic, pos, total, now)
|
|
elif indicator == 'speed':
|
|
add = self.speed(topic, pos, unit, now)
|
|
if not needprogress:
|
|
head = spacejoin(head, add)
|
|
else:
|
|
tail = spacejoin(tail, add)
|
|
if needprogress:
|
|
used = 0
|
|
if head:
|
|
used += encoding.colwidth(head) + 1
|
|
if tail:
|
|
used += encoding.colwidth(tail) + 1
|
|
progwidth = termwidth - used - 3
|
|
if total and pos <= total:
|
|
amt = pos * progwidth // total
|
|
bar = '=' * (amt - 1)
|
|
if amt > 0:
|
|
bar += '>'
|
|
bar += ' ' * (progwidth - amt)
|
|
else:
|
|
progwidth -= 3
|
|
self.indetcount += 1
|
|
# mod the count by twice the width so we can make the
|
|
# cursor bounce between the right and left sides
|
|
amt = self.indetcount % (2 * progwidth)
|
|
amt -= progwidth
|
|
bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
|
|
' ' * int(abs(amt)))
|
|
prog = ''.join(('[', bar , ']'))
|
|
out = spacejoin(head, prog, tail)
|
|
else:
|
|
out = spacejoin(head, tail)
|
|
sys.stderr.write('\r' + encoding.trim(out, termwidth))
|
|
self.lasttopic = topic
|
|
sys.stderr.flush()
|
|
|
|
def clear(self):
|
|
if not shouldprint(self.ui):
|
|
return
|
|
sys.stderr.write('\r%s\r' % (' ' * self.width()))
|
|
|
|
def complete(self):
|
|
if not shouldprint(self.ui):
|
|
return
|
|
if self.ui.configbool('progress', 'clear-complete', default=True):
|
|
self.clear()
|
|
else:
|
|
sys.stderr.write('\n')
|
|
sys.stderr.flush()
|
|
|
|
def width(self):
|
|
tw = self.ui.termwidth()
|
|
return min(int(self.ui.config('progress', 'width', default=tw)), tw)
|
|
|
|
def estimate(self, topic, pos, total, now):
|
|
if total is None:
|
|
return ''
|
|
initialpos = self.startvals[topic]
|
|
target = total - initialpos
|
|
delta = pos - initialpos
|
|
if delta > 0:
|
|
elapsed = now - self.starttimes[topic]
|
|
if elapsed > float(
|
|
self.ui.config('progress', 'estimate', default=2)):
|
|
seconds = (elapsed * (target - delta)) // delta + 1
|
|
return fmtremaining(seconds)
|
|
return ''
|
|
|
|
def speed(self, topic, pos, unit, now):
|
|
initialpos = self.startvals[topic]
|
|
delta = pos - initialpos
|
|
elapsed = now - self.starttimes[topic]
|
|
if elapsed > float(
|
|
self.ui.config('progress', 'estimate', default=2)):
|
|
return _('%d %s/sec') % (delta / elapsed, unit)
|
|
return ''
|
|
|
|
def progress(self, topic, pos, item='', unit='', total=None):
|
|
now = time.time()
|
|
if pos is None:
|
|
self.starttimes.pop(topic, None)
|
|
self.startvals.pop(topic, None)
|
|
self.topicstates.pop(topic, None)
|
|
# reset the progress bar if this is the outermost topic
|
|
if self.topics and self.topics[0] == topic and self.printed:
|
|
self.complete()
|
|
self.resetstate()
|
|
# truncate the list of topics assuming all topics within
|
|
# this one are also closed
|
|
if topic in self.topics:
|
|
self.topics = self.topics[:self.topics.index(topic)]
|
|
# reset the last topic to the one we just unwound to,
|
|
# so that higher-level topics will be stickier than
|
|
# lower-level topics
|
|
if self.topics:
|
|
self.lasttopic = self.topics[-1]
|
|
else:
|
|
self.lasttopic = None
|
|
else:
|
|
if topic not in self.topics:
|
|
self.starttimes[topic] = now
|
|
self.startvals[topic] = pos
|
|
self.topics.append(topic)
|
|
self.topicstates[topic] = pos, item, unit, total
|
|
if now - self.lastprint >= self.refresh and self.topics:
|
|
if (self.lasttopic is None # first time we printed
|
|
# not a topic change
|
|
or topic == self.lasttopic
|
|
# it's been long enough we should print anyway
|
|
or now - self.lastprint >= self.changedelay):
|
|
self.lastprint = now
|
|
self.show(now, topic, *self.topicstates[topic])
|
|
|
|
_singleton = None
|
|
|
|
def uisetup(ui):
|
|
global _singleton
|
|
class progressui(ui.__class__):
|
|
_progbar = None
|
|
|
|
def _quiet(self):
|
|
return self.debugflag or self.quiet
|
|
|
|
def progress(self, *args, **opts):
|
|
if not self._quiet():
|
|
self._progbar.progress(*args, **opts)
|
|
return super(progressui, self).progress(*args, **opts)
|
|
|
|
def write(self, *args, **opts):
|
|
if not self._quiet() and self._progbar.printed:
|
|
self._progbar.clear()
|
|
return super(progressui, self).write(*args, **opts)
|
|
|
|
def write_err(self, *args, **opts):
|
|
if not self._quiet() and self._progbar.printed:
|
|
self._progbar.clear()
|
|
return super(progressui, self).write_err(*args, **opts)
|
|
|
|
# Apps that derive a class from ui.ui() can use
|
|
# setconfig('progress', 'disable', 'True') to disable this extension
|
|
if ui.configbool('progress', 'disable'):
|
|
return
|
|
if shouldprint(ui) and not ui.debugflag and not ui.quiet:
|
|
ui.__class__ = progressui
|
|
# we instantiate one globally shared progress bar to avoid
|
|
# competing progress bars when multiple UI objects get created
|
|
if not progressui._progbar:
|
|
if _singleton is None:
|
|
_singleton = progbar(ui)
|
|
progressui._progbar = _singleton
|
|
|
|
def reposetup(ui, repo):
|
|
uisetup(repo.ui)
|