mirror of
				https://github.com/python/cpython.git
				synced 2025-10-26 03:04:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			173 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			173 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| 
 | |
| import io
 | |
| import os
 | |
| import re
 | |
| import sys
 | |
| 
 | |
| 
 | |
| # types
 | |
| if False:
 | |
|     from typing import Protocol
 | |
|     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 plain_pager
 | |
|     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='': tempfile_pager(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
 | |
| 
 | |
|         def getchar() -> str:
 | |
|             return sys.stdin.read(1)
 | |
| 
 | |
|     except (ImportError, AttributeError, io.UnsupportedOperation):
 | |
|         def getchar() -> str:
 | |
|             return 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 + '"')
 | 
