diff --git a/milc/milc.py b/milc/milc.py index 3757a35..66a61b1 100644 --- a/milc/milc.py +++ b/milc/milc.py @@ -354,6 +354,46 @@ def find_config_file(self) -> Path: return Path(filedir, filename).resolve() + def _handle_deprecated(self, arg_name: str, kwargs: Dict[str, Any]) -> None: + """Called by self.argument: Mark an argument as deprecated, if necessary. + """ + if 'deprecated' in kwargs: + self._deprecated_arguments[arg_name] = kwargs['deprecated'] + if kwargs['help']: + kwargs['help'] += f" [Deprecated]: {kwargs['deprecated']}" + else: + kwargs['help'] = f"[Deprecated]: {kwargs['deprecated']}" + del kwargs['deprecated'] + + def _handle_arg_parsing(self, config_name: str, arg_name: str, args: Sequence[Any], kwargs: Dict[str, Any]) -> None: + """Called by self.argument: Parse this argument into the right datastructures. + """ + arg_strings = get_argument_strings(self._arg_parser, *args, **kwargs) + + if kwargs.get('arg_only'): + if arg_name not in self.arg_only: + self.arg_only[arg_name] = [] + + self.arg_only[arg_name].append(config_name) + del kwargs['arg_only'] + else: + if arg_name not in self.default_arguments: + self.default_arguments[config_name] = {} + + self.default_arguments[config_name][arg_name] = kwargs.get('default') + + if self.config[config_name][arg_name] is None: + self.config[config_name][arg_name] = kwargs.get('default') + + if config_name not in self.args_passed: + self.args_passed[config_name] = {} + + self.args_passed[config_name][arg_name] = False + + for arg in arg_strings: + if _in_argv(arg): + self.args_passed[config_name][arg_name] = True + def argument(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: """Decorator to call self.add_argument or self..add_argument. """ @@ -364,39 +404,9 @@ def argument_function(handler: Callable[..., Any]) -> Callable[..., Any]: config_name = handler.__name__ subcommand_name = config_name.replace("_", "-") arg_name = get_argument_name(self._arg_parser, *args, **kwargs) - arg_strings = get_argument_strings(self._arg_parser, *args, **kwargs) - - if 'deprecated' in kwargs: - self._deprecated_arguments[arg_name] = kwargs['deprecated'] - if kwargs['help']: - kwargs['help'] += f" [Deprecated]: {kwargs['deprecated']}" - else: - kwargs['help'] = f"[Deprecated]: {kwargs['deprecated']}" - del kwargs['deprecated'] - - if kwargs.get('arg_only'): - if arg_name not in self.arg_only: - self.arg_only[arg_name] = [] - - self.arg_only[arg_name].append(handler.__name__) - del kwargs['arg_only'] - else: - if arg_name not in self.default_arguments: - self.default_arguments[config_name] = {} - - self.default_arguments[config_name][arg_name] = kwargs.get('default') - - if self.config[config_name][arg_name] is None: - self.config[config_name][arg_name] = kwargs.get('default') - - if config_name not in self.args_passed: - self.args_passed[config_name] = {} - - self.args_passed[config_name][arg_name] = False - for arg in arg_strings: - if _in_argv(arg): - self.args_passed[config_name][arg_name] = True + self._handle_deprecated(arg_name, kwargs) + self._handle_arg_parsing(config_name, arg_name, args, kwargs) if handler is self._entrypoint: self.add_argument(*args, **kwargs) diff --git a/milc/questions.py b/milc/questions.py index 396374a..131d640 100644 --- a/milc/questions.py +++ b/milc/questions.py @@ -122,6 +122,16 @@ def password( return None +def _cast_answer(answer_type: Callable[[str], str], answer: str) -> Any: + """Attempt to convert answer to answer_type. + """ + try: + return answer_type(answer) + except Exception as e: + cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e)) + return None + + def question( prompt: str, *args: Any, @@ -130,7 +140,7 @@ def question( answer_type: Callable[[str], str] = str, validate: Optional[Callable[..., bool]] = None, **kwargs: Any, -) -> Optional[str]: +) -> str | Any: """Allow the user to type in a free-form string to answer. | Argument | Description | @@ -158,17 +168,10 @@ def question( elif confirm: if yesno('Is the answer "%s" correct?', answer, default=True): - try: - return answer_type(answer) - except Exception as e: - cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e)) - return None + return _cast_answer(answer_type, answer) else: - try: - return answer_type(answer) - except Exception as e: - cli.log.error('Could not convert answer (%s) to type %s: %s', answer, answer_type.__name__, str(e)) + return _cast_answer(answer_type, answer) elif default is not None: return default @@ -217,35 +220,48 @@ def choice( prompt = '%s[%s] ' % (prompt, default + 1) while True: - # Prompt for an answer. - cli.echo(formatted_heading) - - for i, option in enumerate(options, 1): - cli.echo('\t{fg_cyan}%d.{fg_reset} %s', i, option) + answer = _choice_get_answer(options, default, prompt, formatted_heading) - answer = input(format_ansi(prompt)) + if answer: + if confirm and not yesno('Is the answer "%s" correct?', answer, default=True): + continue - # If the user types in one of the options exactly use that - if answer in options: return answer - # Massage the answer into a valid integer - if answer == '' and default is not None: - answer_index = default - elif answer.isnumeric(): - answer_index = int(answer) - 1 - else: - cli.log.error('Invalid choice: %s', answer) - cli.log.debug('Could not convert %s to int', answer) - continue - - # Validate the answer - if answer_index >= len(options) or answer_index < 0: - cli.log.error('Invalid choice: %s', answer_index + 1) - continue - - if confirm and not yesno('Is the answer "%s" correct?', answer_index + 1, default=True): - continue - - # Return the answer they chose. - return options[answer_index] + +def _choice_get_answer( + options: Sequence[str], + default: Optional[int], + prompt: str, + formatted_heading: str, +) -> Optional[str]: + """Get an answer from the user for choice(). + """ + # Prompt for an answer. + cli.echo(formatted_heading) + + for i, option in enumerate(options, 1): + cli.echo('\t{fg_cyan}%d.{fg_reset} %s', i, option) + + answer = input(format_ansi(prompt)) + + # If the user types in one of the options exactly use that + if answer in options: + return answer + + # Massage the answer into a valid integer + if answer == '' and default is not None: + answer_index = default + elif answer.isnumeric(): + answer_index = int(answer) - 1 + else: + cli.log.error('Invalid choice: %s', answer) + return None + + # Validate the answer + if answer_index >= len(options) or answer_index < 0: + cli.log.error('Invalid choice: %s', answer_index + 1) + return None + + # Return the answer they chose. + return options[answer_index] diff --git a/setup.cfg b/setup.cfg index 5cce948..8985ee6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ ignore = per_file_ignores = **/__init__.py:F401 tests/**:N802 -max_complexity = 14 +max_complexity = 12 [metadata] author = skullydazed