I was wondering why isn’t there a pager in one of the packages of the standard library. I know, there’s pydoc.pager(), but that’s not really a pager in itself it uses more or less, that’s great but if there isn’t more or less for some reasons on the system, it’s quite complex for the user because it just don’t use a pager at all.
So I made a pager, simple though but fonctional and fully in Python, with no need to thing external to Python.
I know that my code can’t be like that directly in the standard library and it won’t probably be its own package, but it will be a good fundation I think.
Here’s my code:
from shutil import get_terminal_size
import sys
# Enable ANSI escape codes on Windows
if sys.platform.startswith('win'):
if sys.stdout.isatty():
import ctypes
kernel32 = ctypes.windll.kernel32
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 0x0007)
# Main logic
def pager(text: str) -> None:
"""Display a given text in a pager.
Args:
text (str): The text to be displayed in the pager.
Raises:
TypeError: If the input text is not a string.
KeyboardInterrup: When the user presses Ctrl-C when prompting for the character
"""
if not isinstance(text, str):
raise TypeError('text arg must be str')
# Get the character typed by the user
def get_key() -> str:
"""Get the character typed by the user.
Returns:
str: The character typed by the user.
"""
if sys.platform.startswith('win'):
import msvcrt
return msvcrt.getch().decode()
else:
import tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
print('\033c', end='', flush=True) # Clear the terminal
lines = text.split('\n')
rows, _ = get_terminal_size((35, 148))
prompt = '\033[7m-- More (press \'q\' to exit) --\033[0m'
for idx, line in enumerate(lines):
if idx >= (rows - 1):
print(prompt, end='', flush=True)
key = get_key()
# Fully remove the prompt before printing the line
print('\r' + len(prompt) * ' ' + '\r', end='', flush=True)
if key == '\x03': # Ctrl-C
raise KeyboardInterrupt
if key.lower() == 'q':
break
print(line)
else:
print(line)
Following the source code of pydoc.pager(), a system without the environment variable MANPAGER or PAGER and with the environment variable TERM equal to dumb or emacs.
It’s possible to don’t have it. And sometimes, like on Windows, the pipe is broken. If you try to pipe something to more in Windows, it will display one character by line.
Also some lightweight Linux distros don’t have more or less, and most of custom OSes also.
And having a platform-independent pager for Python programs easy to access can be good and easier.
Not all Windows systems support ANSI terminal codes. Are you willing to support those? What about feature enhancements like searching or scrolling backwards? Or truncating long lines?
If someone were to add a pure Python pager implementation to the stdlib, it would need to handle these types of questions (and if the initial implementation didn’t offer support, the core devs would have to handle the inevitable issues and feature requests). I don’t think this is likely, particularly when piping to the system pager is available already.
In fact Windows supports ANSI escape codes via the ansi.sys driver, and for example colorama provide a good implementation for using them on Windows, but I made a simpler code with only standard library for fundation, I know this isn’t complete.
Do you propose to create a more complete implementation yourself, or are you hoping that someone else will do that? If you do intend to do so, I’d suggest working on the full implementation. If you do that as a package on PyPI, taking care not to use any dependencies outside of the stdlib, you’ll (a) get a better idea of the difficulty of implementing and supporting this, and (b) get a sense of how much interest there would be in having this in the stdlib.
I remade it almost from 0 (I kept only the main part), now it’s better. It’s dispatched between multiple files for better reusability of the code and better readability. The main part works well, and I managed to make it go on a newline when the line is too long. Now I need to check if the backward scroll work well and after I’ll had to add the search and make the ANSI codes work on Windows.