Last active
November 16, 2021 21:09
-
-
Save tremby/65edb50e94311f15360c9926b8d955da to your computer and use it in GitHub Desktop.
Progress bar for anything
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Get a progress bar for anything. | |
Feed this script a line containing two numbers on standard input as often or | |
infrequently as you like. | |
It will give you a progress bar and, once at least two readings have been taken, | |
estimated time remaining. | |
Example for rough progress on a long copy operation, where all files are roughly | |
equal: | |
total=$(find src-dir -type f | wc -l); while sleep 5; do echo $(find dest-dir -type f | wc l) $total; done | python3 progress.py | |
""" | |
from sys import stdin, stdout | |
from collections import deque | |
from datetime import datetime, timedelta | |
from shutil import get_terminal_size | |
READINGS_MAX = 10 | |
latest = deque([]) | |
for line in stdin: | |
# Get status | |
try: | |
current, total = [int(x) for x in line.split()] | |
except ValueError: | |
stdout.write("Expected two whitespace-separated values: current, total\n") | |
continue | |
dt = datetime.now() | |
progress = current / total | |
# How long is the total | |
totalChars = len(str(total)) | |
# Calculate remaining time if possible | |
remainingTime = None | |
if len(latest): | |
refDt, refCurrent, refTotal = latest.popleft() if len(latest) >= READINGS_MAX else latest[0] | |
todo = total - current | |
elapsed = dt - refDt | |
done = current - refCurrent | |
if done == 0: | |
remainingTime = "stalled" | |
else: | |
remainingTime = todo * elapsed / done | |
# Round to seconds | |
remainingTime -= timedelta(microseconds=remainingTime.microseconds) | |
# Prepare status | |
line = f"{current:>{totalChars}}/{total}: {int(progress*100):3d}% []" | |
# Add remaining time if we have it | |
if remainingTime is not None: | |
line += f" {remainingTime}" | |
# Add the progress bar | |
remainingWidth = get_terminal_size((80, 20))[0] - len(line) | |
bar = "=" * int(progress * remainingWidth) | |
line = line.replace("[]", f"[{bar:{remainingWidth}}]") | |
# Clear line and write to output | |
stdout.write("%c[2K\r" % 27) | |
stdout.write(line) | |
# Flush output | |
stdout.flush() | |
# Remember this progress so we can make time predictions | |
latest.append((dt, current, total)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment