175 lines
5.4 KiB
Python
175 lines
5.4 KiB
Python
"""
|
|
https://github.com/AnnikaV9/lowbar
|
|
The simplest no-nonsense progress bar for python.
|
|
"""
|
|
|
|
import shutil
|
|
|
|
|
|
class lowbar:
|
|
"""
|
|
The main lowbar class.
|
|
"""
|
|
def __init__(self, tasks: range | int = 0, desc: str = "",
|
|
load_fill: str = "#", blank_fill: str = "-",
|
|
remove_ends: bool = False, keep_receipt: bool = False) -> None:
|
|
"""
|
|
Checks and initializes the bar with the given
|
|
parameters.
|
|
"""
|
|
if not isinstance(load_fill, str) or len(load_fill) != 1:
|
|
raise TypeError("arg load_fill should be a single char str")
|
|
|
|
if not isinstance(blank_fill, str) or len(blank_fill) != 1:
|
|
raise TypeError("arg blank_fill should be a single char str")
|
|
|
|
if not isinstance(desc, str):
|
|
raise TypeError("arg desc should be type str")
|
|
|
|
if not isinstance(remove_ends, bool):
|
|
raise TypeError("arg remove_ends should be type bool")
|
|
|
|
if not isinstance(keep_receipt, bool):
|
|
raise TypeError("arg keep_receipt should be type bool")
|
|
|
|
if not isinstance(tasks, range):
|
|
if not isinstance(tasks, int):
|
|
raise TypeError("arg tasks should be either type range or int")
|
|
|
|
tasks = range(tasks)
|
|
|
|
self.tasks = tasks
|
|
self.completion = 1
|
|
self.load_fill = load_fill
|
|
self.blank_fill = blank_fill
|
|
self.desc = desc
|
|
self.bar_ends = ("[", "]") if not remove_ends else (" ", " ")
|
|
self.keep_receipt = keep_receipt
|
|
|
|
def __enter__(self) -> object:
|
|
"""
|
|
Context manager setup to automatically display bar
|
|
without requiring new().
|
|
"""
|
|
self.new()
|
|
return self
|
|
|
|
def __exit__(self, *exc) -> None:
|
|
"""
|
|
Context manager exit to clear the bar automatically
|
|
without requiring clear().
|
|
"""
|
|
print() if self.keep_receipt else self.clear()
|
|
|
|
def __iter__(self) -> object:
|
|
"""
|
|
Iterable manager that automatically runs new() at the
|
|
start and clear() at the end
|
|
"""
|
|
self.new()
|
|
div = 100 / len(self.tasks)
|
|
add = div
|
|
try:
|
|
for item in self.tasks:
|
|
yield item
|
|
self.update(int(div))
|
|
div += add
|
|
|
|
finally:
|
|
print() if self.keep_receipt else self.clear()
|
|
|
|
def _print(self, text: str) -> None:
|
|
"""
|
|
Writes data to stdout
|
|
"""
|
|
print(text, end='', flush=True)
|
|
|
|
def _get_columns(self) -> int:
|
|
"""
|
|
Returns the number of columns in the running console.
|
|
"""
|
|
return shutil.get_terminal_size().columns
|
|
|
|
def _update_bar(self) -> None:
|
|
"""
|
|
Refreshes the current bar with new values, and a
|
|
possibly resized console.
|
|
"""
|
|
desc = self.desc if len(self.desc) + 20 < self._get_columns() else ""
|
|
label = f"{desc} {str(self.completion)}" if self.completion < 10 else f"{desc} {str(self.completion)}"
|
|
size = self._get_columns() - (len(label) + 7)
|
|
load_size = int(size * (self.completion / 100))
|
|
bar_blank_fill = size - load_size
|
|
self._print(f"\r{label} % {self.bar_ends[0]}{load_size * self.load_fill}{bar_blank_fill * self.blank_fill}{self.bar_ends[1]} ")
|
|
|
|
def _overwrite_bar(self, text: str = "") -> None:
|
|
"""
|
|
Overwrite the loading bar with optional text.
|
|
"""
|
|
overwrite = " " * (self._get_columns() - len(text))
|
|
self._print(f"\r{text}{overwrite}")
|
|
|
|
def update(self, percentage: int) -> None:
|
|
"""
|
|
Sets the current completion percentage.
|
|
"""
|
|
if not isinstance(percentage, int):
|
|
raise TypeError("arg percentage should be type int")
|
|
|
|
if percentage < 0 or percentage > 100:
|
|
raise ValueError("arg percentage out of range (0-100)")
|
|
|
|
self.completion = percentage
|
|
self._update_bar()
|
|
|
|
def add(self, percentage: int) -> None:
|
|
"""
|
|
Adds to the current completion percentage.
|
|
"""
|
|
if not isinstance(percentage, int):
|
|
raise TypeError("arg percentage should be type int")
|
|
|
|
self.completion += percentage
|
|
self.completion = 100 if self.completion > 100 else self.completion
|
|
self.completion = 0 if self.completion < 0 else self.completion
|
|
self._update_bar()
|
|
|
|
def next(self, tasks: int = 0) -> None:
|
|
"""
|
|
Automatically adds to the completion percentage based
|
|
on the number of tasks to be completed.
|
|
"""
|
|
if not isinstance(tasks, int):
|
|
raise TypeError("arg tasks should be type int")
|
|
|
|
if tasks < 1:
|
|
if not self.tasks:
|
|
raise RuntimeError("empty `next` was called without `tasks` being set in the constructor")
|
|
|
|
self.add(int(100 / len(self.tasks)))
|
|
return
|
|
|
|
self.add(int(100 / tasks))
|
|
|
|
def new(self) -> None:
|
|
"""
|
|
Alias for update(0)
|
|
"""
|
|
self.update(0)
|
|
|
|
def log(self, text: str) -> None:
|
|
"""
|
|
Log text to the console without affecting the bar.
|
|
"""
|
|
if not isinstance(text, str):
|
|
raise TypeError("arg text should be type str")
|
|
|
|
self._overwrite_bar(text)
|
|
print()
|
|
self._update_bar()
|
|
|
|
def clear(self) -> None:
|
|
"""
|
|
Clears the bar completely from the console.
|
|
"""
|
|
self._overwrite_bar()
|