| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | from __future__ import annotations | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import io | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # types | 
					
						
							|  |  |  | if False: | 
					
						
							|  |  |  |     from typing import Protocol, Any | 
					
						
							|  |  |  |     class Pager(Protocol): | 
					
						
							|  |  |  |         def __call__(self, text: str, title: str = "") -> None: | 
					
						
							|  |  |  |             ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_pager() -> Pager: | 
					
						
							|  |  |  |     """Decide what method to use for paging through text.""" | 
					
						
							|  |  |  |     if not hasattr(sys.stdin, "isatty"): | 
					
						
							|  |  |  |         return plain_pager | 
					
						
							|  |  |  |     if not hasattr(sys.stdout, "isatty"): | 
					
						
							|  |  |  |         return plain_pager | 
					
						
							|  |  |  |     if not sys.stdin.isatty() or not sys.stdout.isatty(): | 
					
						
							|  |  |  |         return plain_pager | 
					
						
							|  |  |  |     if sys.platform == "emscripten": | 
					
						
							| 
									
										
										
										
											2024-05-07 01:56:28 +03:00
										 |  |  |         return plain_pager | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |     use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER') | 
					
						
							|  |  |  |     if use_pager: | 
					
						
							|  |  |  |         if sys.platform == 'win32': # pipes completely broken in Windows | 
					
						
							|  |  |  |             return lambda text, title='': tempfile_pager(plain(text), use_pager) | 
					
						
							|  |  |  |         elif os.environ.get('TERM') in ('dumb', 'emacs'): | 
					
						
							|  |  |  |             return lambda text, title='': pipe_pager(plain(text), use_pager, title) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return lambda text, title='': pipe_pager(text, use_pager, title) | 
					
						
							|  |  |  |     if os.environ.get('TERM') in ('dumb', 'emacs'): | 
					
						
							|  |  |  |         return plain_pager | 
					
						
							|  |  |  |     if sys.platform == 'win32': | 
					
						
							|  |  |  |         return lambda text, title='': tempfilepager(plain(text), 'more <') | 
					
						
							|  |  |  |     if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: | 
					
						
							|  |  |  |         return lambda text, title='': pipe_pager(text, 'less', title) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     import tempfile | 
					
						
							|  |  |  |     (fd, filename) = tempfile.mkstemp() | 
					
						
							|  |  |  |     os.close(fd) | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: | 
					
						
							|  |  |  |             return lambda text, title='': pipe_pager(text, 'more', title) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return tty_pager | 
					
						
							|  |  |  |     finally: | 
					
						
							|  |  |  |         os.unlink(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def escape_stdout(text: str) -> str: | 
					
						
							|  |  |  |     # Escape non-encodable characters to avoid encoding errors later | 
					
						
							|  |  |  |     encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8' | 
					
						
							|  |  |  |     return text.encode(encoding, 'backslashreplace').decode(encoding) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def escape_less(s: str) -> str: | 
					
						
							|  |  |  |     return re.sub(r'([?:.%\\])', r'\\\1', s) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def plain(text: str) -> str: | 
					
						
							|  |  |  |     """Remove boldface formatting from text.""" | 
					
						
							|  |  |  |     return re.sub('.\b', '', text) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def tty_pager(text: str, title: str = '') -> None: | 
					
						
							|  |  |  |     """Page through text on a text terminal.""" | 
					
						
							|  |  |  |     lines = plain(escape_stdout(text)).split('\n') | 
					
						
							|  |  |  |     has_tty = False | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         import tty | 
					
						
							|  |  |  |         import termios | 
					
						
							|  |  |  |         fd = sys.stdin.fileno() | 
					
						
							|  |  |  |         old = termios.tcgetattr(fd) | 
					
						
							|  |  |  |         tty.setcbreak(fd) | 
					
						
							|  |  |  |         has_tty = True | 
					
						
							| 
									
										
										
										
											2024-05-17 06:13:24 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def getchar() -> str: | 
					
						
							|  |  |  |             return sys.stdin.read(1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  |     except (ImportError, AttributeError, io.UnsupportedOperation): | 
					
						
							| 
									
										
										
										
											2024-05-17 06:13:24 -04:00
										 |  |  |         def getchar() -> str: | 
					
						
							|  |  |  |             return sys.stdin.readline()[:-1][:1] | 
					
						
							| 
									
										
										
										
											2024-05-05 21:32:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             h = int(os.environ.get('LINES', 0)) | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             h = 0 | 
					
						
							|  |  |  |         if h <= 1: | 
					
						
							|  |  |  |             h = 25 | 
					
						
							|  |  |  |         r = inc = h - 1 | 
					
						
							|  |  |  |         sys.stdout.write('\n'.join(lines[:inc]) + '\n') | 
					
						
							|  |  |  |         while lines[r:]: | 
					
						
							|  |  |  |             sys.stdout.write('-- more --') | 
					
						
							|  |  |  |             sys.stdout.flush() | 
					
						
							|  |  |  |             c = getchar() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if c in ('q', 'Q'): | 
					
						
							|  |  |  |                 sys.stdout.write('\r          \r') | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             elif c in ('\r', '\n'): | 
					
						
							|  |  |  |                 sys.stdout.write('\r          \r' + lines[r] + '\n') | 
					
						
							|  |  |  |                 r = r + 1 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if c in ('b', 'B', '\x1b'): | 
					
						
							|  |  |  |                 r = r - inc - inc | 
					
						
							|  |  |  |                 if r < 0: r = 0 | 
					
						
							|  |  |  |             sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n') | 
					
						
							|  |  |  |             r = r + inc | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     finally: | 
					
						
							|  |  |  |         if has_tty: | 
					
						
							|  |  |  |             termios.tcsetattr(fd, termios.TCSAFLUSH, old) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def plain_pager(text: str, title: str = '') -> None: | 
					
						
							|  |  |  |     """Simply print unformatted text.  This is the ultimate fallback.""" | 
					
						
							|  |  |  |     sys.stdout.write(plain(escape_stdout(text))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def pipe_pager(text: str, cmd: str, title: str = '') -> None: | 
					
						
							|  |  |  |     """Page through text by feeding it to another program.""" | 
					
						
							|  |  |  |     import subprocess | 
					
						
							|  |  |  |     env = os.environ.copy() | 
					
						
							|  |  |  |     if title: | 
					
						
							|  |  |  |         title += ' ' | 
					
						
							|  |  |  |     esc_title = escape_less(title) | 
					
						
							|  |  |  |     prompt_string = ( | 
					
						
							|  |  |  |         f' {esc_title}' + | 
					
						
							|  |  |  |         '?ltline %lt?L/%L.' | 
					
						
							|  |  |  |         ':byte %bB?s/%s.' | 
					
						
							|  |  |  |         '.' | 
					
						
							|  |  |  |         '?e (END):?pB %pB\\%..' | 
					
						
							|  |  |  |         ' (press h for help or q to quit)') | 
					
						
							|  |  |  |     env['LESS'] = '-RmPm{0}$PM{0}$'.format(prompt_string) | 
					
						
							|  |  |  |     proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, | 
					
						
							|  |  |  |                             errors='backslashreplace', env=env) | 
					
						
							|  |  |  |     assert proc.stdin is not None | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         with proc.stdin as pipe: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 pipe.write(text) | 
					
						
							|  |  |  |             except KeyboardInterrupt: | 
					
						
							|  |  |  |                 # We've hereby abandoned whatever text hasn't been written, | 
					
						
							|  |  |  |                 # but the pager is still in control of the terminal. | 
					
						
							|  |  |  |                 pass | 
					
						
							|  |  |  |     except OSError: | 
					
						
							|  |  |  |         pass # Ignore broken pipes caused by quitting the pager program. | 
					
						
							|  |  |  |     while True: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             proc.wait() | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  |         except KeyboardInterrupt: | 
					
						
							|  |  |  |             # Ignore ctl-c like the pager itself does.  Otherwise the pager is | 
					
						
							|  |  |  |             # left running and the terminal is in raw mode and unusable. | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def tempfile_pager(text: str, cmd: str, title: str = '') -> None: | 
					
						
							|  |  |  |     """Page through text by invoking a program on a temporary file.""" | 
					
						
							|  |  |  |     import tempfile | 
					
						
							|  |  |  |     with tempfile.TemporaryDirectory() as tempdir: | 
					
						
							|  |  |  |         filename = os.path.join(tempdir, 'pydoc.out') | 
					
						
							|  |  |  |         with open(filename, 'w', errors='backslashreplace', | 
					
						
							|  |  |  |                   encoding=os.device_encoding(0) if | 
					
						
							|  |  |  |                   sys.platform == 'win32' else None | 
					
						
							|  |  |  |                   ) as file: | 
					
						
							|  |  |  |             file.write(text) | 
					
						
							|  |  |  |         os.system(cmd + ' "' + filename + '"') |