1# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com> 2# Armin Rigo 3# 4# All Rights Reserved 5# 6# 7# Permission to use, copy, modify, and distribute this software and 8# its documentation for any purpose is hereby granted without fee, 9# provided that the above copyright notice appear in all copies and 10# that both that copyright notice and this permission notice appear in 11# supporting documentation. 12# 13# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO 14# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 15# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 16# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 17# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 18# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 19# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 21"""This is an alternative to python_reader which tries to emulate 22the CPython prompt as closely as possible, with the exception of 23allowing multiline input and multiline history entries. 24""" 25 26from __future__ import annotations 27 28import _sitebuiltins 29import linecache 30import functools 31import os 32import sys 33import code 34 35from .readline import _get_reader, multiline_input 36 37TYPE_CHECKING = False 38 39if TYPE_CHECKING: 40 from typing import Any 41 42 43_error: tuple[type[Exception], ...] | type[Exception] 44try: 45 from .unix_console import _error 46except ModuleNotFoundError: 47 from .windows_console import _error 48 49def check() -> str: 50 """Returns the error message if there is a problem initializing the state.""" 51 try: 52 _get_reader() 53 except _error as e: 54 if term := os.environ.get("TERM", ""): 55 term = f"; TERM={term}" 56 return str(str(e) or repr(e) or "unknown error") + term 57 return "" 58 59 60def _strip_final_indent(text: str) -> str: 61 # kill spaces and tabs at the end, but only if they follow '\n'. 62 # meant to remove the auto-indentation only (although it would of 63 # course also remove explicitly-added indentation). 64 short = text.rstrip(" \t") 65 n = len(short) 66 if n > 0 and text[n - 1] == "\n": 67 return short 68 return text 69 70 71def _clear_screen(): 72 reader = _get_reader() 73 reader.scheduled_commands.append("clear_screen") 74 75 76REPL_COMMANDS = { 77 "exit": _sitebuiltins.Quitter('exit', ''), 78 "quit": _sitebuiltins.Quitter('quit' ,''), 79 "copyright": _sitebuiltins._Printer('copyright', sys.copyright), 80 "help": "help", 81 "clear": _clear_screen, 82 "\x1a": _sitebuiltins.Quitter('\x1a', ''), 83} 84 85 86def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool: 87 # ooh, look at the hack: 88 src = _strip_final_indent(unicodetext) 89 try: 90 code = console.compile(src, "<stdin>", "single") 91 except (OverflowError, SyntaxError, ValueError): 92 lines = src.splitlines(keepends=True) 93 if len(lines) == 1: 94 return False 95 96 last_line = lines[-1] 97 was_indented = last_line.startswith((" ", "\t")) 98 not_empty = last_line.strip() != "" 99 incomplete = not last_line.endswith("\n") 100 return (was_indented or not_empty) and incomplete 101 else: 102 return code is None 103 104 105def run_multiline_interactive_console( 106 console: code.InteractiveConsole, 107 *, 108 future_flags: int = 0, 109) -> None: 110 from .readline import _setup 111 _setup(console.locals) 112 if future_flags: 113 console.compile.compiler.flags |= future_flags 114 115 more_lines = functools.partial(_more_lines, console) 116 input_n = 0 117 118 def maybe_run_command(statement: str) -> bool: 119 statement = statement.strip() 120 if statement in console.locals or statement not in REPL_COMMANDS: 121 return False 122 123 reader = _get_reader() 124 reader.history.pop() # skip internal commands in history 125 command = REPL_COMMANDS[statement] 126 if callable(command): 127 command() 128 return True 129 130 if isinstance(command, str): 131 # Internal readline commands require a prepared reader like 132 # inside multiline_input. 133 reader.prepare() 134 reader.refresh() 135 reader.do_cmd((command, [statement])) 136 reader.restore() 137 return True 138 139 return False 140 141 while 1: 142 try: 143 try: 144 sys.stdout.flush() 145 except Exception: 146 pass 147 148 ps1 = getattr(sys, "ps1", ">>> ") 149 ps2 = getattr(sys, "ps2", "... ") 150 try: 151 statement = multiline_input(more_lines, ps1, ps2) 152 except EOFError: 153 break 154 155 if maybe_run_command(statement): 156 continue 157 158 input_name = f"<python-input-{input_n}>" 159 linecache._register_code(input_name, statement, "<stdin>") # type: ignore[attr-defined] 160 more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg] 161 assert not more 162 input_n += 1 163 except KeyboardInterrupt: 164 r = _get_reader() 165 if r.input_trans is r.isearch_trans: 166 r.do_cmd(("isearch-end", [""])) 167 r.pos = len(r.get_unicode()) 168 r.dirty = True 169 r.refresh() 170 r.in_bracketed_paste = False 171 console.write("\nKeyboardInterrupt\n") 172 console.resetbuffer() 173 except MemoryError: 174 console.write("\nMemoryError\n") 175 console.resetbuffer() 176