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
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 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 ''
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 __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 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
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
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)
def __init__(self, name, description, steps=None): self.name = name self.description = description self.steps = steps self.values = Namespace() self.parser = StatementParser()
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)
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
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
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