class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, application, server, encoding): assert isinstance(addr, tuple) # (addr, port) tuple assert isinstance(application, TelnetApplication) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.application = application self.closed = False self.handling_command = True self.server = server self.encoding = encoding self.callback = None # Function that handles the CLI result. # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size) # Create an eventloop (adaptor) for the CommandLineInterface. self.eventloop = _TelnetEventLoopInterface(server) # Set default CommandLineInterface. self.set_application(create_prompt_application()) # Call client_connected application.client_connected(self) # Draw for the first time. self.handling_command = False self.cli._redraw() def set_application(self, app, callback=None): """ Set ``CommandLineInterface`` instance for this connection. (This can be replaced any time.) :param cli: CommandLineInterface instance. :param callback: Callable that takes the result of the CLI. """ assert isinstance(app, Application) assert callback is None or callable(callback) self.cli = CommandLineInterface(application=app, eventloop=self.eventloop, output=self.vt100_output) self.callback = callback # Create a parser, and parser callbacks. cb = self.cli.create_eventloop_callbacks() inputstream = InputStream(cb.feed_key) # Input decoder for stdin. (Required when working with multibyte # characters, like chinese input.) stdin_decoder_cls = getincrementaldecoder(self.encoding) stdin_decoder = [stdin_decoder_cls()] # nonlocal def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) try: result = stdin_decoder[0].decode(data) inputstream.feed(result) except UnicodeDecodeError: stdin_decoder[0] = stdin_decoder_cls() return '' def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) cb.terminal_size_changed() self.parser = TelnetProtocolParser(data_received, size_received) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) # Render again. self.cli._redraw() # When a return value has been set (enter was pressed), handle command. if self.cli.is_returning: try: return_value = self.cli.return_value() except (EOFError, KeyboardInterrupt) as e: # Control-D or Control-C was pressed. logger.info('%s, closing connection.', type(e).__name__) self.close() return # Handle CLI command self._handle_command(return_value) def _handle_command(self, command): """ Handle command. This will run in a separate thread, in order not to block the event loop. """ logger.info('Handle command %r', command) def in_executor(): self.handling_command = True try: if self.callback is not None: self.callback(self, command) finally: self.server.call_from_executor(done) def done(): self.handling_command = False # Reset state and draw again. (If the connection is still open -- # the application could have called TelnetConnection.close() if not self.closed: self.cli.reset() self.cli.buffers[DEFAULT_BUFFER].reset() self.cli.renderer.request_absolute_cursor_position() self.vt100_output.flush() self.cli._redraw() self.server.run_in_executor(in_executor) def erase_screen(self): """ Erase output screen. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush() def send(self, data): """ Send text to the client. """ assert isinstance(data, text_type) # When data is send back to the client, we should replace the line # endings. (We didn't allocate a real pseudo terminal, and the telnet # connection is raw, so we are responsible for inserting \r.) self.stdout.write(data.replace('\n', '\r\n')) self.stdout.flush() def close(self): """ Close the connection. """ self.application.client_leaving(self) self.conn.close() self.closed = True
class Tosh: def __init__(self, base_dir, config): for module in config.get('modules'): import_module(module) self.tasks = TaskManager(self) self.window = MainWindow(self) self.style = ToshStyle(config.get('ui', 'style')) self._parser = CommandLineParser(self, base_dir) self.config = config self.variables = {} application = Application( layout=self.window, buffer=Buffer( enable_history_search=True, complete_while_typing=False, is_multiline=False, history=FileHistory(base_dir + "/history"), # validator=validator, completer=CommandLineCompleter(self), auto_suggest=AutoSuggestFromHistory(), accept_action=AcceptAction(self.run_command), ), mouse_support=config.get('ui', 'mouse'), style=self.style, key_bindings_registry=get_key_bindings(self), use_alternate_screen=True) self._cli = CommandLineInterface(application=application, eventloop=create_asyncio_eventloop(), output=create_output(true_color=True)) def refresh(self): self._cli.request_redraw() def _exception_handler(self, loop, context): self._cli.reset() print(context['message']) if 'exception' in context: traceback.print_exc() sys.exit(1) def run(self): for cmd in self.config.get('autostart'): cmd_task = self._parser.parse(cmd) cmd_task.set_cmdline(cmd) self.tasks._tasks.append(cmd_task) asyncio.ensure_future(cmd_task.run()) asyncio.get_event_loop().set_exception_handler(self._exception_handler) try: asyncio.get_event_loop().run_until_complete(self._cli.run_async()) except EOFError: pass def run_command(self, _, document): if not document.text: return try: cmd_task = self._parser.parse(document.text) if isinstance(cmd_task, Statement): cmd_task.set_cmdline(document.text) document.reset(append_to_history=True) self.tasks._tasks.append(cmd_task) asyncio.ensure_future(cmd_task.run()) else: self.tasks._tasks.append( ErrorStatement(self, document.text, "Parser returned no task")) document.reset(append_to_history=False) except BaseException as e: # Last resort error handler self.tasks._tasks.append( ErrorStatement(self, document.text, traceback.format_exc())) document.reset(append_to_history=False)
class ShellConnection(TelnetConnection): def __init__(self, reader, writer, shell, window_size_changed_callback, loop): super(ShellConnection, self).__init__(reader, writer, window_size_changed_callback) self._shell = shell self._loop = loop self._cli = None self._cb = None self._size = Size(rows=40, columns=79) self.encoding = 'utf-8' async def connected(self): # prompt_toolkit internally checks if it's on windows during output rendering but # we need to force that we use Vt100_Output not Win32_Output from prompt_toolkit import renderer renderer.is_windows = lambda: False def get_size(): return self._size self._cli = CommandLineInterface( application=create_prompt_application(self._shell.prompt), eventloop=UnstoppableEventLoop(create_asyncio_eventloop(self._loop)), input=PatchedStdinInput(sys.stdin), output=Vt100_Output(self, get_size)) self._cb = self._cli.create_eventloop_callbacks() self._inputstream = InputStream(self._cb.feed_key) # Taken from prompt_toolkit telnet server # https://github.com/jonathanslenders/python-prompt-toolkit/blob/99fa7fae61c9b4ed9767ead3b4f9b1318cfa875d/prompt_toolkit/contrib/telnet/server.py#L165 self._cli._is_running = True if self._shell.welcome_message is not None: self.send(self._shell.welcome_message.encode()) self._cli._redraw() async def disconnected(self): pass @asyncio.coroutine def window_size_changed(self, columns, rows): self._size = Size(rows=rows, columns=columns) self._cb.terminal_size_changed() if self._window_size_changed_callback: yield from self._window_size_changed_callback(columns, rows) async def feed(self, data): data = data.decode() self._inputstream.feed(data) self._cli._redraw() # Prompt toolkit has returned the command if self._cli.is_returning: try: returned_value = self._cli.return_value() except (EOFError, KeyboardInterrupt) as e: # don't close terminal, just keep it alive self.close() return command = returned_value.text res = await self._shell._parse_command(command) self.send(res.encode()) self.reset() def reset(self): """ Resets terminal screen""" self._cli.reset() self._cli.buffers[DEFAULT_BUFFER].reset() self._cli.renderer.request_absolute_cursor_position() self._cli._redraw() def write(self, data): """ Compat with CLI""" self.send(data) def flush(self): """ Compat with CLI""" pass
class ShellConnection(TelnetConnection): def __init__(self, reader, writer, shell, loop): super(ShellConnection, self).__init__(reader, writer) self._shell = shell self._loop = loop self._cli = None self._cb = None self._size = Size(rows=40, columns=79) self.encoding = 'utf-8' @asyncio.coroutine def connected(self): # prompt_toolkit internally checks if it's on windows during output rendering but # we need to force that we use Vt100_Output not Win32_Output from prompt_toolkit import renderer renderer.is_windows = lambda: False def get_size(): return self._size self._cli = CommandLineInterface( application=create_prompt_application(self._shell.prompt), eventloop=UnstoppableEventLoop(create_asyncio_eventloop( self._loop)), input=PatchedStdinInput(sys.stdin), output=Vt100_Output(self, get_size)) self._cb = self._cli.create_eventloop_callbacks() self._inputstream = InputStream(self._cb.feed_key) # Taken from prompt_toolkit telnet server # https://github.com/jonathanslenders/python-prompt-toolkit/blob/99fa7fae61c9b4ed9767ead3b4f9b1318cfa875d/prompt_toolkit/contrib/telnet/server.py#L165 self._cli._is_running = True if self._shell.welcome_message is not None: self.send(self._shell.welcome_message.encode()) self._cli._redraw() @asyncio.coroutine def disconnected(self): pass def window_size_changed(self, columns, rows): self._size = Size(rows=rows, columns=columns) self._cb.terminal_size_changed() @asyncio.coroutine def feed(self, data): data = data.decode() self._inputstream.feed(data) self._cli._redraw() # Prompt toolkit has returned the command if self._cli.is_returning: try: returned_value = self._cli.return_value() except (EOFError, KeyboardInterrupt) as e: # don't close terminal, just keep it alive self.close() return command = returned_value.text res = yield from self._shell._parse_command(command) self.send(res.encode()) self.reset() def reset(self): """ Resets terminal screen""" self._cli.reset() self._cli.buffers[DEFAULT_BUFFER].reset() self._cli.renderer.request_absolute_cursor_position() self._cli._redraw() def write(self, data): """ Compat with CLI""" self.send(data) def flush(self): """ Compat with CLI""" pass
class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, application, server, encoding): assert isinstance(addr, tuple) # (addr, port) tuple assert isinstance(application, TelnetApplication) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.application = application self.closed = False self.handling_command = True self.server = server self.encoding = encoding self.callback = None # Function that handles the CLI result. # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size) # Create an eventloop (adaptor) for the CommandLineInterface. self.eventloop = _TelnetEventLoopInterface(server) # Set default CommandLineInterface. self.set_application(create_prompt_application()) # Call client_connected application.client_connected(self) # Draw for the first time. self.handling_command = False self.cli._redraw() def set_application(self, app, callback=None): """ Set ``CommandLineInterface`` instance for this connection. (This can be replaced any time.) :param cli: CommandLineInterface instance. :param callback: Callable that takes the result of the CLI. """ assert isinstance(app, Application) assert callback is None or callable(callback) self.cli = CommandLineInterface(application=app, eventloop=self.eventloop, output=self.vt100_output) self.callback = callback # Create a parser, and parser callbacks. cb = self.cli.create_eventloop_callbacks() inputstream = InputStream(cb.feed_key) # Input decoder for stdin. (Required when working with multibyte # characters, like chinese input.) stdin_decoder_cls = getincrementaldecoder(self.encoding) stdin_decoder = [stdin_decoder_cls()] # nonlocal # Tell the CLI that it's running. We don't start it through the run() # call, but will still want _redraw() to work. self.cli._is_running = True def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) try: result = stdin_decoder[0].decode(data) inputstream.feed(result) except UnicodeDecodeError: stdin_decoder[0] = stdin_decoder_cls() return "" def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) cb.terminal_size_changed() self.parser = TelnetProtocolParser(data_received, size_received) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) # Render again. self.cli._redraw() # When a return value has been set (enter was pressed), handle command. if self.cli.is_returning: try: return_value = self.cli.return_value() except (EOFError, KeyboardInterrupt) as e: # Control-D or Control-C was pressed. logger.info("%s, closing connection.", type(e).__name__) self.close() return # Handle CLI command self._handle_command(return_value) def _handle_command(self, command): """ Handle command. This will run in a separate thread, in order not to block the event loop. """ logger.info("Handle command %r", command) def in_executor(): self.handling_command = True try: if self.callback is not None: self.callback(self, command) finally: self.server.call_from_executor(done) def done(): self.handling_command = False # Reset state and draw again. (If the connection is still open -- # the application could have called TelnetConnection.close() if not self.closed: self.cli.reset() self.cli.buffers[DEFAULT_BUFFER].reset() self.cli.renderer.request_absolute_cursor_position() self.vt100_output.flush() self.cli._redraw() self.server.run_in_executor(in_executor) def erase_screen(self): """ Erase output screen. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush() def send(self, data): """ Send text to the client. """ assert isinstance(data, text_type) # When data is send back to the client, we should replace the line # endings. (We didn't allocate a real pseudo terminal, and the telnet # connection is raw, so we are responsible for inserting \r.) self.stdout.write(data.replace("\n", "\r\n")) self.stdout.flush() def close(self): """ Close the connection. """ self.application.client_leaving(self) self.conn.close() self.closed = True