예제 #1
0
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()
예제 #2
0
    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
예제 #3
0
    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)
예제 #4
0
    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
    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)
예제 #7
0
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)
예제 #8
0
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()
예제 #9
0
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)