##// END OF EJS Templates
relink: format reclaimed byte count nicely
relink: format reclaimed byte count nicely

File last commit:

r13236:3f299f5d default
r13656:0e200e18 default
Show More
progress.py
275 lines | 9.9 KiB | text/x-python | PythonLexer
Augie Fackler
Progress bar extension
r10434 # progress.py show progress bars for some actions
#
# Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
#
# This program 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 2 of the License, or (at your
# option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""show progress bars for some actions
timeless
progress: fix description
r10450 This extension uses the progress information logged by hg commands
to draw progress bars that are as informative as possible. Some progress
Augie Fackler
Progress bar extension
r10434 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
refresh = 0.1 # time in seconds between refreshes of the progress bar
Augie Fackler
progress: include time estimate as part of the default progress format
r13148 format = topic bar number estimate # format of the progress bar
Augie Fackler
Progress bar extension
r10434 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
Augie Fackler
progress: document progress.disable config option
r10656 disable = False # if true, don't show a progress bar
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 assume-tty = False # if true, ALWAYS show a progress bar, unless
# disable is given
Augie Fackler
Progress bar extension
r10434
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 Valid entries for the format field are topic, bar, number, unit,
estimate, 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.
Augie Fackler
Progress bar extension
r10434 """
import sys
import time
timeless
progress: Add estimated time remaining for long tasks...
r13131 from mercurial.i18n import _
Augie Fackler
Progress bar extension
r10434 from mercurial import util
def spacejoin(*args):
Brodie Rao
progress: simplify spacejoin()
r10452 return ' '.join(s for s in args if s)
Augie Fackler
Progress bar extension
r10434
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 def shouldprint(ui):
Augie Fackler
progress: make sure stderr has isatty before calling (issue2191)
r12654 return (getattr(sys.stderr, 'isatty', None) and
(sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
Augie Fackler
progress: check stderr.isatty() before each print...
r11458
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 def fmtremaining(seconds):
if seconds < 60:
Martin Geisler
progress: explain format strings to translators
r13139 # i18n: format XX seconds as "XXs"
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 return _("%02ds") % (seconds)
minutes = seconds // 60
if minutes < 60:
seconds -= minutes * 60
Martin Geisler
progress: explain format strings to translators
r13139 # i18n: format X minutes and YY seconds as "XmYYs"
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132 return _("%dm%02ds") % (minutes, seconds)
# we're going to ignore seconds in this case
minutes += 1
hours = minutes // 60
minutes -= hours * 60
timeless
progress: handle days, weeks and years...
r13236 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)
Augie Fackler
progress: refactor for readability and show XXs instead of 0mXXs.
r13132
Augie Fackler
Progress bar extension
r10434 class progbar(object):
def __init__(self, ui):
self.ui = ui
self.resetstate()
def resetstate(self):
self.topics = []
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 self.topicstates = {}
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes = {}
self.startvals = {}
Augie Fackler
Progress bar extension
r10434 self.printed = False
self.lastprint = time.time() + float(self.ui.config(
'progress', 'delay', default=3))
self.indetcount = 0
self.refresh = float(self.ui.config(
'progress', 'refresh', default=0.1))
self.order = self.ui.configlist(
'progress', 'format',
Augie Fackler
progress: include time estimate as part of the default progress format
r13148 default=['topic', 'bar', 'number', 'estimate'])
Augie Fackler
Progress bar extension
r10434
timeless
progress: Add estimated time remaining for long tasks...
r13131 def show(self, now, topic, pos, item, unit, total):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Augie Fackler
Progress bar extension
r10434 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 = item[-wid:]
else:
add = item[:wid]
add += (wid - len(add)) * ' '
elif indicator == 'bar':
add = ''
needprogress = True
elif indicator == 'unit' and unit:
add = unit
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 elif indicator == 'estimate':
add = self.estimate(topic, pos, total, now)
Augie Fackler
Progress bar extension
r10434 if not needprogress:
head = spacejoin(head, add)
else:
Augie Fackler
progress: fix adding format elements after the progress bar...
r13146 tail = spacejoin(tail, add)
Augie Fackler
Progress bar extension
r10434 if needprogress:
used = 0
if head:
used += len(head) + 1
if tail:
used += len(tail) + 1
progwidth = termwidth - used - 3
Augie Fackler
progress: fall back to indeterminate progress if position is >= total
r10891 if total and pos <= total:
Augie Fackler
Progress bar extension
r10434 amt = pos * progwidth // total
Brodie Rao
progress: make determinate bar more like wget progress bar...
r10453 bar = '=' * (amt - 1)
if amt > 0:
bar += '>'
bar += ' ' * (progwidth - amt)
Augie Fackler
Progress bar extension
r10434 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)
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\r' + out[:termwidth])
sys.stderr.flush()
Augie Fackler
Progress bar extension
r10434
def clear(self):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\r%s\r' % (' ' * self.width()))
Augie Fackler
Progress bar extension
r10434
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 def complete(self):
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if not shouldprint(self.ui):
return
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 if self.ui.configbool('progress', 'clear-complete', default=True):
self.clear()
else:
Augie Fackler
progress: use stderr instead of stdout; check stderr.isatty()...
r10788 sys.stderr.write('\n')
sys.stderr.flush()
Benoit Boissinot
progress: correctly handle empty progress topic
r10439
Augie Fackler
Progress bar extension
r10434 def width(self):
Augie Fackler
termwidth: move to ui.ui from util
r12689 tw = self.ui.termwidth()
Augie Fackler
Progress bar extension
r10434 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 def estimate(self, topic, pos, total, now):
Augie Fackler
progress: don't compute estimate without a total...
r13154 if total is None:
return ''
Augie Fackler
progress: only show time estimate when progress format contains 'estimate'
r13147 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 ''
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 def progress(self, topic, pos, item='', unit='', total=None):
timeless
progress: Add estimated time remaining for long tasks...
r13131 now = time.time()
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 if pos is None:
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes.pop(topic, None)
self.startvals.pop(topic, None)
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 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:
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 self.complete()
Augie Fackler
progress: only reset state if finishing progress for the current topic...
r10441 self.resetstate()
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 # 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)]
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 else:
if topic not in self.topics:
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.starttimes[topic] = now
self.startvals[topic] = pos
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 self.topics.append(topic)
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 self.topicstates[topic] = pos, item, unit, total
if now - self.lastprint >= self.refresh and self.topics:
Benoit Boissinot
progress: correctly handle empty progress topic
r10439 self.lastprint = now
Augie Fackler
progress: react more reasonably to nested progress topics...
r13130 current = self.topics[-1]
timeless
progress: Add estimated time remaining for long tasks...
r13131 self.show(now, topic, *self.topicstates[topic])
Augie Fackler
Progress bar extension
r10434
def uisetup(ui):
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 class progressui(ui.__class__):
_progbar = None
def progress(self, *args, **opts):
self._progbar.progress(*args, **opts)
return super(progressui, self).progress(*args, **opts)
def write(self, *args, **opts):
if self._progbar.printed:
self._progbar.clear()
return super(progressui, self).write(*args, **opts)
def write_err(self, *args, **opts):
if self._progbar.printed:
self._progbar.clear()
return super(progressui, self).write_err(*args, **opts)
Steve Borho
progress: provide an explicit disable method for developers...
r10540 # Apps that derive a class from ui.ui() can use
# setconfig('progress', 'disable', 'True') to disable this extension
if ui.configbool('progress', 'disable'):
return
Augie Fackler
progress: check stderr.isatty() before each print...
r11458 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 ui.__class__ = progressui
Augie Fackler
Progress bar extension
r10434 # we instantiate one globally shared progress bar to avoid
# competing progress bars when multiple UI objects get created
Brodie Rao
color/progress: subclass ui instead of using wrapfunction (issue2096)...
r11555 if not progressui._progbar:
progressui._progbar = progbar(ui)
Augie Fackler
Progress bar extension
r10434
def reposetup(ui, repo):
uisetup(repo.ui)