def get_completions(self) -> Iterable[Completion]: """ Returns a """ logger = logging.getLogger(f"{type(self).__name__}.get_completions") remaining = None try: parsed = parser.parse(self.doc.text, expect_subcommand=self.cmd.super_command) except parser.CommandParseError as e: parsed = e.partial_result remaining = e.remaining # This is a funky but reliable way to figure that last token we are # interested in manually parsing, This will return the last key=value # including if the value is a 'value', [list], or {dict} or combination # of these. This also matches positional arguments. if self.doc.char_before_cursor in " ]}": last_token = "" else: last_space = (self.doc.find_backwards(" ", in_current_line=True) or -1) last_token = self.doc.text[(last_space + 1):] # noqa # We pick the bigger match here. The reason we want to look into # remaining is to capture the state that we are in an open list, # dictionary, or any other value that may have spaces in it but fails # parsing (yet). if remaining and len(remaining) > len(last_token): last_token = remaining try: return self._prepare_args_completions(parsed_command=parsed, last_token=last_token) except Exception as e: logger.exception(str(e)) return []
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 sub_inspection = self.subcommand_metadata(subcommand) if not sub_inspection: cprint( "Invalid sub-command '{}', valid values: " "{}".format(subcommand, ", ".join(self._get_subcommands())), "red", ) return 2 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