def feed_app_with_input(type, message, text, **kwargs): """ Create an application, feed it with the given user input and return the CLI object. This returns a (result, CLI) tuple. note: this only works if you import your prompt and then this function!! """ # If the given text doesn't end with a newline, the interface won't finish. assert text.endswith('\r') inp = PosixPipeInput(text) try: with create_app_session(input=inp, output=DummyOutput()) as session: application = getattr(prompts, type).question(message, **kwargs) #print(application.input) #breakpoint() if isinstance(application, Application): result = application.run() elif isinstance(application, PromptSession): result = application.prompt() return result finally: inp.close()
def __init__( self, conn: socket.socket, addr: Tuple[str, int], interact: Callable[["TelnetConnection"], Awaitable[None]], server: "TelnetServer", encoding: str, style: Optional[BaseStyle], ) -> None: self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size() -> Size: return self.size self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) self.vt100_output = Vt100_Output(self.stdout, get_size, write_binary=False) def data_received(data: bytes) -> None: """ TelnetProtocolParser 'data_received' callback """ self.vt100_input.send_bytes(data) def size_received(rows: int, columns: int) -> None: """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) self.context: Optional[contextvars.Context] = None
def __init__(self, conn, addr, interact, server, encoding, style): assert isinstance(addr, tuple) # (addr, port) tuple assert callable(interact) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Execution context. self._context_id = None # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size, write_binary=False) def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) self.vt100_input.send_bytes(data) def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received)
def __init__(self, interact: Callable[[], Awaitable[None]]) -> None: self.interact = interact self._chan = None self.app_session: Optional[AppSession] = None # PipInput object, for sending input in the CLI. # (This is something that we can use in the prompt_toolkit event loop, # but still write date in manually.) self._input = PosixPipeInput() # Output object. Don't render to the real stdout, but write everything # in the SSH channel. class Stdout: def write(s, data): if self._chan is not None: self._chan.write(data.replace('\n', '\r\n')) def flush(s): pass self._output = Vt100_Output(cast(TextIO, Stdout()), self._get_size, write_binary=False)
def __init__(self, conn: socket.socket, addr: Tuple[str, int], interact: Callable[['TelnetConnection'], Awaitable[None]], server: 'TelnetServer', encoding: str, style: Optional[BaseStyle]) -> None: self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size() -> Size: return self.size self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) self.vt100_output = Vt100_Output( self.stdout, get_size, write_binary=False) def data_received(data: bytes) -> None: """ TelnetProtocolParser 'data_received' callback """ self.vt100_input.send_bytes(data) def size_received(rows: int, columns: int) -> None: """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) self.context: Optional[contextvars.Context] = None
class PromptToolkitSession(asyncssh.SSHServerSession): def __init__(self, interact: Callable[[], Awaitable[None]]) -> None: self.interact = interact self._chan = None self.app_session: Optional[AppSession] = None # PipInput object, for sending input in the CLI. # (This is something that we can use in the prompt_toolkit event loop, # but still write date in manually.) self._input = PosixPipeInput() # Output object. Don't render to the real stdout, but write everything # in the SSH channel. class Stdout: def write(s, data): if self._chan is not None: self._chan.write(data.replace('\n', '\r\n')) def flush(s): pass self._output = Vt100_Output(cast(TextIO, Stdout()), self._get_size, write_binary=False) def _get_size(self) -> Size: """ Callable that returns the current `Size`, required by Vt100_Output. """ if self._chan is None: return Size(rows=20, columns=79) else: width, height, pixwidth, pixheight = self._chan.get_terminal_size() return Size(rows=height, columns=width) def connection_made(self, chan): self._chan = chan def shell_requested(self) -> bool: return True def session_started(self) -> None: asyncio.get_event_loop().create_task(self._interact()) async def _interact(self) -> None: if self._chan is None: # Should not happen. raise Exception('`_interact` called before `connection_made`.') # Disable the line editing provided by asyncssh. Prompt_toolkit # provides the line editing. self._chan.set_line_mode(False) with create_app_session(input=self._input, output=self._output) as session: self.app_session = session try: await self.interact() except BaseException: traceback.print_exc() finally: # Close the connection. self._chan.close() def terminal_size_changed(self, width, height, pixwidth, pixheight): # Send resize event to the current application. if self.app_session and self.app_session.app: self.app_session.app._on_resize() def data_received(self, data, datatype): self._input.send_text(data)
class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, interact, server, encoding, style): assert isinstance(addr, tuple) # (addr, port) tuple assert callable(interact) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Execution context. self._context_id = None # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size, write_binary=False) def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) self.vt100_input.send_bytes(data) def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) def run_application(self): """ Run application. """ def handle_incoming_data(): data = self.conn.recv(1024) if data: self.feed(data) else: # Connection closed by client. logger.info('Connection closed by client. %r %r' % self.addr) self.close() def run(): with context() as ctx_id: self._context_id = ctx_id # Set input/output for all application running in this context. set_default_input(self.vt100_input) set_default_output(self.vt100_output) # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: obj = self.interact(self) if _is_coroutine(obj): # Got an asyncio coroutine. import asyncio f = asyncio.ensure_future(obj) yield From(Future.from_asyncio_future(f)) else: # Got a prompt_toolkit coroutine. yield From(obj) except Exception as e: print('Got %s' % type(e).__name__, e) import traceback traceback.print_exc() raise finally: self.close() return ensure_future(run()) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) def close(self): """ Closed by client. """ if not self._closed: self._closed = True self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close() def send(self, formatted_text): """ Send text to the client. """ formatted_text = to_formatted_text(formatted_text) print_formatted_text(self.vt100_output, formatted_text, self.style or DummyStyle()) def send_above_prompt(self, formatted_text): """ Send text to the client. This is asynchronous, returns a `Future`. """ formatted_text = to_formatted_text(formatted_text) return self._run_in_terminal(lambda: self.send(formatted_text)) def _run_in_terminal(self, func): # Make sure that when an application was active for this connection, # that we print the text above the application. with context(self._context_id): return run_in_terminal(func) def erase_screen(self): """ Erase the screen and move the cursor to the top. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush()
class TelnetConnection: """ Class that represents one Telnet connection. """ def __init__(self, conn, addr: Tuple[str, int], interact: Callable, server: 'TelnetServer', encoding: str, style) -> None: self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size(): return self.size self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) self.vt100_output = Vt100_Output(self.stdout, get_size, write_binary=False) def data_received(data: bytes) -> None: """ TelnetProtocolParser 'data_received' callback """ self.vt100_input.send_bytes(data) def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) self.context: Optional[contextvars.Context] = None async def run_application(self): """ Run application. """ def handle_incoming_data(): data = self.conn.recv(1024) if data: self.feed(data) else: # Connection closed by client. logger.info('Connection closed by client. %r %r' % self.addr) self.close() async def run(): # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: await self.interact(self) except Exception as e: print('Got %s' % type(e).__name__, e) import traceback traceback.print_exc() raise finally: self.close() with create_app_session(input=self.vt100_input, output=self.vt100_output): self.context = contextvars.copy_context() return await run() def feed(self, data: bytes) -> None: """ Handler for incoming data. (Called by TelnetServer.) """ self.parser.feed(data) def close(self): """ Closed by client. """ if not self._closed: self._closed = True self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close() def send(self, formatted_text): """ Send text to the client. """ formatted_text = to_formatted_text(formatted_text) print_formatted_text(self.vt100_output, formatted_text, self.style or DummyStyle()) def send_above_prompt(self, formatted_text): """ Send text to the client. This is asynchronous, returns a `Future`. """ formatted_text = to_formatted_text(formatted_text) return self._run_in_terminal(lambda: self.send(formatted_text)) def _run_in_terminal(self, func): # Make sure that when an application was active for this connection, # that we print the text above the application. self.context.run(run_in_terminal, func) def erase_screen(self) -> None: """ Erase the screen and move the cursor to the top. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush()
class TelnetConnection: """ Class that represents one Telnet connection. """ def __init__(self, conn: socket.socket, addr: Tuple[str, int], interact: Callable[['TelnetConnection'], Awaitable[None]], server: 'TelnetServer', encoding: str, style: Optional[BaseStyle]) -> None: self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size() -> Size: return self.size self.stdout = cast(TextIO, _ConnectionStdout(conn, encoding=encoding)) self.vt100_output = Vt100_Output( self.stdout, get_size, write_binary=False) def data_received(data: bytes) -> None: """ TelnetProtocolParser 'data_received' callback """ self.vt100_input.send_bytes(data) def size_received(rows: int, columns: int) -> None: """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) self.context: Optional[contextvars.Context] = None async def run_application(self) -> None: """ Run application. """ def handle_incoming_data() -> None: data = self.conn.recv(1024) if data: self.feed(data) else: # Connection closed by client. logger.info('Connection closed by client. %r %r' % self.addr) self.close() async def run() -> None: # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: await self.interact(self) except Exception as e: print('Got %s' % type(e).__name__, e) import traceback; traceback.print_exc() raise finally: self.close() with create_app_session(input=self.vt100_input, output=self.vt100_output): self.context = contextvars.copy_context() await run() def feed(self, data: bytes) -> None: """ Handler for incoming data. (Called by TelnetServer.) """ self.parser.feed(data) def close(self) -> None: """ Closed by client. """ if not self._closed: self._closed = True self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close() def send(self, formatted_text: AnyFormattedText) -> None: """ Send text to the client. """ formatted_text = to_formatted_text(formatted_text) print_formatted_text(self.vt100_output, formatted_text, self.style or DummyStyle()) def send_above_prompt(self, formatted_text: AnyFormattedText) -> None: """ Send text to the client. This is asynchronous, returns a `Future`. """ formatted_text = to_formatted_text(formatted_text) return self._run_in_terminal(lambda: self.send(formatted_text)) def _run_in_terminal(self, func: Callable[[], None]) -> None: # Make sure that when an application was active for this connection, # that we print the text above the application. if self.context: self.context.run(run_in_terminal, func) else: raise RuntimeError('Called _run_in_terminal outside `run_application`.') def erase_screen(self) -> None: """ Erase the screen and move the cursor to the top. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush()
class PromptToolkitSession(asyncssh.SSHServerSession): def __init__(self, interact: Callable[[], Awaitable[None]]) -> None: self.interact = interact self._chan = None self.app_session: Optional[AppSession] = None # PipInput object, for sending input in the CLI. # (This is something that we can use in the prompt_toolkit event loop, # but still write date in manually.) self._input = PosixPipeInput() # Output object. Don't render to the real stdout, but write everything # in the SSH channel. class Stdout: def write(s, data): if self._chan is not None: self._chan.write(data.replace('\n', '\r\n')) def flush(s): pass self._output = Vt100_Output(cast(TextIO, Stdout()), self._get_size, write_binary=False) def _get_size(self) -> Size: """ Callable that returns the current `Size`, required by Vt100_Output. """ if self._chan is None: return Size(rows=20, columns=79) else: width, height, pixwidth, pixheight = self._chan.get_terminal_size() return Size(rows=height, columns=width) def connection_made(self, chan): self._chan = chan def shell_requested(self) -> bool: return True def session_started(self) -> None: asyncio.create_task(self._interact()) async def _interact(self) -> None: if self._chan is None: # Should not happen. raise Exception('`_interact` called before `connection_made`.') # Disable the line editing provided by asyncssh. Prompt_toolkit # provides the line editing. self._chan.set_line_mode(False) with create_app_session(input=self._input, output=self._output) as session: self.app_session = session try: await self.interact() except BaseException: traceback.print_exc() finally: # Close the connection. self._chan.close() def terminal_size_changed(self, width, height, pixwidth, pixheight): # Send resize event to the current application. if self.app_session and self.app_session.app: self.app_session.app._on_resize() def data_received(self, data, datatype): self._input.send_text(data)