initial working implementation of ProgressBar2
This commit is contained in:
parent
b691bb9b25
commit
3a0f77ab96
1 changed files with 157 additions and 132 deletions
289
core/text.py
289
core/text.py
|
@ -42,13 +42,21 @@ def lexical_join(words, oxford=False):
|
||||||
# --------------------------------------------------
|
# --------------------------------------------------
|
||||||
|
|
||||||
class _ProgressBarChannel:
|
class _ProgressBarChannel:
|
||||||
def __init__(self, unit, top, prefix_base2, precision):
|
def __init__(self, unit, top, precision, use_prefixes=True, prefix_base2=False, min_width_raw=0, min_width_rate=0, min_width_time=0, min_width_percent=7):
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.top = top
|
self.top = top
|
||||||
|
self.dynamic_top = top is None
|
||||||
self.prefix_base2 = prefix_base2
|
self.prefix_base2 = prefix_base2
|
||||||
self.precision = precision
|
self.precision = precision
|
||||||
|
self.use_prefixes = use_prefixes
|
||||||
|
self.min_width = {
|
||||||
|
"raw": min_width_raw,
|
||||||
|
"rate": min_width_rate,
|
||||||
|
"percent": min_width_percent,
|
||||||
|
"time": min_width_time
|
||||||
|
}
|
||||||
|
|
||||||
self.value = 0
|
self._value = 0
|
||||||
self.set()
|
self.set()
|
||||||
|
|
||||||
if prefix_base2:
|
if prefix_base2:
|
||||||
|
@ -56,11 +64,42 @@ class _ProgressBarChannel:
|
||||||
|
|
||||||
def set(self, value=None):
|
def set(self, value=None):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.value = value
|
self._value = value
|
||||||
|
|
||||||
self.ratio = self.value / self.top
|
self.ratio = self._value / self.top if self.top else None
|
||||||
s = Unit(self.value, self.unit, format="%.{:d}f pU".format(self.precision))
|
|
||||||
self.text = str(s).split(" ", 1)
|
def _render(self, value, unit=None, just=None):
|
||||||
|
if unit is None:
|
||||||
|
unit = self.unit
|
||||||
|
|
||||||
|
u = Unit(value, unit, format="%.{:d}f pU".format(self.precision))
|
||||||
|
|
||||||
|
if not self.use_prefixes or just == "percent":
|
||||||
|
u._prefixes = (('', 0),) # Unit needs fixin'
|
||||||
|
|
||||||
|
s = str(u)
|
||||||
|
|
||||||
|
if just:
|
||||||
|
s = s.rjust(self.min_width[just])
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._render(self._value, just="raw")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inverse(self):
|
||||||
|
return self._render(self.top - self._value, just="raw")
|
||||||
|
|
||||||
|
def divide(self, amount):
|
||||||
|
"""
|
||||||
|
Divides the amount into amount * ratio, amount * (1 - ratio) and rounds to integers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
A = round(amount * self.ratio)
|
||||||
|
|
||||||
|
return (A, amount - A)
|
||||||
|
|
||||||
class ProgressBar2:
|
class ProgressBar2:
|
||||||
"""
|
"""
|
||||||
|
@ -68,7 +107,7 @@ class ProgressBar2:
|
||||||
|
|
||||||
Each atom consists of three characters: a literal §, an operator and a channel.
|
Each atom consists of three characters: a literal §, an operator and a channel.
|
||||||
An operator is one of:
|
An operator is one of:
|
||||||
% - a percentage (value from 0 to 100, padded to three characters)
|
% - a percentage (value from 0 to 100, padded with spaces to three characters)
|
||||||
r - displays the raw value of the channel
|
r - displays the raw value of the channel
|
||||||
z - displays the maximum (total) value
|
z - displays the maximum (total) value
|
||||||
s - displays the rate of change in units/s
|
s - displays the rate of change in units/s
|
||||||
|
@ -80,7 +119,7 @@ class ProgressBar2:
|
||||||
at 100% it fills the entirety of the remaining space
|
at 100% it fills the entirety of the remaining space
|
||||||
|
|
||||||
A channel ID as a lowercase letter is the channel's actual value. An uppercase letter
|
A channel ID as a lowercase letter is the channel's actual value. An uppercase letter
|
||||||
is that channel's complement.
|
is that channel's complement. Operations z, s, m and h cannot have a complement.
|
||||||
|
|
||||||
Use §§ to display a literal §.
|
Use §§ to display a literal §.
|
||||||
|
|
||||||
|
@ -93,7 +132,7 @@ class ProgressBar2:
|
||||||
Output: 53 % (21.0/39.6 kf) [---------------------> ] 27.1 f/s [233/439 Mio] (ETA 00:11:26)
|
Output: 53 % (21.0/39.6 kf) [---------------------> ] 27.1 f/s [233/439 Mio] (ETA 00:11:26)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, format, channels, width=None, space_before_unit=True):
|
def __init__(self, format, channels, width=None):
|
||||||
"""
|
"""
|
||||||
Initialize the ProgressBar.
|
Initialize the ProgressBar.
|
||||||
|
|
||||||
|
@ -130,8 +169,7 @@ class ProgressBar2:
|
||||||
|
|
||||||
self.format = format
|
self.format = format
|
||||||
self.channels = {id: _ProgressBarChannel(**conf) for id, conf in channels.items()}
|
self.channels = {id: _ProgressBarChannel(**conf) for id, conf in channels.items()}
|
||||||
self.space_before_unit = space_before_unit
|
self.time_start = None
|
||||||
self.time_start = time.time()
|
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
||||||
def set(self, channel_id, value):
|
def set(self, channel_id, value):
|
||||||
|
@ -141,6 +179,11 @@ class ProgressBar2:
|
||||||
|
|
||||||
c = self.channels[channel_id]
|
c = self.channels[channel_id]
|
||||||
c.set(value)
|
c.set(value)
|
||||||
|
|
||||||
|
if c.ratio is not None:
|
||||||
|
for channel in self.channels.values():
|
||||||
|
if channel.dynamic_top:
|
||||||
|
channel.top = channel._value / c.ratio
|
||||||
|
|
||||||
def render(self, clear=True, stream=sys.stderr):
|
def render(self, clear=True, stream=sys.stderr):
|
||||||
"""
|
"""
|
||||||
|
@ -149,18 +192,83 @@ class ProgressBar2:
|
||||||
|
|
||||||
width = self.width or get_terminal_size()[1]
|
width = self.width or get_terminal_size()[1]
|
||||||
raw = self.format
|
raw = self.format
|
||||||
|
t = time.time()
|
||||||
|
|
||||||
for tag in re.findall("§.[a-zA-Z]", raw):
|
if not self.time_start:
|
||||||
|
self.time_start = t
|
||||||
|
|
||||||
|
for tag in re.findall("§[%rzsmhtT][a-zA-Z]", raw):
|
||||||
op = tag[1]
|
op = tag[1]
|
||||||
ch = tag[2]
|
ch = tag[2]
|
||||||
|
channel = self.channels[ch.lower()]
|
||||||
inverse = ch.isupper()
|
inverse = ch.isupper()
|
||||||
|
|
||||||
|
if op == "%":
|
||||||
|
ratio = 1 - channel.ratio if inverse else channel.ratio
|
||||||
|
|
||||||
|
raw = raw.replace(tag, channel._render(ratio * 100, "%", "percent"), 1)
|
||||||
|
|
||||||
|
elif op == "r": # display the current value
|
||||||
|
raw = raw.replace(tag, channel.inverse if inverse else channel.value, 1)
|
||||||
|
|
||||||
|
elif op == "z": # display the top value
|
||||||
|
if inverse:
|
||||||
|
raise ValueError("inverse of the top value is not defined: {:s}".format(tag))
|
||||||
|
|
||||||
|
if channel.top:
|
||||||
|
s = channel._render(channel.top, just="raw")
|
||||||
|
else:
|
||||||
|
s = "- {:s}".format(channel.unit).rjust(channel.min_width["raw"])
|
||||||
|
|
||||||
|
raw = raw.replace(tag, s, 1)
|
||||||
|
|
||||||
|
elif op.lower() == "t":
|
||||||
|
t_elapsed = t - self.time_start
|
||||||
|
t_to_end = (t_elapsed * (channel.top - channel._value) / channel._value) if channel._value > 0 else None
|
||||||
|
|
||||||
|
raw = raw.replace(tag, self._render_time(t_to_end if inverse else t_elapsed, op.isupper(), channel.min_width["time"]), 1)
|
||||||
|
|
||||||
|
elif op in "smh":
|
||||||
|
if inverse:
|
||||||
|
raise ValueError("the inverse of rates is not defined: {:s}".format(tag))
|
||||||
|
|
||||||
|
t_elapsed = t - self.time_start
|
||||||
|
|
||||||
|
if op == "s":
|
||||||
|
t_unit = "s"
|
||||||
|
t_div = 1
|
||||||
|
elif op == "m":
|
||||||
|
t_unit = "min"
|
||||||
|
t_div = 60
|
||||||
|
elif op == "h":
|
||||||
|
t_unit = "h"
|
||||||
|
t_div = 3600
|
||||||
|
|
||||||
|
if t_elapsed > 0:
|
||||||
|
rate = t_div * channel._value / t_elapsed
|
||||||
|
s = channel._render(rate, channel.unit + "/" + t_unit, "rate")
|
||||||
|
else:
|
||||||
|
s = "- {:s}/{:s}".format(channel.unit, t_unit).rjust(channel.min_width["rate"])
|
||||||
|
|
||||||
|
raw = raw.replace(tag, s, 1)
|
||||||
|
|
||||||
# display literal §s
|
# all that remains are bars
|
||||||
|
free_space = width - len(re.sub("§[^%rzsmhtT][a-zA-Z]", "", raw))
|
||||||
|
|
||||||
|
for tag in re.findall("§[^%rzsmhtT][a-zA-Z]", raw):
|
||||||
|
op = tag[1]
|
||||||
|
ch = tag[2]
|
||||||
|
channel = self.channels[ch.lower()]
|
||||||
|
lengths = channel.divide(free_space)
|
||||||
|
|
||||||
|
raw = raw.replace(tag, op * lengths[1 if ch.isupper() else 0], 1)
|
||||||
|
|
||||||
|
# render literal §s
|
||||||
raw = raw.replace("§§", "§")
|
raw = raw.replace("§§", "§")
|
||||||
|
|
||||||
# pave over previous render and draw the new one
|
# pave over previous render and draw the new one
|
||||||
if clear:
|
if clear:
|
||||||
stream.write("\r" + "_" * width)
|
stream.write("\r" + " " * width)
|
||||||
|
|
||||||
stream.write("\r" + raw)
|
stream.write("\r" + raw)
|
||||||
|
|
||||||
|
@ -178,6 +286,20 @@ class ProgressBar2:
|
||||||
self.render()
|
self.render()
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
def _render_time(self, seconds, hhmmss=False, just=0):
|
||||||
|
if hhmmss:
|
||||||
|
if seconds is None:
|
||||||
|
output = "--:--:--"
|
||||||
|
else:
|
||||||
|
mm, ss = divmod(int(seconds), 60)
|
||||||
|
hh, mm = divmod(mm, 60)
|
||||||
|
output = "{:02d}:{:02d}:{:02d}".format(hh, mm, ss)
|
||||||
|
|
||||||
|
else:
|
||||||
|
output = "- s" if seconds is None else "{:d} s".format(int(seconds))
|
||||||
|
|
||||||
|
return output.rjust(just)
|
||||||
|
|
||||||
class ProgressBar:
|
class ProgressBar:
|
||||||
'''
|
'''
|
||||||
|
@ -202,6 +324,8 @@ class ProgressBar:
|
||||||
|
|
||||||
self.old_len = 0
|
self.old_len = 0
|
||||||
self.t_start = None
|
self.t_start = None
|
||||||
|
|
||||||
|
_print("over.core.text.ProgressBar is deprecated and will be replaced by ProgressBar2 soon", prefix.warn)
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
if not self.t_start:
|
if not self.t_start:
|
||||||
|
@ -401,116 +525,6 @@ def paragraph(text, width=0, indent=0, prefix=None, stamp=None):
|
||||||
|
|
||||||
return '\n'.join(lines_tmp) # put lines together
|
return '\n'.join(lines_tmp) # put lines together
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
# def strtrim(text, length, mode=0, dots=True, colors=True):
|
|
||||||
# '''
|
|
||||||
# str text text to trim
|
|
||||||
# int length desired length, >1
|
|
||||||
# int mode -1 = cut chars from the left, 0 = from the middle, 1 = from the right
|
|
||||||
# bool dots add an ellipsis to the point of cutting
|
|
||||||
# bool colors dont count color tags into length; also turns the ellipsis blue
|
|
||||||
# '''
|
|
||||||
|
|
||||||
# if len(text) <= length:
|
|
||||||
# return text
|
|
||||||
|
|
||||||
# if length <= 3:
|
|
||||||
# dots = False
|
|
||||||
|
|
||||||
# if dots:
|
|
||||||
# length -= 3
|
|
||||||
|
|
||||||
# if mode == -1:
|
|
||||||
# if dots:
|
|
||||||
# return '...' + cut(text, length, 1, colors)
|
|
||||||
# else:
|
|
||||||
# return cut(text, length, 1, colors)
|
|
||||||
|
|
||||||
# elif mode == 0:
|
|
||||||
# if length%2 == 1:
|
|
||||||
# part1 = cut(text, length/2+1, 0, colors)
|
|
||||||
# else:
|
|
||||||
# part1 = cut(text, length/2, 0, colors)
|
|
||||||
|
|
||||||
# part2 = cut(text, length/2, 1, colors)
|
|
||||||
|
|
||||||
# if dots:
|
|
||||||
# part1 += '...'
|
|
||||||
|
|
||||||
# return part1 + part2
|
|
||||||
|
|
||||||
# else:
|
|
||||||
# if dots:
|
|
||||||
# return cut(text, length, 0, colors) + '...'
|
|
||||||
# else:
|
|
||||||
# return cut(text, length, 0, colors)
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
# def textfilter(text, set=None):
|
|
||||||
# '''
|
|
||||||
# str text text to filter
|
|
||||||
# dict set {to_replace: replacement}
|
|
||||||
|
|
||||||
# Text filter that replaces occurences of to_replace keys with their respective values.
|
|
||||||
|
|
||||||
# Defaults to filtering of 'bad' characters if no translational dictionary is provided.
|
|
||||||
# '''
|
|
||||||
|
|
||||||
# if not set:
|
|
||||||
# set = badchars
|
|
||||||
|
|
||||||
# for to_replace in set.keys():
|
|
||||||
# text = text.replace(to_replace, set[to_replace])
|
|
||||||
|
|
||||||
# return text
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
# def cut(text, size, end=0, colors=True):
|
|
||||||
# '''
|
|
||||||
# str text text to cut
|
|
||||||
# int size how many chars to return, >=0
|
|
||||||
# int end return chars from 0 = left, 1 = right
|
|
||||||
# bool colors skip color tags
|
|
||||||
# '''
|
|
||||||
|
|
||||||
# out = ''
|
|
||||||
|
|
||||||
# strlen = len(text)
|
|
||||||
|
|
||||||
# if end == 0:
|
|
||||||
# ptr = 0 # go from left
|
|
||||||
# else:
|
|
||||||
# ptr = strlen - 1
|
|
||||||
|
|
||||||
# while size:
|
|
||||||
# if end == 0:
|
|
||||||
# if colors and text[ptr] == '<' and strlen - ptr >= 4 and text[ptr+1] == 'C' and text[ptr+3] == '>': # we have a tag
|
|
||||||
# out += text[ptr:ptr+4]
|
|
||||||
# ptr += 4
|
|
||||||
# else:
|
|
||||||
# out += text[ptr]
|
|
||||||
# ptr += 1
|
|
||||||
# size -= 1
|
|
||||||
# else:
|
|
||||||
# if colors and text[ptr] == '>' and ptr >= 4 and text[ptr-2] == 'C' and text[ptr-3] == '<': # we have a tag
|
|
||||||
# out = text[ptr-3:ptr+1] + out
|
|
||||||
# ptr -= 4
|
|
||||||
# else:
|
|
||||||
# out = text[ptr] + out
|
|
||||||
# ptr -= 1
|
|
||||||
# size -= 1
|
|
||||||
|
|
||||||
# # reached end ?
|
|
||||||
# if (end == 0 and ptr == strlen) or (end == 1 and ptr == -1):
|
|
||||||
# break
|
|
||||||
|
|
||||||
# return out
|
|
||||||
|
|
||||||
# --------------------------------------------------
|
|
||||||
|
|
||||||
class prefix:
|
class prefix:
|
||||||
info = (' ', 'INFO')
|
info = (' ', 'INFO')
|
||||||
debug = (' §b?§/', '§bDEBG§/')
|
debug = (' §b?§/', '§bDEBG§/')
|
||||||
|
@ -659,25 +673,36 @@ _print = Output('over.core.text', stream=sys.stderr)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pb = ProgressBar2(
|
pb = ProgressBar2(
|
||||||
"§%a (§ra/§ma) [§-a>§ A] §sa [§rb/§rB] (ETA §tA)",
|
"§%a (§ra/§za) [§=a>§ A] §sa [§rb/§zb] (ETA §TA)",
|
||||||
{
|
{
|
||||||
"a": {
|
"a": {
|
||||||
"unit": "f",
|
"unit": "f",
|
||||||
"prefix_base2": False,
|
"prefix_base2": False,
|
||||||
"top": 39600,
|
"top": 39600,
|
||||||
"precision": 1
|
"precision": 1,
|
||||||
|
"min_width_raw": 10,
|
||||||
|
"min_width_rate": 10,
|
||||||
|
# "min_width_percent": 7, # 7 is already the default
|
||||||
|
"min_width_time": 0
|
||||||
},
|
},
|
||||||
"b": {
|
"b": {
|
||||||
"unit": "o",
|
"unit": "o",
|
||||||
"prefix_base2": True,
|
"prefix_base2": True,
|
||||||
"top": 3200,
|
"top": 3200,
|
||||||
"precision": 0
|
"precision": 0,
|
||||||
|
"min_width_raw": 10,
|
||||||
|
"min_width_rate": 10,
|
||||||
|
"min_width_time": 10
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
80
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pb.set("a", 12000)
|
ratios = [0, 0.1, 0.25, 0.4, 0.6, 0.8, 0.95, 1.0]
|
||||||
pb.set("b", 1500)
|
|
||||||
pb.render()
|
for ratio in ratios:
|
||||||
|
pb.set("a", ratio * pb.channels["a"].top)
|
||||||
|
pb.set("b", ratio * pb.channels["b"].top)
|
||||||
|
pb.render()
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
pb.end(not True)
|
pb.end(not True)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue