def evaluate_command(self, cmd, args, raw): args = args or "" if cmd in self._command_registry: cmd_instance = self._command_registry.find_command(cmd) else: suggestions = find_approx( cmd, self._command_registry.get_all_commands_map()) if self._options.auto_execute_single_suggestions and len( suggestions) == 1: print() cprint( "Auto-correcting '{}' to '{}'".format(cmd, suggestions[0]), "red", attrs=["bold"], ) cmd_instance = self._command_registry.find_command( suggestions[0]) else: print() cprint( "Unknown Command '{}',{} type `help` to see all " "available commands".format(cmd, suggestions_msg(suggestions)), "red", attrs=["bold"], ) cmd_instance = None if cmd_instance is not None: try: ret = self._blacklist.is_blacklisted(cmd) if ret: return ret except Exception as e: err_message = ("Blacklist executing failed, " "all commands are available.\n" "{}".format(str(e))) cprint(err_message, "red") logging.error(err_message) try: catchall(self._usagelogger.pre_exec) result = cmd_instance.run_interactive(cmd, args, raw) catchall(self._usagelogger.post_exec, cmd, args, result, False) self._status_bar.set_last_command_status(result) return result except NotImplementedError as e: cprint("[NOT IMPLEMENTED]: {}".format(str(e)), "yellow", attrs=["bold"]) # not implemented error code return 99
def test_find_approx(self): commands_map = ["maintenance", "malloc", "move", "list"] # check levenshtein approximation self.assertEqual(find_approx("maintenanec", commands_map), ["maintenance"]) self.assertEqual(find_approx("ls", commands_map), ["list"]) # check prefix matching with single result self.assertEqual(find_approx("mal", commands_map), ["malloc"]) self.assertEqual(find_approx("maint", commands_map), ["maintenance"]) # check prefix matching and levenshtein don't generate duplicate suggestions self.assertEqual(find_approx("lis", commands_map), ["list"]) # check prefix matching with more than one result - should return none self.assertEqual(find_approx("ma", commands_map), ["maintenance", "malloc"]) self.assertEqual(find_approx("m", commands_map), ["maintenance", "malloc", "move"]) # check no results self.assertEqual(find_approx("a", commands_map), [])
def run_interactive(self, cmd, args, raw): try: args_metadata = self.metadata.arguments parsed = parser.parse(args, expect_subcommand=self.super_command) # prepare args dict parsed_dict = parsed.asDict() args_dict = parsed.kv.asDict() key_values = parsed.kv.asDict() command_name = cmd # if this is a super command, we need first to create an instance of # the class (fn) and pass the right arguments if self.super_command: subcommand = parsed_dict.get("__subcommand__") if not subcommand: cprint( "A sub-command must be supplied, valid values: " "{}".format(", ".join(self._get_subcommands())), "red", ) return 2 subcommands = self._get_subcommands() if subcommand not in subcommands: suggestions = find_approx(subcommand, subcommands) if (self._options.auto_execute_single_suggestions and len(suggestions) == 1): print() cprint( "Auto-correcting '{}' to '{}'".format( subcommand, suggestions[0]), "red", attrs=["bold"], ) subcommand = suggestions[0] else: print() cprint( "Invalid sub-command '{}',{}, " "valid sub-commands: {}".format( subcommand, suggestions_msg(suggestions), ", ".join(self._get_subcommands()), ), "red", attrs=["bold"], ) return 2 sub_inspection = self.subcommand_metadata(subcommand) instance, remaining_args = self._create_subcommand_obj( args_dict) assert instance args_dict = remaining_args key_values = copy.copy(args_dict) args_metadata = sub_inspection.arguments attrname = self._find_subcommand_attr(subcommand) command_name = subcommand assert attrname is not None fn = getattr(instance, attrname) else: # not a super-command, use use the function instead fn = self._fn positionals = parsed_dict[ "positionals"] if parsed.positionals != "" else [] # We only allow positionals for arguments that have positional=True # ِ We filter out the OrderedDict this way to ensure we don't lose the # order of the arguments. We also filter out arguments that have # been passed by name already. The order of the positional arguments # follows the order of the function definition. can_be_positional = self._positional_arguments( args_metadata, args_dict.keys()) if len(positionals) > len(can_be_positional): if len(can_be_positional) == 0: err = "This command does not support positional arguments" else: # We have more positionals than we should err = ( "This command only supports ({}) positional arguments, " "namely arguments ({}). You have passed {} arguments ({})" " instead!").format( len(can_be_positional), ", ".join(can_be_positional.keys()), len(positionals), ", ".join(str(x) for x in positionals), ) cprint(err, "red") return 2 # constuct key_value dict from positional arguments. args_from_positionals = { key: value for value, key in zip(positionals, can_be_positional) } # update the total arguments dict with the positionals args_dict.update(args_from_positionals) # Run some validations on number of arguments provided # do we have keys that are supplied in both positionals and # key_value style? duplicate_keys = set(args_from_positionals.keys()).intersection( set(key_values.keys())) if duplicate_keys: cprint( "Arguments '{}' have been passed already, cannot have" " duplicate keys".format(list(duplicate_keys)), "red", ) return 2 # check for verbosity override in kwargs ctx = context.get_context() old_verbose = ctx.args.verbose if "verbose" in args_dict: ctx.set_verbose(args_dict["verbose"]) del args_dict["verbose"] del key_values["verbose"] # do we have keys that we know nothing about? extra_keys = set(args_dict.keys()) - set(args_metadata) if extra_keys: cprint( "Unknown argument(s) {} were" " passed".format(list(extra_keys)), "magenta", ) return 2 # is there any required keys that were not resolved from positionals # nor key_values? missing_keys = set(args_metadata) - set(args_dict.keys()) if missing_keys: required_missing = [] for key in missing_keys: if not args_metadata[key].default_value_set: required_missing.append(key) if required_missing: cprint( "Missing required argument(s) {} for command" " {}".format(required_missing, command_name), "yellow", ) return 3 # convert expected types for arguments for key, value in args_dict.items(): target_type = args_metadata[key].type if target_type is None: target_type = str try: new_value = apply_typing(value, target_type) except ValueError: fn_name = function_to_str(target_type, False, False) cprint( 'Cannot convert value "{}" to {} on argument {}'. format(value, fn_name, key), "yellow", ) return 4 else: args_dict[key] = new_value # Validate that arguments with `choices` are supplied with the # acceptable values. for arg, value in args_dict.items(): choices = args_metadata[arg].choices if choices: # Validate the choices in the case of values and list of # values. if is_list_type(args_metadata[arg].type): bad_inputs = [v for v in value if v not in choices] if bad_inputs: cprint( f"Argument '{arg}' got an unexpected " f"value(s) '{bad_inputs}'. Expected one " f"or more of {choices}.", "red", ) return 4 elif value not in choices: cprint( f"Argument '{arg}' got an unexpected value " f"'{value}'. Expected one of " f"{choices}.", "red", ) return 4 # arguments appear to be fine, time to run the function try: # convert argument names back to match the function signature args_dict = { args_metadata[k].arg: v for k, v in args_dict.items() } if inspect.iscoroutinefunction(fn): loop = asyncio.get_event_loop() ret = loop.run_until_complete(fn(**args_dict)) else: ret = fn(**args_dict) ctx.set_verbose(old_verbose) except Exception as e: cprint("Error running command: {}".format(str(e)), "red") cprint("-" * 60, "yellow") traceback.print_exc(file=sys.stderr) cprint("-" * 60, "yellow") return 1 return ret except CommandParseError as e: cprint("Error parsing command", "red") cprint(cmd + " " + args, "white", attrs=["bold"]) cprint((" " * (e.col + len(cmd))) + "^", "white", attrs=["bold"]) cprint(str(e), "yellow") return 1