Issue with term cols and stderr progress bar when piping stdout to file
Yomguithereal opened this issue · comments
Hello @rsalmei, thanks for your awesome library.
I have a little issue when printing the progress bar using file=sys.stderr
and piping the stdout of a script into a file. Here is a script reproducing the problem (note that the terminal has to be ~130 cols wide for it to show):
import time
import os
import sys
import shutil
from alive_progress import alive_bar
import patch
total = 1000
stdout_cols = shutil.get_terminal_size()[0]
stderr_cols = os.get_terminal_size(sys.stderr.fileno())[0]
print("Cols of stdout", stdout_cols)
print("Cols of stderr", stderr_cols)
print("-" * stderr_cols)
with alive_bar(
total,
title="Processing range",
file=sys.stderr,
ctrl_c=False,
unit="apple",
) as bar:
for i in range(1000):
time.sleep(0.005)
bar()
If you run:
python example.py
everything works fine. But if you run:
python example.py > log.txt
then the progress bar will be erroneously truncated.
This happens because alive_progress
always uses stdout cols to print itself, and when piped to a file, shutil.get_terminal_size
always return some default desirable value (usually around 80). Related code can be found here.
Now if I monkey-patch your library using the following code:
import os
from types import SimpleNamespace
def patched_new(original):
write = original.write
flush = original.flush
def cols():
# more resilient one, although 7x slower than os' one.
# HERE: only line changed HERE #
return os.get_terminal_size(original.fileno())[0]
def _ansi_escape_sequence(code, param=""):
def inner(_available=None): # because of jupyter.
write(inner.sequence)
inner.sequence = f"\x1b[{param}{code}"
return inner
def factory_cursor_up(num):
return _ansi_escape_sequence("A", num) # sends cursor up: CSI {x}A.
clear_line = _ansi_escape_sequence(
"2K\r"
) # clears the entire line: CSI n K -> with n=2.
clear_end_line = _ansi_escape_sequence("K") # clears line from cursor: CSI K.
clear_end_screen = _ansi_escape_sequence("J") # clears screen from cursor: CSI J.
hide_cursor = _ansi_escape_sequence("?25l") # hides the cursor: CSI ? 25 l.
show_cursor = _ansi_escape_sequence("?25h") # shows the cursor: CSI ? 25 h.
carriage_return = "\r"
return SimpleNamespace(**locals())
import alive_progress.utils.terminal.tty as m
m.new = patched_new
it fixes the problem.
Of course my code is naive and should be a little bit more subtle, especially when dealing with files that do not have a fileno
etc. and I don't know if this kind of solution would break something else. But if you feel this is a good fix, I can submit a PR.
I wish you a good evening
Another solution would also be to add some kwarg letting users indicate how many cols they want because they might know better.
Hello @Yomguithereal! Sorry for the delay.
Thanks, man! That's really a bug, I've never thought about grabbing the terminal size with a different handle.
I'll fix it as soon as I can.
Hey @Yomguithereal, it is ready!
The PR is #231, I'm glad I could find some time to work on this.
Released 👍
Thanks @rsalmei