Beispiel #1
0
    def complete(self, text, state):  # pylint: disable=unused-argument
        '''
        Tab complete for the current step.
        '''

        if state == 0:
            parser = StatementParser(self.features)
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''

            line = line[:endidx]
            if self.complete_single_token:
                # treat the entire line as a single token
                args = [line]
            else:
                # tokenize the line
                tokens = parser.tokenize(line)
                tokens = parser.condense(tokens)
                args = [t.text for t in tokens if isinstance(t, StringToken)]
            self.completions = self.active_step.complete(self, args, prefix)

        if state < len(self.completions):
            return self.completions[state]
        return None
Beispiel #2
0
    def complete(self, text, state):  # pylint: disable=unused-argument
        '''
        Tab complete for the current step.
        '''

        if state == 0:
            parser = StatementParser(self.features)
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''

            line = line[:endidx]
            if self.complete_single_token:
                # treat the entire line as a single token
                args = [line]
            else:
                # tokenize the line
                tokens = parser.tokenize(line)
                tokens = parser.condense(tokens)
                args = [t.text for t in tokens if isinstance(t, StringToken)]
            self.completions = self.active_step.complete(self, args, prefix)

        if state < len(self.completions):
            return self.completions[state]
        return None
Beispiel #3
0
    def __init__(self, shell_name='pypsi', width=80, exit_rc=-1024, ctx=None):
        '''
        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a command
            when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.real_stdout = sys.stdout
        self.real_stdin = sys.stdin
        self.real_stderr = sys.stderr
        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.on_shell_ready()
Beispiel #4
0
    def preprocess_single(self, raw, origin):
        tokens = self.on_tokenize([StringToken(0, raw, quote='"')], origin)

        if tokens:
            parser = StatementParser(self.features)
            parser.clean_escapes(tokens)
            ret = ''
            for token in tokens:
                ret += token.text
            return ret
        return ''
Beispiel #5
0
 def __init__(self, name, description, steps=None):
     '''
     :param str name: the prompt wizard name to display to the user
     :param str description: a short description of what the wizard does
     :param list steps: a list of :class:`WizardStep` objects
     '''
     self.name = name
     self.description = description
     self.steps = steps
     self.values = Namespace()
     self.parser = StatementParser()
Beispiel #6
0
    def __init__(self, shell_name='pypsi', width=80, exit_rc=-1024, ctx=None):
        '''
        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a command
            when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.real_stdout = sys.stdout
        self.real_stdin = sys.stdin
        self.real_stderr = sys.stderr
        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.on_shell_ready()
Beispiel #7
0
 def __init__(self, name, description, steps=None):
     '''
     :param str name: the prompt wizard name to display to the user
     :param str description: a short description of what the wizard does
     :param list steps: a list of :class:`WizardStep` objects
     '''
     self.name = name
     self.description = description
     self.steps = steps
     self.values = Namespace()
     self.parser = StatementParser()
Beispiel #8
0
    def __init__(self, shell_name='pypsi', width=79, exit_rc=-1024, ctx=None):
        '''
        Subclasses need to call the Shell constructor to properly initialize
        it.

        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a
            command when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.backup_stdout = None
        self.backup_stdin = None
        self.backup_stderr = None
        self.backup_print = None

        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.eof_is_sigint = False
        self._backup_completer = readline.get_completer()

        self.bootstrap()

        self.on_shell_ready()
Beispiel #9
0
    def get_completions(self, line, prefix):
        '''
        Get the list of completions given a line buffer and a prefix.

        :param str line: line buffer content up to cursor
        :param str prefix: readline prefix token
        :returns list[str]: list of completions
        '''
        try:
            parser = StatementParser(TabCompletionFeatures(self.features))
            tokens = parser.tokenize(line)
            parser.clean_escapes(tokens)

            cmd_name = ""
            loc = None
            args = []
            next_arg = True
            ret = []
            in_quote = None
            for token in tokens:
                if isinstance(token, StringToken):
                    in_quote = token.quote if token.open_quote else None
                    if not cmd_name:
                        cmd_name = token.text
                        loc = 'name'
                    elif loc == 'name':
                        cmd_name += token.text
                    else:
                        if next_arg:
                            args.append(token.text)
                            next_arg = False
                        else:
                            args[-1] += token.text
                elif isinstance(token, OperatorToken):
                    in_quote = None
                    if token.operator in ('|', ';', '&&', '||'):
                        cmd_name = None
                        args = []
                        next_arg = True
                    elif token.operator in ('>', '<', '>>'):
                        loc = 'path'
                        args = []
                elif isinstance(token, WhitespaceToken):
                    in_quote = None
                    if loc == 'name':
                        loc = None
                    next_arg = True

            if loc == 'path':
                ret = path_completer(''.join(args), prefix)
            elif not cmd_name or loc == 'name':
                if is_path_prefix(cmd_name):
                    ret = path_completer(cmd_name, prefix)
                else:
                    ret = self.get_command_name_completions(cmd_name)
            else:
                if cmd_name not in self.commands:
                    ret = []
                else:
                    if next_arg:
                        args.append('')

                    cmd = self.commands[cmd_name]
                    ret = cmd.complete(self, args, prefix)

            ret = self._clean_completions(ret, in_quote)
        except:
            ret = []

        return ret
Beispiel #10
0
    def execute(self, raw):
        '''
        Parse and execute a statement.

        :param str raw: the raw command line to parse.
        :param function input: a function that returns a string,
                                overrides default input function (stdin).
        :returns int: the return code of the statement.
        '''

        parser = StatementParser(self.features)
        input_complete = False
        while not input_complete:
            text = self.preprocess(raw, 'input')
            if text is None:
                return None

            try:
                tokens = parser.tokenize(text)
            except (UnclosedQuotationError, TrailingEscapeError):
                input_complete = False
            else:
                # Parsing succeeded, break out of the input loop
                input_complete = True

            if not input_complete:
                # This is a multiline input
                try:
                    # hide prompt if reading from a file
                    raw = input("> " if sys.stdin.isatty() else '')
                except (EOFError, KeyboardInterrupt) as e:
                    self.on_input_canceled()
                    raise e

        tokens = self.on_tokenize(tokens, 'input')
        statement = None

        if not tokens:
            return None

        try:
            statement = parser.build(tokens)
        except StatementSyntaxError as e:
            self.error(str(e))
            return 1

        rc = None

        if not statement:
            # The line was empty, a comment, or just contained whitespace.
            return rc

        # Setup the invocations
        for invoke in statement:
            try:
                # Open any and all I/O redirections and resolve the pypsi
                # command.
                invoke.setup(self)
            except Exception as e:
                for sub in statement:
                    sub.close_streams()

                if isinstance(e, (IORedirectionError, CommandNotFoundError)):
                    # pypsi can handle I/O redirection and command not found
                    # errors, these are not fatal.
                    self.error(str(e))
                    return -1
                # Unhandled fatal exception, re-raise it
                raise

        # Current pipe being built
        pipe = []

        # Process the statement
        for invoke in statement:
            if invoke.chain_pipe():
                # We are in a pipe
                pipe.append(invoke)
            else:
                # We are not in a pipe

                if pipe:
                    # We have a pipe built that needs to be executed.
                    # Create the invocation threads for the pipe.
                    threads, stdin = self.create_pipe_threads(pipe)
                    # Reset the building pipe
                    pipe = []
                    # Set the current invocation's stdin to the last
                    # invocation's stdout.
                    invoke.stdin = stdin
                else:
                    # We were not in a pipe
                    threads = []

                # Start all the pipe threads, if we are processing a pipe
                for t in threads:
                    t.start()

                # Execute the invocation in the current thread.
                try:
                    rc = invoke(self)
                except Exception as e:
                    # Unhandled exception, stop all threads if any are running.
                    for t in threads:
                        t.stop()

                    # Wait for threads to terminate.
                    try:
                        for t in threads:
                            t.join()
                    except:
                        # Something went wrong or a KeyboardInterrupt was
                        # issued. Stop waiting for threads to terminate.
                        pass

                    # Print thread-specific unhandled exceptions.
                    for t in threads:
                        if t.exc_info:
                            if t.exc_info[0] == OSError:
                                msg = t.exc_info[1].strerror
                            else:
                                msg = str(t.exc_info[1])

                            print(AnsiCodes.red,
                                  t.invoke.name,
                                  ": ",
                                  msg,
                                  AnsiCodes.reset,
                                  sep='')

                    if isinstance(e, KeyboardInterrupt):
                        # Ctrl+c was entered
                        print()
                        rc = -1
                    elif isinstance(e, SystemExit):
                        # The command is requesting to exit the shell.
                        rc = e.code  # pylint: disable=no-member
                        print("exiting....")
                        self.running = False
                    elif isinstance(e, RuntimeError):
                        # The command was aborted by a generic exception.
                        self.error("command aborted: " + str(e))
                        rc = -1
                    else:
                        # Unhandled fatal exception, re-raise it
                        raise

                self.errno = rc

                # Check if the statement's next invocation be executed.
                if not invoke.should_continue(rc):
                    break

        return rc
Beispiel #11
0
class Shell(object):
    '''
    The command line interface that the user interacts with. All shell's need
    to inherit this base class.
    '''

    def __init__(self, shell_name='pypsi', width=79, exit_rc=-1024, ctx=None):
        '''
        Subclasses need to call the Shell constructor to properly initialize
        it.

        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a
            command when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.backup_stdout = None
        self.backup_stdin = None
        self.backup_stderr = None
        self.backup_print = None

        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.eof_is_sigint = False
        self._backup_completer = readline.get_completer()

        self.bootstrap()

        self.on_shell_ready()

    def bootstrap(self):
        import builtins
        if not isinstance(sys.stdout, ThreadLocalStream):
            self.backup_stdout = sys.stdout
            sys.stdout = ThreadLocalStream(sys.stdout, width=self.width)

        if not isinstance(sys.stderr, ThreadLocalStream):
            self.backup_stderr = sys.stderr
            sys.stderr = ThreadLocalStream(sys.stderr, width=self.width)

        if not isinstance(sys.stdin, ThreadLocalStream):
            self.backup_stdin = sys.stdin
            sys.stdin = ThreadLocalStream(sys.stdin)

        if builtins.print != pypsi_print:
            self.backup_print = print
            builtins.print = pypsi_print

    def restore(self):
        if self.backup_stdout:
            sys.stdout = self.backup_stdout

        if self.backup_stderr:
            sys.stderr = self.backup_stderr

        if self.backup_stdin:
            sys.stdin = self.backup_stdin

        if self.backup_print:
            import builtins
            builtins.print = self.backup_print

    def register_base_plugins(self):
        '''
        Register all base plugins that are defined.
        '''

        cls = self.__class__
        for name in dir(cls):
            attr = getattr(cls, name)
            if isinstance(attr, Command) or isinstance(attr, Plugin):
                self.register(attr)

    def register(self, obj):
        '''
        Register a :class:`~pypsi.core.Command` or a
        :class:`~pypsi.core.Plugin`.
        '''

        if isinstance(obj, Command):
            self.commands[obj.name] = obj

        if isinstance(obj, Plugin):
            self.plugins.append(obj)
            if obj.preprocess is not None:
                self.preprocessors.append(obj)
                self.preprocessors = sorted(self.preprocessors,
                                            key=lambda x: x.preprocess)
            if obj.postprocess is not None:
                self.postprocessors.append(obj)
                self.postprocessors = sorted(self.postprocessors,
                                             key=lambda x: x.postprocess)

        obj.setup(self)
        return 0

    def on_shell_ready(self):
        '''
        Hook that is called after the shell has been created.
        '''

        return 0

    def on_cmdloop_begin(self):
        '''
        Hook that is called once the :meth:`cmdloop` function is called.
        '''

        return 0

    def on_cmdloop_end(self):
        '''
        Hook that is called once the :meth:`cmdloop` has ended.
        '''

        return 0

    def get_current_prompt(self):
        if callable(self.prompt):
            prompt = self.prompt()
        else:
            prompt = self.prompt

        return self.preprocess_single(prompt, 'prompt')

    def set_readline_completer(self):
        if readline.get_completer() != self.complete:
            readline.parse_and_bind("tab: complete")
            self._backup_completer = readline.get_completer()
            readline.set_completer(self.complete)

    def reset_readline_completer(self):
        if readline.get_completer() == self.complete:
            readline.set_completer(self._backup_completer)

    def cmdloop(self):
        '''
        Begin the input processing loop where the user will be prompted for
        input.
        '''

        self.running = True
        self.set_readline_completer()
        self.on_cmdloop_begin()
        rc = 0
        try:
            while self.running:
                try:
                    raw = input(self.get_current_prompt())
                except EOFError:
                    if self.eof_is_sigint:
                        print()
                        for pp in self.preprocessors:
                            pp.on_input_canceled(self)
                    else:
                        self.running = False
                        print("exiting....")
                except KeyboardInterrupt:
                    print()
                    for pp in self.preprocessors:
                        pp.on_input_canceled(self)
                else:
                    try:
                        rc = self.execute(raw)
                        for pp in self.postprocessors:
                            pp.on_statement_finished(self, rc)
                    except SystemExit as e:
                        rc = e.code
                        print("exiting....")
                        self.running = False
        finally:
            self.on_cmdloop_end()
            self.reset_readline_completer()
        return rc

    def error(self, msg):
        print(
            AnsiCodes.red, self.shell_name, ": ", msg, AnsiCodes.reset,
            file=sys.stderr, sep=''
        )

    def mkpipe(self):
        r, w = os.pipe()
        return (
            os.fdopen(r, 'r'),
            os.fdopen(w, 'w')
        )

    def execute(self, raw):
        '''
        Parse and execute a statement.

        :param str raw: the raw command line to parse.
        :returns int: the return code of the statement.
        '''

        tokens = self.preprocess(raw, 'input')
        if not tokens:
            return 0

        statement = None

        try:
            statement = self.parser.build(tokens)
        except StatementSyntaxError as e:
            self.error(str(e))
            return 1

        rc = None

        if not statement:
            # The line was empty, a comment, or just contained whitespace.
            return rc

        # Setup the invocations
        for invoke in statement:
            try:
                # Open any and all I/O redirections and resolve the pypsi
                # comand.
                invoke.setup(self)
            except Exception as e:
                for sub in statement:
                    sub.close_streams()

                if isinstance(e, (IORedirectionError, CommandNotFoundError)):
                    # pypsi can handle I/O redirection and command not found
                    # errors, these are not fatal.
                    self.error(str(e))
                    return -1
                else:
                    # Unhandled fatal exception, re-raise it
                    raise

        # Current pipe being built
        pipe = []

        # Process the statement
        for invoke in statement:
            if invoke.chain_pipe():
                # We are in a pipe
                pipe.append(invoke)
            else:
                # We are not in a pipe

                if pipe:
                    # We have a pipe built that needs to be executed.
                    # Create the invocation threads for the pipe.
                    threads, stdin = self.create_pipe_threads(pipe)
                    # Reset the building pipe
                    pipe = []
                    # Set the current invocation's stdin to the last
                    # invocation's stdout.
                    invoke.stdin = stdin
                else:
                    # We were not in a pipe
                    threads = []

                # Start all the pipe threads, if we are processing a pipe
                for t in threads:
                    t.start()

                # Execute the invocation in the current thread.
                try:
                    rc = invoke(self)
                except Exception as e:
                    # Unhandled exception, stop all threads if any are running.
                    for t in threads:
                        t.stop()

                    # Wait for threads to terminate.
                    try:
                        for t in threads:
                            t.join()
                    except:
                        # Something went wrong or a KeyboardInterrupt was
                        # issued. Stop waiting for threads to terminate.
                        pass

                    # Print thread-specific unhandled exceptions.
                    for t in threads:
                        if t.exc_info:
                            if t.exc_info[0] == OSError:
                                msg = t.exc_info[1].strerror
                            else:
                                msg = str(t.exc_info[1])

                            print(
                                AnsiCodes.red, t.invoke.name, ": ", msg,
                                AnsiCodes.reset, sep=''
                            )

                    if isinstance(e, KeyboardInterrupt):
                        # Ctrl+c was entered
                        print()
                        rc = -1
                    elif isinstance(e, SystemExit):
                        # The command is requesting to exit the shell.
                        rc = e.code
                        print("exiting....")
                        self.running = False
                    elif isinstance(e, RuntimeError):
                        # The command was aborted by a generic exception.
                        self.error("command aborted: "+str(e))
                        rc = -1
                    else:
                        # Unhandled fatal exception, re-raise it
                        raise

                self.errno = rc

                # Check if the statement's next invocation be executed.
                if not invoke.should_continue(rc):
                    break

        return rc

    def create_pipe_threads(self, pipe):
        '''
        Given a pipe (list of :class:`~pypsi.cmdline.CommandInvocation`
        objects) create a thread to execute for each invocation.

        :returns tuple: a tuple containing the list of threads
            (:class:`~pypsi.pipes.CommandThread`) and the last invocation's
            stdout stream.
        '''

        threads = []
        stdin = None
        for invoke in pipe:
            next_stdin, stdout = self.mkpipe()

            t = InvocationThread(self, invoke, stdin=stdin, stdout=stdout)
            threads.append(t)

            stdin = next_stdin

        return threads, stdin

    def preprocess(self, raw, origin):
        for pp in self.preprocessors:
            raw = pp.on_input(self, raw)
            if raw is None:
                return None

        tokens = self.parser.tokenize(raw)
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if tokens is None:
                break

        return tokens

    def preprocess_single(self, raw, origin):
        tokens = [StringToken(0, raw, quote='"')]
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if not tokens:
                break

        if tokens:
            self.parser.clean_escapes(tokens)
            ret = ''
            for token in tokens:
                ret += token.text
            return ret
        return ''

    def get_completions(self, line, prefix):
        tokens = self.parser.tokenize(line)
        cmd_name = ""
        loc = None
        args = []
        next_arg = True
        ret = []
        for token in tokens:
            if isinstance(token, StringToken):
                if not cmd_name:
                    cmd_name = token.text
                    loc = 'name'
                elif loc == 'name':
                    cmd_name += token.text
                else:
                    if next_arg:
                        args.append(token.text)
                        next_arg = False
                    else:
                        args[-1] += token.text
            elif isinstance(token, OperatorToken):
                if token.operator in ('|', ';', '&&', '||'):
                    cmd_name = None
                    args = []
                    next_arg = True
                elif token.operator in ('>', '<', '>>'):
                    loc = 'path'
                    args = []
            elif isinstance(token, WhitespaceToken):
                if loc == 'name':
                    loc = None
                next_arg = True

        if loc == 'path':
            ret = path_completer(''.join(args))
        elif not cmd_name or loc == 'name':
            if is_path_prefix(cmd_name):
                ret = path_completer(cmd_name)
            else:
                ret = self.get_command_name_completions(cmd_name)
        else:
            if cmd_name not in self.commands:
                ret = []
            else:
                if next_arg:
                    args.append('')

                cmd = self.commands[cmd_name]
                ret = cmd.complete(self, args, prefix)
        return ret

    def get_command_name_completions(self, prefix):
        return [cmd for cmd in self.commands if cmd.startswith(prefix)]

    def complete(self, text, state):
        if state == 0:
            self.completion_matches = []
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''
            line = line[:endidx]
            self.completion_matches = self.get_completions(line, prefix)

        if state < len(self.completion_matches):
            return self.completion_matches[state]
        return None

    def print_completion_matches(self, substitution, matches, max_len):
        print("substitution:", substitution)
        print("matches:     ", matches)
        print("max_len:     ", max_len)
Beispiel #12
0
 def __init__(self, name, description, steps=None):
     self.name = name
     self.description = description
     self.steps = steps
     self.values = Namespace()
     self.parser = StatementParser()
Beispiel #13
0
class Shell(object):
    '''
    The command line interface that the user interacts with. All shell's need to
    inherit this base class.
    '''
    def __init__(self, shell_name='pypsi', width=80, exit_rc=-1024, ctx=None):
        '''
        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a command
            when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.real_stdout = sys.stdout
        self.real_stdin = sys.stdin
        self.real_stderr = sys.stderr
        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.on_shell_ready()

    def register_base_plugins(self):
        '''
        Register all base plugins that are defined.
        '''

        cls = self.__class__
        for name in dir(cls):
            attr = getattr(cls, name)
            if isinstance(attr, Command) or isinstance(attr, Plugin):
                self.register(attr)

    def register(self, obj):
        '''
        Register a :class:`~pypsi.base.Command` or a :class:`~pypsi.base.Plugin`.
        '''

        if isinstance(obj, Command):
            self.commands[obj.name] = obj

        if isinstance(obj, Plugin):
            self.plugins.append(obj)
            if obj.preprocess is not None:
                self.preprocessors.append(obj)
                self.preprocessors = sorted(self.preprocessors,
                                            key=lambda x: x.preprocess)
            if obj.postprocess is not None:
                self.postprocessors.append(obj)
                self.postprocessors = sorted(self.postprocessors,
                                             key=lambda x: x.postprocess)

        obj.setup(self)
        return 0

    def on_shell_ready(self):
        return 0

    def on_cmdloop_begin(self):
        return 0

    def on_cmdloop_end(self):
        return 0

    def get_current_prompt(self):
        return self.preprocess_single(self.prompt, 'prompt')

    def cmdloop(self):
        self.running = True
        self.on_cmdloop_begin()
        readline.parse_and_bind("tab: complete")
        old_completer = readline.get_completer()
        readline.set_completer(self.complete)
        rc = 0
        try:
            while self.running:
                try:
                    raw = input(self.get_current_prompt())
                except EOFError:
                    self.running = False
                    print("exiting....")
                except KeyboardInterrupt:
                    print()
                    for pp in self.preprocessors:
                        pp.on_input_canceled(self)
                else:
                    rc = self.execute(raw)
        finally:
            self.on_cmdloop_end()
            readline.set_completer(old_completer)
        return rc

    def execute(self, raw, ctx=None):
        if not ctx:
            ctx = StatementContext()

        tokens = self.preprocess(raw, 'input')
        if not tokens:
            return 0

        statement = None

        try:
            statement = self.parser.build(tokens, ctx)
        except StatementSyntaxError as e:
            self.error(self.shell_name, ": ", str(e), '\n')
            return 1

        rc = None
        if statement:
            (params, op) = statement.next()
            while params:
                cmd = None
                if params.name in self.commands:
                    cmd = self.commands[params.name]
                elif self.fallback_cmd:
                    cmd = self.fallback_cmd.fallback(self, params.name,
                                                     params.args,
                                                     statement.ctx)

                if not cmd:
                    statement.ctx.reset_io()
                    self.error(self.shell_name, ": ", params.name,
                               ": command not found\n")
                    return 1

                statement.ctx.setup_io(cmd, params, op)
                rc = self.run_cmd(cmd, params, statement.ctx)
                if op == '||':
                    if rc == 0:
                        statement.ctx.reset_io()
                        return 0
                elif op == '&&' or op == '|':
                    if rc != 0:
                        statement.ctx.reset_io()
                        return rc

                (params, op) = statement.next()

        statement.ctx.reset_io()

        for pp in self.postprocessors:
            pp.on_statement_finished(self)

        return rc

    def run_cmd(self, cmd, params, ctx):
        self.errno = cmd.run(self, params.args, ctx)
        return self.errno

    def preprocess(self, raw, origin):
        for pp in self.preprocessors:
            raw = pp.on_input(self, raw)
            if raw is None:
                return None

        tokens = self.parser.tokenize(raw)
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if tokens is None:
                break

        return tokens

    def preprocess_single(self, raw, origin):
        tokens = [StringToken(0, raw, quote='"')]
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if not tokens:
                break

        if tokens:
            self.parser.clean_escapes(tokens)
            ret = ''
            for token in tokens:
                ret += token.text
            return ret
        return ''

    def get_completions(self, line, prefix):
        tokens = self.parser.tokenize(line)
        cmd_name = None
        loc = None
        args = []
        next_arg = True
        prev = None
        ret = []
        for token in tokens:
            if isinstance(token, StringToken):
                if not cmd_name:
                    cmd_name = token.text
                    loc = 'name'
                elif loc == 'name':
                    cmd_name += token.text
                else:
                    if next_arg:
                        args.append(token.text)
                        next_arg = False
                    else:
                        args[-1] += token.text
            elif isinstance(token, OperatorToken):
                if token.operator in ('|', ';', '&&', '||'):
                    cmd_name = None
                    args = []
                    next_arg = True
                elif token.operator in ('>', '<', '>>'):
                    loc = 'path'
                    args = []
            elif isinstance(token, WhitespaceToken):
                if loc == 'name':
                    loc = None
                next_arg = True
            prev = token

        if loc == 'path':
            ret = path_completer(self, args, prefix)
        elif not cmd_name or loc == 'name':
            ret = [cmd for cmd in self.commands if cmd.startswith(prefix)]
        else:
            if cmd_name not in self.commands:
                ret = []
            else:
                if next_arg:
                    args.append('')

                cmd = self.commands[cmd_name]
                ret = cmd.complete(self, args, prefix)
        return ret

    def complete(self, text, state):
        if state == 0:
            self.completion_matches = []
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''
            line = line[:endidx]
            self.completion_matches = self.get_completions(line, prefix)

        if state < len(self.completion_matches):
            return self.completion_matches[state]
        return None

    def print_completion_matches(self, substitution, matches, max_len):
        print("substitution:", substitution)
        print("matches:     ", matches)
        print("max_len:     ", max_len)
Beispiel #14
0
class PromptWizard(object):
    '''
    A user input prompt wizards.

    PromptWizards will walk the user through a series of questions
    (:class:`WizardStep`) and accept input. The user may at any time enter the
    ``?`` key to get help regarding the current step.

    Each step can have validators that determine if a value is valid before
    proceeding to the next step. Also, each step can have a default value that
    is saved if the user hits ``Return`` with no input.

    Once complete, the wizard will return a :class:`~pypsi.namespace.Namespace`
    object that contains all the user's answers. Each step contains an ``id``
    attribute that determines what variable is set in the returned namespace.

    For example, a step may have an id of ``"ip_addr"``. When the user enters
    ``"192.168.0.1"`` for this step, the input can be retrieved through the
    namespace's ``ip_addr`` attribute.
    '''
    def __init__(self, name, description, steps=None):
        '''
        :param str name: the prompt wizard name to display to the user
        :param str description: a short description of what the wizard does
        :param list steps: a list of :class:`WizardStep` objects
        '''
        self.name = name
        self.description = description
        self.steps = steps
        self.values = Namespace()
        self.parser = StatementParser()

    def run(self, shell, print_header=True):
        '''
        Execute the wizard, prompting the user for input.

        :param pypsi.shell.Shell shell: the active shell
        :returns: a :class:`~pypsi.namespace.Namespace` object containing all
         the answers on success, :const:`None` if the user exited the wizard
        '''

        self.old_completer = readline.get_completer()
        readline.set_completer(self.complete)

        if print_header:
            print(title_str("Entering " + self.name + " Wizard",
                            width=shell.width,
                            box=True,
                            align='center'),
                  '\n',
                  self.description,
                  '\n\n',
                  "To exit, enter either Ctrl+C, Ctrl+D, or 'quit'. For help "
                  "about the current step, enter 'help' or '?'.",
                  sep='')

        for step in self.steps:
            self.active_step = step
            valid = False
            while not valid:
                print()
                raw = None
                prompt = step.name
                if step.default is not None:
                    d = step.default
                    if callable(d):
                        d = d(self.values)
                    prompt += ' [{}]'.format(d)
                prompt += ': '
                try:
                    raw = input(prompt)
                except (KeyboardInterrupt, EOFError):
                    print()
                    print(AnsiCodes.red,
                          "Wizard canceled",
                          AnsiCodes.reset,
                          sep='')
                    readline.set_completer(self.old_completer)
                    return None

                if raw.lower() == 'quit':
                    print(AnsiCodes.red,
                          "Exiting wizard",
                          AnsiCodes.reset,
                          sep='')
                    readline.set_completer(self.old_completer)
                    return None
                elif raw.lower() in ('?', 'help'):
                    print(step.help)
                else:
                    if not raw.strip() and step.default is not None:
                        raw = step.default

                    try:
                        value = step.validate(self.values, raw)
                    except ValueError as e:
                        print(AnsiCodes.red,
                              "Error: ",
                              str(e),
                              AnsiCodes.reset,
                              sep='')
                        print(AnsiCodes.yellow,
                              step.name,
                              ": ",
                              step.help,
                              AnsiCodes.reset,
                              sep='')
                    else:
                        self.values[step.id] = value
                        valid = True

        readline.set_completer(self.old_completer)
        return self.values

    def complete(self, text, state):
        '''
        Tab complete for the current step.
        '''

        if state == 0:
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''

            line = line[:endidx]
            tokens = self.parser.tokenize(line)
            tokens = self.parser.condense(tokens)
            args = [t.text for t in tokens if isinstance(t, StringToken)]
            self.completions = self.active_step.complete(self, args, prefix)

        if state < len(self.completions):
            return self.completions[state]
        else:
            return None
Beispiel #15
0
class Shell(object):
    '''
    The command line interface that the user interacts with. All shell's need to
    inherit this base class.
    '''

    def __init__(self, shell_name='pypsi', width=80, exit_rc=-1024, ctx=None):
        '''
        :param str shell_name: the name of the shell; used in error messages
        :param int exit_rc: the exit return code that is returned from a command
            when the shell needs to end execution
        :param pypsi.namespace.Namespace ctx: the base context
        '''
        self.real_stdout = sys.stdout
        self.real_stdin = sys.stdin
        self.real_stderr = sys.stderr
        self.width = width
        self.shell_name = shell_name
        self.exit_rc = exit_rc
        self.errno = 0
        self.commands = {}
        self.preprocessors = []
        self.postprocessors = []
        self.plugins = []
        self.prompt = "{name} )> ".format(name=shell_name)
        self.ctx = ctx or Namespace()

        self.parser = StatementParser()
        self.default_cmd = None
        self.register_base_plugins()
        self.fallback_cmd = None

        self.on_shell_ready()

    def register_base_plugins(self):
        '''
        Register all base plugins that are defined.
        '''

        cls = self.__class__
        for name in dir(cls):
            attr = getattr(cls, name)
            if isinstance(attr, Command) or isinstance(attr, Plugin):
                self.register(attr)

    def register(self, obj):
        '''
        Register a :class:`~pypsi.base.Command` or a :class:`~pypsi.base.Plugin`.
        '''

        if isinstance(obj, Command):
            self.commands[obj.name] = obj

        if isinstance(obj, Plugin):
            self.plugins.append(obj)
            if obj.preprocess is not None:
                self.preprocessors.append(obj)
                self.preprocessors = sorted(self.preprocessors, key=lambda x: x.preprocess)
            if obj.postprocess is not None:
                self.postprocessors.append(obj)
                self.postprocessors = sorted(self.postprocessors, key=lambda x: x.postprocess)

        obj.setup(self)
        return 0

    def on_shell_ready(self):
        return 0

    def on_cmdloop_begin(self):
        return 0

    def on_cmdloop_end(self):
        return 0

    def get_current_prompt(self):
        return self.preprocess_single(self.prompt, 'prompt')

    def cmdloop(self):
        self.running = True
        self.on_cmdloop_begin()
        readline.parse_and_bind("tab: complete")
        old_completer = readline.get_completer()
        readline.set_completer(self.complete)
        rc = 0
        try:
            while self.running:
                try:
                    raw = input(self.get_current_prompt())
                except EOFError:
                    self.running = False
                    print("exiting....")
                except KeyboardInterrupt:
                    print()
                    for pp in self.preprocessors:
                        pp.on_input_canceled(self)
                else:
                    rc = self.execute(raw)
        finally:
            self.on_cmdloop_end()
            readline.set_completer(old_completer)
        return rc

    def execute(self, raw, ctx=None):
        if not ctx:
            ctx = StatementContext()

        tokens = self.preprocess(raw, 'input')
        if not tokens:
            return 0

        statement = None

        try:
            statement = self.parser.build(tokens, ctx)
        except StatementSyntaxError as e:
            self.error(self.shell_name, ": ", str(e), '\n')
            return 1

        rc = None
        if statement:
            (params, op) = statement.next()
            while params:
                cmd = None
                if params.name in self.commands:
                    cmd = self.commands[params.name]
                elif self.fallback_cmd:
                    cmd = self.fallback_cmd.fallback(self, params.name, params.args, statement.ctx)

                if not cmd:
                    statement.ctx.reset_io()
                    self.error(self.shell_name, ": ", params.name, ": command not found\n")
                    return 1

                statement.ctx.setup_io(cmd, params, op)
                rc = self.run_cmd(cmd, params, statement.ctx)
                if op == '||':
                    if rc == 0:
                        statement.ctx.reset_io()
                        return 0
                elif op == '&&' or op == '|':
                    if rc != 0:
                        statement.ctx.reset_io()
                        return rc

                (params, op) = statement.next()

        statement.ctx.reset_io()

        for pp in self.postprocessors:
            pp.on_statement_finished(self)

        return rc

    def run_cmd(self, cmd, params, ctx):
        self.errno = cmd.run(self, params.args, ctx)
        return self.errno

    def preprocess(self, raw, origin):
        for pp in self.preprocessors:
            raw = pp.on_input(self, raw)
            if raw is None:
                return None

        tokens = self.parser.tokenize(raw)
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if tokens is None:
                break

        return tokens

    def preprocess_single(self, raw, origin):
        tokens = [StringToken(0, raw, quote='"')]
        for pp in self.preprocessors:
            tokens = pp.on_tokenize(self, tokens, origin)
            if not tokens:
                break

        if tokens:
            self.parser.clean_escapes(tokens)
            ret = ''
            for token in tokens:
                ret += token.text
            return ret
        return ''

    def get_completions(self, line, prefix):
        tokens = self.parser.tokenize(line)
        cmd_name = None
        loc = None
        args = []
        next_arg = True
        prev = None
        ret = []
        for token in tokens:
            if isinstance(token, StringToken):
                if not cmd_name:
                    cmd_name = token.text
                    loc = 'name'
                elif loc == 'name':
                    cmd_name += token.text
                else:
                    if next_arg:
                        args.append(token.text)
                        next_arg = False
                    else:
                        args[-1] += token.text
            elif isinstance(token, OperatorToken):
                if token.operator in ('|', ';', '&&', '||'):
                    cmd_name = None
                    args = []
                    next_arg = True
                elif token.operator in ('>', '<', '>>'):
                    loc = 'path'
                    args = []
            elif isinstance(token, WhitespaceToken):
                if loc == 'name':
                    loc = None
                next_arg = True
            prev = token

        if loc == 'path':
            ret = path_completer(self, args, prefix)
        elif not cmd_name or loc == 'name':
            ret = [cmd for cmd in self.commands if cmd.startswith(prefix)]
        else:
            if cmd_name not in self.commands:
                ret = []
            else:
                if next_arg:
                    args.append('')

                cmd = self.commands[cmd_name]
                ret = cmd.complete(self, args, prefix)
        return ret

    def complete(self, text, state):
        if state == 0:
            self.completion_matches = []
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''
            line = line[:endidx]
            self.completion_matches = self.get_completions(line, prefix)

        if state < len(self.completion_matches):
            return self.completion_matches[state]
        return None

    def print_completion_matches(self, substitution, matches, max_len):
        print("substitution:", substitution)
        print("matches:     ", matches)
        print("max_len:     ", max_len)
Beispiel #16
0
class PromptWizard(object):

    def __init__(self, name, description, steps=None):
        self.name = name
        self.description = description
        self.steps = steps
        self.values = Namespace()
        self.parser = StatementParser()

    def run(self, shell):
        self.old_completer = readline.get_completer()
        readline.set_completer(self.complete)
        print(
            title_str("Entering " + self.name + " Wizard", width=shell.width, box=True, align='center'),
            '\n',
            self.description, '\n\n',
            "To exit, enter either Ctrl+C, Ctrl+D, or 'quit'. For help "
            "about the current step, enter 'help' or '?'.",
            sep=''
        )

        running = True
        for step in self.steps:
            self.active_step = step
            valid = False
            while not valid:
                raw = None
                prompt = step.name
                if step.default is not None:
                    d = step.default
                    if callable(d):
                        d = d(self.values)
                    prompt += ' [{}]'.format(d)
                prompt += ': '
                try:
                    raw = input(prompt)
                except (KeyboardInterrupt, EOFError):
                    print()
                    print(AnsiCodes.red, "Wizard canceled", AnsiCodes.reset, sep='')
                    readline.set_completer(self.old_completer)
                    return None

                if raw.lower() == 'quit':
                    print(AnsiCodes.red, "Exiting wizard", AnsiCodes.reset, sep='')
                    readline.set_completer(self.old_completer)
                    return None
                elif raw.lower() in ('?', 'help'):
                    print(step.help)
                else:
                    if not raw.strip() and step.default is not None:
                        raw = step.default

                    try:
                        value = step.validate(self.values, raw)
                    except ValueError as e:
                        print(AnsiCodes.red, "Error: ", str(e), AnsiCodes.reset, sep='')
                        print(AnsiCodes.yellow, step.name, ": ", step.help, sep='')
                    else:
                        self.values[step.id] = value
                        valid = True

        readline.set_completer(self.old_completer)
        return self.values

    def complete(self, text, state):
        if state == 0:
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''

            line = line[:endidx]
            tokens = self.parser.tokenize(line)
            tokens = self.parser.condense(tokens)
            args = [t.text for t in tokens if isinstance(t, StringToken)]
            self.completions = self.active_step.complete(self, args, prefix)
        return self.completions[state] if state < len(self.completions) else None
Beispiel #17
0
class PromptWizard(object):
    '''
    A user input prompt wizards.

    PromptWizards will walk the user through a series of questions
    (:class:`WizardStep`) and accept input. The user may at any time enter the
    ``?`` key to get help regarding the current step.

    Each step can have validators that determine if a value is valid before
    proceeding to the next step. Also, each step can have a default value that
    is saved if the user hits ``Return`` with no input.

    Once complete, the wizard will return a :class:`~pypsi.namespace.Namespace`
    object that contains all the user's answers. Each step contains an ``id``
    attribute that determines what variable is set in the returned namespace.

    For example, a step may have an id of ``"ip_addr"``. When the user enters
    ``"192.168.0.1"`` for this step, the input can be retrieved through the
    namespace's ``ip_addr`` attribute.
    '''

    def __init__(self, name, description, steps=None):
        '''
        :param str name: the prompt wizard name to display to the user
        :param str description: a short description of what the wizard does
        :param list steps: a list of :class:`WizardStep` objects
        '''
        self.name = name
        self.description = description
        self.steps = steps
        self.values = Namespace()
        self.parser = StatementParser()

    def run(self, shell, print_header=True):
        '''
        Execute the wizard, prompting the user for input.

        :param pypsi.shell.Shell shell: the active shell
        :returns: a :class:`~pypsi.namespace.Namespace` object containing all
         the answers on success, :const:`None` if the user exited the wizard
        '''

        self.old_completer = readline.get_completer()
        readline.set_completer(self.complete)

        if print_header:
            print(
                title_str("Entering " + self.name + " Wizard",
                          width=shell.width, box=True, align='center'),
                '\n',
                self.description, '\n\n',
                "To exit, enter either Ctrl+C, Ctrl+D, or 'quit'. For help "
                "about the current step, enter 'help' or '?'.", sep=''
            )

        for step in self.steps:
            self.active_step = step
            valid = False
            while not valid:
                print()
                raw = None
                prompt = step.name
                if step.default is not None:
                    d = step.default
                    if callable(d):
                        d = d(self.values)
                    prompt += ' [{}]'.format(d)
                prompt += ': '
                try:
                    raw = input(prompt)
                except (KeyboardInterrupt, EOFError):
                    print()
                    print(AnsiCodes.red, "Wizard canceled", AnsiCodes.reset,
                          sep='')
                    readline.set_completer(self.old_completer)
                    return None

                if raw.lower() == 'quit':
                    print(AnsiCodes.red, "Exiting wizard", AnsiCodes.reset,
                          sep='')
                    readline.set_completer(self.old_completer)
                    return None
                elif raw.lower() in ('?', 'help'):
                    print(step.help)
                else:
                    if not raw.strip() and step.default is not None:
                        raw = step.default

                    try:
                        value = step.validate(self.values, raw)
                    except ValueError as e:
                        print(AnsiCodes.red, "Error: ", str(e),
                              AnsiCodes.reset, sep='')
                        print(AnsiCodes.yellow, step.name, ": ", step.help,
                              AnsiCodes.reset, sep='')
                    else:
                        self.values[step.id] = value
                        valid = True

        readline.set_completer(self.old_completer)
        return self.values

    def complete(self, text, state):
        '''
        Tab complete for the current step.
        '''

        if state == 0:
            begidx = readline.get_begidx()
            endidx = readline.get_endidx()
            line = readline.get_line_buffer()
            prefix = line[begidx:endidx] if line else ''

            line = line[:endidx]
            tokens = self.parser.tokenize(line)
            tokens = self.parser.condense(tokens)
            args = [t.text for t in tokens if isinstance(t, StringToken)]
            self.completions = self.active_step.complete(self, args, prefix)

        if state < len(self.completions):
            return self.completions[state]
        else:
            return None