mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 09:17:30 +03:00
43489d91d4
Summary: Flushing behavior changed in Python 3 and now we need to flush the progress bar after clearing it, since clearing it is part of suspending the progress and we don't want any stored up redraw bytes to wipe out future writes from the caller. Reviewed By: quark-zju Differential Revision: D22579013 fbshipit-source-id: f3afd560e1365696509f56b137cceababcfed794
784 lines
25 KiB
Python
784 lines
25 KiB
Python
# Portions Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
# progress.py progress bars related code
|
|
#
|
|
# 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.
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import contextlib
|
|
import errno
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
from bindings import threading as rustthreading, tracing
|
|
|
|
from . import encoding, pycompat, util
|
|
from .i18n import _, _x
|
|
|
|
|
|
_tracer = util.tracer
|
|
|
|
|
|
def spacejoin(*args):
|
|
return " ".join(s for s in args if s)
|
|
|
|
|
|
def shouldprint(ui):
|
|
return not (ui.quiet or ui.plain("progress")) and (
|
|
ui._isatty(ui.ferr) or ui.configbool("progress", "assume-tty")
|
|
)
|
|
|
|
|
|
def fmtremaining(seconds):
|
|
"""format a number of remaining seconds in human readable way
|
|
|
|
This will properly display seconds, minutes, hours, days if needed"""
|
|
if seconds is None:
|
|
return ""
|
|
|
|
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)
|
|
|
|
|
|
def estimateremaining(bar):
|
|
if not bar._total:
|
|
return None
|
|
bounds = bar._getestimatebounds()
|
|
if bounds is None:
|
|
return None
|
|
startpos, starttime = bounds[0]
|
|
endpos, endtime = bounds[1]
|
|
if startpos is None or endpos is None:
|
|
return None
|
|
if startpos == endpos:
|
|
return None
|
|
target = bar._total - startpos
|
|
delta = endpos - startpos
|
|
if target >= delta and delta > 0.1:
|
|
elapsed = endtime - starttime
|
|
seconds = (elapsed * (target - delta)) // delta + 1
|
|
return seconds
|
|
return None
|
|
|
|
|
|
def fmtspeed(speed, bar):
|
|
if speed is None:
|
|
return ""
|
|
elif bar._formatfunc:
|
|
return _("%s/sec") % bar._formatfunc(speed)
|
|
elif bar._unit:
|
|
return _("%d %s/sec") % (speed, bar._unit)
|
|
else:
|
|
return _("%d per sec") % speed
|
|
|
|
|
|
def estimatespeed(bar):
|
|
bounds = bar._getestimatebounds()
|
|
if bounds is None:
|
|
return None
|
|
startpos, starttime = bounds[0]
|
|
endpos, endtime = bounds[1]
|
|
if startpos is None or endpos is None:
|
|
return None
|
|
delta = endpos - startpos
|
|
elapsed = endtime - starttime
|
|
if elapsed > 0:
|
|
return delta // elapsed
|
|
return None
|
|
|
|
|
|
# file_write() and file_flush() of Python 2 do not restart on EINTR if
|
|
# the file is attached to a "slow" device (e.g. a terminal) and raise
|
|
# IOError. We cannot know how many bytes would be written by file_write(),
|
|
# but a progress text is known to be short enough to be written by a
|
|
# single write() syscall, so we can just retry file_write() with the whole
|
|
# text. (issue5532)
|
|
#
|
|
# This should be a short-term workaround. We'll need to fix every occurrence
|
|
# of write() to a terminal or pipe.
|
|
def _eintrretry(func, *args):
|
|
while True:
|
|
try:
|
|
return func(*args)
|
|
except IOError as err:
|
|
if err.errno == errno.EINTR:
|
|
continue
|
|
raise
|
|
|
|
|
|
class baserenderer(object):
|
|
"""progress bar renderer for classic-style progress bars"""
|
|
|
|
def __init__(self, bar):
|
|
self._bar = bar
|
|
self.printed = False
|
|
self.configwidth = bar._ui.config("progress", "width", default=None)
|
|
|
|
def _writeprogress(self, msg, flush=False):
|
|
ui = self._bar._ui
|
|
if ui.streampager is not None:
|
|
msg = msg.strip("\r\n") + "\f"
|
|
try:
|
|
ui.streampager.write_progress(
|
|
pycompat.encodeutf8(msg, errors="replace")
|
|
)
|
|
except IOError:
|
|
# IOError can happen if the pager has just exited. Ignore it.
|
|
pass
|
|
else:
|
|
_eintrretry(ui.ferr.write, pycompat.encodeutf8(msg))
|
|
if flush:
|
|
_eintrretry(ui.ferr.flush)
|
|
|
|
def width(self):
|
|
ui = self._bar._ui
|
|
tw = ui.termwidth()
|
|
if self.configwidth is not None:
|
|
return min(int(self.configwidth), tw)
|
|
else:
|
|
return tw
|
|
|
|
def show(self):
|
|
raise NotImplementedError()
|
|
|
|
def clear(self):
|
|
if not self.printed:
|
|
return
|
|
self._writeprogress("\r%s\r" % (" " * self.width()))
|
|
self._bar._ui.ferr.flush()
|
|
|
|
def complete(self):
|
|
if not self.printed:
|
|
return
|
|
self.show(time.time())
|
|
self._writeprogress("\n")
|
|
|
|
|
|
class classicrenderer(baserenderer):
|
|
def __init__(self, bar):
|
|
super(classicrenderer, self).__init__(bar)
|
|
self.order = bar._ui.configlist("progress", "format")
|
|
|
|
def show(self, now):
|
|
pos, item = _progvalue(self._bar.value)
|
|
if pos is None:
|
|
pos = round(now - self._bar._enginestarttime, 1)
|
|
formatfunc = self._bar._formatfunc
|
|
if formatfunc is None:
|
|
formatfunc = str
|
|
topic = self._bar._topic
|
|
unit = self._bar._unit
|
|
total = self._bar._total
|
|
termwidth = self.width()
|
|
self.printed = True
|
|
head = ""
|
|
needprogress = False
|
|
tail = ""
|
|
for indicator in self.order:
|
|
add = ""
|
|
if indicator == "topic":
|
|
add = topic
|
|
elif indicator == "number":
|
|
fpos = formatfunc(pos)
|
|
if total:
|
|
ftotal = formatfunc(total)
|
|
maxlen = max(len(fpos), len(ftotal))
|
|
add = ("% " + str(maxlen) + "s/%s") % (fpos, ftotal)
|
|
else:
|
|
add = fpos
|
|
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 = fmtremaining(estimateremaining(self._bar))
|
|
elif indicator == "speed":
|
|
add = fmtspeed(estimatespeed(self._bar), self._bar)
|
|
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 pos is not None and total and pos <= total:
|
|
amt = pos * progwidth // total
|
|
bar = "=" * (amt - 1)
|
|
if amt > 0:
|
|
bar += ">"
|
|
bar += " " * (progwidth - amt)
|
|
else:
|
|
elapsed = now - self._bar._enginestarttime
|
|
indetpos = int(elapsed / self._bar._refresh)
|
|
progwidth -= 3
|
|
# mod the count by twice the width so we can make the
|
|
# cursor bounce between the right and left sides
|
|
amt = indetpos % (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)
|
|
self._writeprogress("\r" + encoding.trim(out, termwidth), flush=True)
|
|
|
|
|
|
class fancyrenderer(baserenderer):
|
|
def __init__(self, bar):
|
|
super(fancyrenderer, self).__init__(bar)
|
|
|
|
def _mergespans(self, leftspans, rightspans):
|
|
spans = []
|
|
leftspans.reverse()
|
|
rightspans.reverse()
|
|
while leftspans and rightspans:
|
|
leftwidth, leftlabel = leftspans.pop()
|
|
rightwidth, rightlabel = rightspans.pop()
|
|
if leftwidth < rightwidth:
|
|
spans.append((leftwidth, spacejoin(leftlabel, rightlabel)))
|
|
rightspans.append((rightwidth - leftwidth, rightlabel))
|
|
elif leftwidth == rightwidth:
|
|
spans.append((leftwidth, spacejoin(leftlabel, rightlabel)))
|
|
elif leftwidth > rightwidth:
|
|
spans.append((rightwidth, spacejoin(leftlabel, rightlabel)))
|
|
leftspans.append((leftwidth - rightwidth, leftlabel))
|
|
spans.extend(reversed(leftspans))
|
|
spans.extend(reversed(rightspans))
|
|
return spans
|
|
|
|
def _applyspans(self, ui, line, spans):
|
|
out = []
|
|
outpos = 0
|
|
outdebt = 0
|
|
linebyte = 0
|
|
linewidth = encoding.colwidth(line)
|
|
spans.reverse()
|
|
while outpos < linewidth:
|
|
if not spans:
|
|
out.append(line[linebyte:])
|
|
break
|
|
spanwidth, spanlabel = spans.pop()
|
|
spantext = encoding.trim(line[linebyte:], spanwidth + outdebt)
|
|
outdebt += spanwidth - encoding.colwidth(spantext)
|
|
linebyte += len(spantext)
|
|
out.append(ui.label(spantext, spanlabel))
|
|
outpos += spanwidth
|
|
return "".join(out)
|
|
|
|
def show(self, now):
|
|
topic = self._bar._topic
|
|
total = self._bar._total
|
|
pos, item = _progvalue(self._bar.value)
|
|
if total:
|
|
style = "normal"
|
|
else:
|
|
if pos is None:
|
|
style = "spinner"
|
|
pos = round(now - self._bar._enginestarttime, 1)
|
|
else:
|
|
style = "indet"
|
|
spinpos = int((now - self._bar._enginestarttime) * 20)
|
|
termwidth = self.width()
|
|
self.printed = True
|
|
formatfunc = self._bar._formatfunc or str
|
|
fpos = formatfunc(pos)
|
|
if total:
|
|
ftotal = formatfunc(total)
|
|
number = ("% " + str(len(ftotal)) + "s/%s") % (fpos, ftotal)
|
|
remaining = " " + fmtremaining(estimateremaining(self._bar))
|
|
else:
|
|
number = fpos
|
|
remaining = ""
|
|
|
|
start = " %s" % topic
|
|
if item:
|
|
start += ": "
|
|
end = " %s%s " % (number, remaining)
|
|
startwidth = encoding.colwidth(start)
|
|
endwidth = encoding.colwidth(end)
|
|
midwidth = termwidth - startwidth - endwidth
|
|
mid = encoding.trim(item + " " * midwidth, midwidth)
|
|
line = encoding.trim(start + mid + end, termwidth)
|
|
if style == "normal":
|
|
progpos = termwidth * pos // total
|
|
spans = [
|
|
(progpos, "progress.fancy.bar.normal"),
|
|
(termwidth - progpos, "progress.fancy.bar.background"),
|
|
]
|
|
elif style == "indet":
|
|
spinnerwidth = min(6, termwidth / 2)
|
|
progpos = spinpos % ((termwidth - spinnerwidth) * 2)
|
|
if progpos >= (termwidth - spinnerwidth):
|
|
progpos = 2 * (termwidth - spinnerwidth) - progpos
|
|
spans = [
|
|
(progpos, "progress.fancy.bar.background"),
|
|
(spinnerwidth, "progress.fancy.bar.indeterminate"),
|
|
(termwidth - spinnerwidth - progpos, "progress.fancy.bar.background"),
|
|
]
|
|
elif style == "spinner":
|
|
spinnerwidth = min(6, termwidth / 2)
|
|
progpos = spinpos % termwidth
|
|
spans = []
|
|
on = "progress.fancy.bar.spinner"
|
|
off = "progress.fancy.bar.background"
|
|
if progpos > termwidth - spinnerwidth:
|
|
spans = [
|
|
(spinnerwidth - (termwidth - progpos), on),
|
|
(termwidth - spinnerwidth, off),
|
|
(termwidth - progpos, on),
|
|
]
|
|
else:
|
|
spans = [
|
|
(progpos, off),
|
|
(spinnerwidth, on),
|
|
(termwidth - spinnerwidth - progpos, off),
|
|
]
|
|
|
|
spans = self._mergespans(
|
|
spans,
|
|
[
|
|
(startwidth, "progress.fancy.topic"),
|
|
(midwidth, "progress.fancy.item"),
|
|
(endwidth, "progress.fancy.count"),
|
|
],
|
|
)
|
|
line = self._applyspans(self._bar._ui, line, spans)
|
|
self._writeprogress("\r" + line + "\r", flush=True)
|
|
|
|
|
|
class nullrenderer(baserenderer):
|
|
def __init__(self, bar):
|
|
super(nullrenderer, self).__init__(bar)
|
|
|
|
def show(self, now):
|
|
pass
|
|
|
|
|
|
renderers = {"classic": classicrenderer, "fancy": fancyrenderer, "none": nullrenderer}
|
|
|
|
|
|
def getrenderer(bar):
|
|
renderername = bar._ui.config("progress", "renderer")
|
|
return renderers.get(renderername, classicrenderer)(bar)
|
|
|
|
|
|
class engine(object):
|
|
def __init__(self):
|
|
self._cond = rustthreading.Condition()
|
|
self._active = False
|
|
self._refresh = None
|
|
self._delay = None
|
|
self._bars = []
|
|
self._currentbarindex = None
|
|
|
|
@contextlib.contextmanager
|
|
def lock(self):
|
|
# Ugly hack for buggy Python (https://bugs.python.org/issue29988)
|
|
#
|
|
# Python can skip executing "__exit__" if a signal arrives at the
|
|
# "right" time. Workaround it by using N "__exit__"s. Skipping all of
|
|
# them would require N signals to be all sent at "right" time. Unlikely
|
|
# in practise.
|
|
b = rustthreading.bug29988wrapper(self._cond)
|
|
with b, b, b, b, b, b:
|
|
yield
|
|
|
|
@contextlib.contextmanager
|
|
def _lockclear(self):
|
|
with self.lock():
|
|
bar = self._currentbar()
|
|
if bar is not None:
|
|
bar._enginerenderer.clear()
|
|
yield
|
|
|
|
def resetstate(self):
|
|
with self.lock():
|
|
self._clear()
|
|
self._bars = []
|
|
self._currentbarindex = None
|
|
self._refresh = None
|
|
self._cond.notify_all()
|
|
|
|
def register(self, bar):
|
|
with self.lock():
|
|
self._activate(bar._ui)
|
|
now = time.time()
|
|
bar._enginestarttime = now
|
|
bar._enginerenderer = getrenderer(bar)
|
|
self._bars.append(bar)
|
|
self._recalculatedisplay(now)
|
|
global suspend
|
|
suspend = self._lockclear
|
|
# Do not redraw when registering a nested bar
|
|
if len(self._bars) <= 1:
|
|
self._cond.notify_all()
|
|
|
|
def unregister(self, bar):
|
|
with self.lock():
|
|
try:
|
|
index = self._bars.index(bar)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
if index == self._currentbarindex:
|
|
if index == 0:
|
|
self._complete()
|
|
else:
|
|
self._clear()
|
|
del self._bars[index:]
|
|
self._recalculatedisplay(time.time())
|
|
if not self._bars:
|
|
global suspend
|
|
suspend = util.nullcontextmanager
|
|
# Do not redraw when unregistering a nested bar
|
|
if len(self._bars) < 1:
|
|
self._cond.notify_all()
|
|
bar._enginerenderer = None
|
|
|
|
def _activate(self, ui):
|
|
with self.lock():
|
|
if not self._active:
|
|
self._active = True
|
|
self._thread = threading.Thread(target=self._run, name="progress")
|
|
self._thread.daemon = True
|
|
self._thread.start()
|
|
ui.atexit(self._deactivate)
|
|
|
|
def _deactivate(self):
|
|
if self._active:
|
|
with self.lock():
|
|
self._active = False
|
|
self._cond.notify_all()
|
|
self._thread.join()
|
|
|
|
def _run(self):
|
|
with self.lock():
|
|
while self._active:
|
|
self._cond.wait(self._refresh)
|
|
if self._active:
|
|
now = time.time()
|
|
self._recalculatedisplay(now)
|
|
self._updateestimation(now)
|
|
self._show(now)
|
|
|
|
def _show(self, now):
|
|
with self.lock():
|
|
bar = self._currentbar()
|
|
if bar is not None:
|
|
bar._enginerenderer.show(now)
|
|
|
|
def _clear(self):
|
|
with self.lock():
|
|
bar = self._currentbar()
|
|
if bar is not None:
|
|
bar._enginerenderer.clear()
|
|
|
|
def _complete(self):
|
|
with self.lock():
|
|
bar = self._currentbar()
|
|
if bar is not None:
|
|
if bar._ui.configbool("progress", "clear-complete"):
|
|
bar._enginerenderer.clear()
|
|
else:
|
|
bar._enginerenderer.complete()
|
|
|
|
def _currentbar(self):
|
|
if self._currentbarindex is not None:
|
|
return self._bars[self._currentbarindex]
|
|
return None
|
|
|
|
def _recalculatedisplay(self, now):
|
|
"""determine which bar should be displayed, if any"""
|
|
with self.lock():
|
|
if not self._bars:
|
|
self._currentbarindex = None
|
|
self._refresh = None
|
|
return
|
|
|
|
# Look to see if there is a new bar to show, or how long until
|
|
# another bar should be shown.
|
|
if self._currentbarindex is None:
|
|
nextbarindex = 0
|
|
newbarindex = None
|
|
else:
|
|
newbarindex = min(self._currentbarindex, len(self._bars) - 1)
|
|
nextbarindex = self._currentbarindex + 1
|
|
changetimes = []
|
|
for b in reversed(range(nextbarindex, len(self._bars))):
|
|
bar = self._bars[b]
|
|
if self._currentbarindex is None:
|
|
startdelay = bar._delay
|
|
else:
|
|
startdelay = bar._changedelay
|
|
if bar._enginestarttime + startdelay < now:
|
|
newbarindex = b
|
|
else:
|
|
changetimes.append(bar._enginestarttime + startdelay - now)
|
|
self._currentbarindex = newbarindex
|
|
|
|
# Update the refresh time.
|
|
bar = self._currentbar()
|
|
if bar is not None:
|
|
changetimes.append(bar._refresh)
|
|
if changetimes:
|
|
self._refresh = min(changetimes)
|
|
else:
|
|
self._refresh = None
|
|
|
|
def _updateestimation(self, now):
|
|
with self.lock():
|
|
for bar in self._bars:
|
|
bar._updateestimation(now)
|
|
|
|
|
|
_engine_pid = None
|
|
_engine = None
|
|
|
|
|
|
def getengine():
|
|
global _engine
|
|
global _engine_pid
|
|
pid = os.getpid()
|
|
if pid != _engine_pid:
|
|
_engine = engine()
|
|
_engine_pid = pid
|
|
return _engine
|
|
|
|
|
|
suspend = util.nullcontextmanager
|
|
|
|
|
|
def _progvalue(value):
|
|
"""split a progress bar value into a position and item"""
|
|
if isinstance(value, tuple):
|
|
return value
|
|
else:
|
|
return value, ""
|
|
|
|
|
|
class tracedbar(object):
|
|
"""base class for progress bars that generates tracing events"""
|
|
|
|
def __enter__(self):
|
|
spanid = _tracer.span(
|
|
[("name", "Progress Bar: %s" % self._topic), ("cat", "progressbar")]
|
|
)
|
|
_tracer.enter(spanid)
|
|
self._spanid = spanid
|
|
return self.enter()
|
|
|
|
def __exit__(self, exctype, excvalue, traceback):
|
|
spanid = self._spanid
|
|
total = getattr(self, "_total", None)
|
|
if total is not None:
|
|
_tracer.edit(spanid, [("total", str(total))])
|
|
_tracer.exit(spanid)
|
|
return self.exit(exctype, excvalue, traceback)
|
|
|
|
|
|
class normalbar(tracedbar):
|
|
"""context manager that adds a progress bar to slow operations
|
|
|
|
To use this, wrap a section of code that takes a long time like this:
|
|
|
|
with progress.bar(ui, "topic") as prog:
|
|
# processing code
|
|
prog.value = pos
|
|
# alternatively: prog.value = (pos, item)
|
|
"""
|
|
|
|
def __init__(self, ui, topic, unit="", total=None, start=0, formatfunc=None):
|
|
self._ui = ui
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self._start = start
|
|
self._formatfunc = formatfunc
|
|
self._delay = ui.configwith(float, "progress", "delay")
|
|
self._refresh = ui.configwith(float, "progress", "refresh")
|
|
self._changedelay = ui.configwith(float, "progress", "changedelay")
|
|
self._estimateinterval = ui.configwith(float, "progress", "estimateinterval")
|
|
self._estimatecount = max(20, int(self._estimateinterval))
|
|
self._estimatetick = self._estimateinterval / self._estimatecount
|
|
self._estimatering = util.ring(self._estimatecount)
|
|
|
|
def reset(self, topic, unit="", total=None):
|
|
with getengine().lock():
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self.value = self._start
|
|
self._estimatering = util.ring(self._estimatecount)
|
|
|
|
def _getestimatebounds(self):
|
|
if len(self._estimatering) < 2:
|
|
return None
|
|
else:
|
|
return self._estimatering[0], self._estimatering[-1]
|
|
|
|
def _updateestimation(self, now):
|
|
ring = self._estimatering
|
|
if len(ring) == 0 or ring[-1][1] + self._estimatetick <= now:
|
|
pos, _item = _progvalue(self.value)
|
|
ring.push((pos, now))
|
|
|
|
def enter(self):
|
|
self.value = self._start
|
|
getengine().register(self)
|
|
return self
|
|
|
|
def exit(self, type, value, traceback):
|
|
getengine().unregister(self)
|
|
|
|
|
|
class debugbar(tracedbar):
|
|
def __init__(self, ui, topic, unit="", total=None, start=0, formatfunc=None):
|
|
self._ui = ui
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self._start = start
|
|
self._formatfunc = formatfunc
|
|
self._started = False
|
|
|
|
def reset(self, topic, unit="", total=None):
|
|
if self._started:
|
|
self._ui.write(_x("progress: %s (reset)\n") % self._topic)
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self.value = self._start
|
|
self._started = False
|
|
|
|
def enter(self):
|
|
super(debugbar, self).__setattr__("value", self._start)
|
|
return self
|
|
|
|
def exit(self, type, value, traceback):
|
|
if self._started:
|
|
self._ui.write(_x("progress: %s (end)\n") % self._topic)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name == "value":
|
|
self._started = True
|
|
pos, item = _progvalue(value)
|
|
unit = (" %s" % self._unit) if self._unit else ""
|
|
item = (" %s" % item) if item else ""
|
|
if self._total:
|
|
pct = 100.0 * pos / self._total
|
|
self._ui.write(
|
|
_x("progress: %s:%s %d/%d%s (%4.2f%%)\n")
|
|
% (self._topic, item, pos, self._total, unit, pct)
|
|
)
|
|
else:
|
|
self._ui.write(
|
|
_x("progress: %s:%s %d%s\n") % (self._topic, item, pos, unit)
|
|
)
|
|
super(debugbar, self).__setattr__(name, value)
|
|
|
|
|
|
class nullbar(tracedbar):
|
|
"""A progress bar context manager that just keeps track of state."""
|
|
|
|
def __init__(self, ui, topic, unit="", total=None, start=0, formatfunc=None):
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self._start = start
|
|
self._formatfunc = formatfunc
|
|
|
|
def reset(self, topic, unit="", total=None):
|
|
self._topic = topic
|
|
self._unit = unit
|
|
self._total = total
|
|
self.value = self._start
|
|
|
|
def enter(self):
|
|
self.value = self._start
|
|
return self
|
|
|
|
def exit(self, type, value, traceback):
|
|
pass
|
|
|
|
|
|
def bar(ui, topic, unit="", total=None, start=0, formatfunc=None):
|
|
if ui.configbool("progress", "debug"):
|
|
return debugbar(ui, topic, unit, total, start, formatfunc)
|
|
elif (
|
|
ui.quiet
|
|
or ui.debugflag
|
|
or ui.configbool("progress", "disable")
|
|
or not shouldprint(ui)
|
|
):
|
|
return nullbar(ui, topic, unit, total, start, formatfunc)
|
|
else:
|
|
return normalbar(ui, topic, unit, total, start, formatfunc)
|
|
|
|
|
|
def spinner(ui, topic):
|
|
return bar(ui, topic, start=None)
|
|
|
|
|
|
def resetstate():
|
|
getengine().resetstate()
|