import click from typing import ( Any, Callable, Optional, Sequence, Tuple, Union, ) from staking_deposit.exceptions import ValidationError from staking_deposit.utils import config def _value_of(f: Union[Callable[[], Any], Any]) -> Any: ''' If the input, f, is a function, return f(), else return f. ''' return(f() if callable(f) else f) class JITOption(click.Option): ''' A click.Option, except certain values are recomputed before they are used. ''' def __init__( self, param_decls: str, default: Union[Callable[[], Any], None, Any] = None, help: Union[Callable[[], str], str, None] = None, prompt: Union[Callable[[], str], str, None] = None, **kwargs: Any, ): self.callable_default = default self.callable_help = help self.callable_prompt = prompt return super().__init__( param_decls=[_value_of(param_decls)], default=_value_of(default), help=_value_of(help), prompt=_value_of(prompt), **kwargs, ) def prompt_for_value(self, ctx: click.Context) -> Any: self.prompt = _value_of(self.callable_prompt) return super().prompt_for_value(ctx) def get_help_record(self, ctx: click.Context) -> Tuple[str, str]: self.help = _value_of(self.callable_help) return super().get_help_record(ctx) def get_default(self, ctx: click.Context, call: bool = True) -> Any: self.default = _value_of(self.callable_default) return super().get_default(ctx, call) def jit_option(*args: Any, **kwargs: Any) -> Callable[[Any], Any]: """Attaches an option to the command. All positional arguments are passed as parameter declarations to :class:`Option`; all keyword arguments are forwarded unchanged (except ``cls``). This is equivalent to creating an :class:`Option` instance manually and attaching it to the :attr:`Command.params` list. :param cls: the option class to instantiate. This defaults to :class:`Option`. """ def decorator(f: Callable[[Any], Any]) -> Callable[[Any], Any]: click.decorators._param_memo(f, JITOption(*args, **kwargs)) return f return decorator def captive_prompt_callback( processing_func: Callable[[str], Any], prompt: Callable[[], str], confirmation_prompt: Optional[Callable[[], str]]=None, confirmation_mismatch_msg: Callable[[], str]=lambda: '', hide_input: bool=False, ) -> Callable[[click.Context, str, str], Any]: ''' Traps the user in a prompt until the value chosen is acceptable as defined by `processing_func` not returning a ValidationError :param processing_func: A function to process the user's input that possibly raises a ValidationError :param prompt: the text to prompt the user with, should their input raise an error when passed to processing_func() :param confirmation_prompt: the optional prompt for confirming user input (the user must repeat their input) :param confirmation_mismatch_msg: the message displayed to the user should their input and confirmation not match :param hide_input: bool, hides the input as the user types ''' def callback(ctx: click.Context, param: Any, user_input: str) -> Any: if config.non_interactive: return processing_func(user_input) while True: try: processed_input = processing_func(user_input) # Logic for confirming user input: if confirmation_prompt is not None and processed_input != '': confirmation_input = click.prompt(confirmation_prompt(), hide_input=hide_input) if processing_func(confirmation_input) != processed_input: raise ValidationError(confirmation_mismatch_msg()) return processed_input except ValidationError as e: click.echo(e) user_input = click.prompt(prompt(), hide_input=hide_input) return callback def choice_prompt_func(prompt_func: Callable[[], str], choices: Sequence[str]) -> Callable[[], str]: ''' Formats the prompt and choices in a printable manner. ''' return lambda: '%s %s: ' % (prompt_func(), choices)