mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
	
	
		
			170 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			170 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | 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": | ||
|  |         return plainpager | ||
|  |     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) | ||
|  |         getchar = lambda: sys.stdin.read(1) | ||
|  |         has_tty = True | ||
|  |     except (ImportError, AttributeError, io.UnsupportedOperation): | ||
|  |         getchar = lambda: sys.stdin.readline()[:-1][:1] | ||
|  | 
 | ||
|  |     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 + '"') |