Skip to content

Instantly share code, notes, and snippets.

@Leedehai
Last active June 4, 2020 23:09
Show Gist options
  • Save Leedehai/5e5128adf9ff5d374c50638a9f103076 to your computer and use it in GitHub Desktop.
Save Leedehai/5e5128adf9ff5d374c50638a9f103076 to your computer and use it in GitHub Desktop.
Colored progress bar in Python (tested on Linux/macOS)
#!/usr/bin/env python3
import os, sys
from enum import Enum, unique
from typing import Optional
@unique
class ProgressBarColor(Enum):
BLUE = "\x1b[38;5;75m"
PINK = "\x1b[38;5;203m"
YELLOW = "\x1b[38;5;223m"
GREEN = "\x1b[38;5;50m"
MAGENTA = "\x1b[38;5;219m"
MONO = "\x1b[38;5;251m"
@unique
class ProgressBarStyle(Enum):
THICK = "━"
THIN = "─"
BLOCK = "█"
DOT = "‧"
ARROW = ">"
PIPE = "|"
class ProgressBar:
color_lowkey = "\x1b[38;5;243m" # gray
def __init__(self,
max_tick: int,
*,
as_percent: bool = False,
color: ProgressBarColor = ProgressBarColor.BLUE,
style: ProgressBarStyle = ProgressBarStyle.THICK,
heading: str = "Progress"):
assert type(max_tick) == int and max_tick > 0
self.current_tick = -1
self.max_tick = max_tick
self.as_percent = as_percent
self.bar_color = color
self.bar_style = style
self.heading = heading
self.did_print = False
try:
self.terminal_width = os.get_terminal_size().columns
terminal_width_min = len(self.heading) \
+ 2 * len(str(self.max_tick)) + 5
if self.terminal_width <= terminal_width_min:
self.terminal_width = None
except (AttributeError, OSError):
self.terminal_width = None
self.tick()
def tick(self) -> None:
self.current_tick += 1
if self.as_percent:
numeric_repr = "%.1f%%/100%%" % (
self.current_tick * 1.0 / self.max_tick * 100)
else:
numeric_repr = "%d/%d" % (self.current_tick, self.max_tick)
bar_width = self.terminal_width \
- len(self.heading) - len(numeric_repr) - 10
bar_width = min(bar_width, 70)
if self.terminal_width == None:
self._print(self._compose_line(numeric_repr))
return
bar_width = min(70, self.terminal_width \
- len(self.heading) - len(numeric_repr) - 10)
if bar_width < 5:
self._print(self._compose_line(numeric_repr))
return
bar_content = self._make_bar(
bar_width, self.current_tick, self.max_tick)
s = self._compose_line(numeric_repr, bar_content)
self._print(s)
def _compose_line(self,
numeric_repr: str,
bar_content: Optional[str] = None) -> str:
if bar_content == None:
return "%s %s" % (self.heading, numeric_repr)
assert len(bar_content) > 0
return "%s %s %s" % (
ProgressBar._color_str(self.heading, ProgressBar.color_lowkey),
bar_content,
ProgressBar._color_str(numeric_repr, ProgressBar.color_lowkey))
def _make_bar(self, width: int, filled: int, total: int) -> str:
assert 0 <= filled <= total
filled_char_num = int(filled * 1.0 / total * width)
vacant_char_num = width - filled_char_num
filled_chars = self.bar_style.value * filled_char_num
vacant_chars = self.bar_style.value * vacant_char_num
return "%s%s" % (
ProgressBar._color_str(filled_chars, self.bar_color.value),
ProgressBar._color_str(vacant_chars, ProgressBar.color_lowkey))
@staticmethod
def _color_str(s: str, color_sequence: str) -> str:
return color_sequence + s + "\x1b[0m"
def _print(self, s: str) -> None:
if self.did_print and self.terminal_width:
cursor_up_and_clear = "\x1b[1A\x1b[2K"
else:
cursor_up_and_clear = ""
sys.stdout.write("%s%s\n" % (cursor_up_and_clear, s))
sys.stdout.flush()
self.did_print = True
if __name__ == "__main__":
import time, random
n = 20
bar_colors, bar_styles = list(ProgressBarColor), list(ProgressBarStyle)
for i in range(max(len(bar_colors), len(bar_styles))):
progress = ProgressBar(n, # The rest are optional arguments
as_percent=i % 2 == 0,
color=bar_colors[i % len(bar_colors)],
style=bar_styles[i % len(bar_styles)])
for _ in range(n - int(random.uniform(1, n))):
time.sleep(0.05)
progress.tick()
@Leedehai
Copy link
Author

Leedehai commented Jun 4, 2020

Screen Shot 2020-06-04 at 15 44 40

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment