Skip to content

Commit bbb4d27

Browse files
authored
Merge pull request #21 from tmck-code/pbar-summary-info
Pbar summary info
2 parents fbc779d + 1003a38 commit bbb4d27

4 files changed

Lines changed: 63 additions & 15 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pypi/clean:
2-
rm -rf build/ dist/ *.egg-info
2+
rm -rfv build/ dist/ *.egg-info
33

44
pypi/build: pypi/clean
55
uv build

laser_prynter/pbar.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(self, total: int, c1: RGB = DEFAULT_C1, c2: RGB = DEFAULT_C2):
4343
self.c1, self.c2 = c1, c2
4444
self.curr = 0
4545
self.progress = 0 # Track logical progress out of total
46+
self.start_time = time.time()
4647
if self.t > self.w:
4748
self.g = list(interp_xyz(c1, c2, self.t + 1))
4849
else:
@@ -52,10 +53,11 @@ def __init__(self, total: int, c1: RGB = DEFAULT_C1, c2: RGB = DEFAULT_C2):
5253

5354
@staticmethod
5455
def randgrad() -> tuple[RGB, RGB]:
55-
return (
56-
RGB(randint(0, 255), randint(0, 255), randint(0, 255)),
57-
RGB(randint(0, 255), randint(0, 255), randint(0, 255)),
56+
rgb1 = RGB(randint(0, 255), randint(0, 255), randint(0, 255))
57+
rgb2 = RGB(
58+
(rgb1.r + (255 // 2)) % 255, (rgb1.g + (255 // 2)) % 255, (rgb1.b + (255 // 2)) % 255
5859
)
60+
return (rgb1, rgb2)
5961

6062
def _pbar(self) -> Iterator[tuple[tuple[int, RGB]]]:
6163
for x in range(self.w + 1): # TODO: i'm so dumb, why do I need a +1 here?
@@ -74,17 +76,56 @@ def __iter__(self) -> PBar:
7476
def _true_colour(rgb: RGB) -> str:
7577
return f'\x1b[48;2;{rgb.r};{rgb.g};{rgb.b}m'
7678

79+
@staticmethod
80+
def _format_time(seconds: float) -> str:
81+
'Format seconds into human-readable time string.'
82+
83+
if seconds < 60:
84+
isec, fsec = divmod(seconds, 1)
85+
return f'{int(isec)}.{int(fsec * 100):02d}'
86+
elif seconds < 3600:
87+
isec, fsec = divmod(seconds, 60)
88+
return f'{int(isec)}:{fsec:05.2f}'
89+
else:
90+
hours, rem = divmod(seconds, 3600)
91+
mins, secs = divmod(rem, 60)
92+
return f'{int(hours)}:{int(mins)}:{secs:05.2f}'
93+
94+
def _print_info(self) -> None:
95+
'Print progress info in the line above the bar.'
96+
97+
elapsed = time.time() - self.start_time
98+
99+
if self.progress == 0:
100+
eta_str = '??:??'
101+
else:
102+
eta_str = self._format_time((elapsed / self.progress) * (self.t - self.progress))
103+
104+
pct = (self.progress / self.t) * 100
105+
item_info = f'[\x1b[1;32m{self.progress}\x1b[0m/{self.t}] \x1b[1;97m{pct:.1f}%\x1b[0m'
106+
time_info = f'\x1b[92m+{self._format_time(elapsed)}\x1b[0m \x1b[93m-{eta_str}\x1b[0m'
107+
108+
# Clear the line and print info above the progress bar
109+
_print_to_terminal(
110+
'\x1b7' # save cursor position
111+
f'\x1b[{self.h - 1};0H' # move to line above bar
112+
'\x1b[2K' # clear entire line
113+
f'{item_info} | {time_info}'
114+
'\x1b8' # restore cursor position
115+
)
116+
77117
def _print_bar_chars(self, s: str) -> None:
78118
_print_to_terminal(
79119
'\x1b7' # save cursor position
80120
f'\x1b[{self.h};{self.curr}H' # move to bottom line
81-
f'{s}' # the "bar" characters
121+
f'{s}' # the 'bar' characters
82122
'\x1b[0m' # reset color
83123
'\x1b8' # restore cursor position
84124
)
85125

86126
def _initial_bar(self) -> None:
87-
"print initial bar in end color"
127+
'print initial bar in end color'
128+
88129
self._print_bar_chars(
89130
f'\x1b[48;2;{self.c2.r};{self.c2.g};{self.c2.b}m' + ' ' * self.w + '\x1b[0m'
90131
)
@@ -98,7 +139,8 @@ def __next__(self) -> None:
98139
raise StopIteration
99140

100141
def update(self, n: int) -> None:
101-
"update the progress bar by n steps"
142+
'update the progress bar by n steps'
143+
102144
self.progress = min(self.progress + n, self.t)
103145
# Calculate target terminal position based on logical progress
104146
target_pos = int((self.progress / (self.t)) * self.w)
@@ -108,16 +150,21 @@ def update(self, n: int) -> None:
108150
next(self)
109151
except StopIteration:
110152
break
153+
# Update progress info
154+
self._print_info()
111155

112156
def __enter__(self) -> PBar:
157+
self.start_time = time.time()
113158
_print_to_terminal(
114-
'\n' # ensure space for scrollbar
159+
'\x1b[?25l' # hide cursor
160+
'\n\n' # ensure space for info line and scrollbar
115161
'\x1b7' # save cursor position
116-
f'\x1b[0;{self.h - 1}r' # set top & bottom regions (margins)
162+
f'\x1b[0;{self.h - 2}r' # set top & bottom regions (margins) - reserve 2 lines
117163
'\x1b8' # restore cursor position
118-
'\x1b[1A' # move cursor up
164+
'\x1b[2A' # move cursor up 2 lines
119165
)
120166
self._initial_bar()
167+
self._print_info()
121168
return self
122169

123170
def __exit__(self, _exc_type: type, _exc_val: BaseException, _exc_tb: type) -> None:
@@ -128,6 +175,7 @@ def __exit__(self, _exc_type: type, _exc_val: BaseException, _exc_tb: type) -> N
128175
except StopIteration:
129176
break
130177
_print_to_terminal(
178+
'\x1b[?25h' # show cursor
131179
f'\x1b[0;{self.h}r' # reset margins
132180
f'\x1b[{self.h};0H' # move to bottom line
133181
'\n'
@@ -136,8 +184,8 @@ def __exit__(self, _exc_type: type, _exc_val: BaseException, _exc_tb: type) -> N
136184

137185

138186
if __name__ == '__main__':
139-
with PBar(100, *PBar.randgrad()) as pbar:
140-
for i in range(100):
141-
time.sleep(0.01)
187+
with PBar(200, *PBar.randgrad()) as pbar:
188+
for i in range(200):
189+
time.sleep(randint(int(0.01 * 100), int(0.1 * 100)) / 100)
142190
print(f'-> {i}')
143191
pbar.update(1)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "laser-prynter"
3-
version = "0.4.4"
3+
version = "0.5.0"
44
authors = [{ name = "tmck-code", email = "tmck01@gmail.com" }]
55
description = "terminal/cli/python helpers for colour and pretty-printing"
66
readme = "README.md"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)