""" Module to check mutually exclusive cli parameters """ from typing import Any, List, Mapping, Tuple, cast import click from samcli.commands._utils.custom_options.replace_help_option import ReplaceHelpSummaryOption class ClickMutex(ReplaceHelpSummaryOption): """ Preprocessing checks for mutually exclusive or required parameters as supported by click api. required_param_lists: List[List[str]] List of lists with each supported combination of params Ex: With option = "a" and required_param_lists = [["b", "c"], ["c", "d"]] It is valid to specify --a --b --c or --a --c --d but not --a --b --d required_params_hint: str String to be appended after default missing required params prompt incompatible_params: List[str] List of incompatible parameters incompatible_params_hint: str String to be appended after default incompatible params prompt """ def __init__(self, *args, **kwargs): self.required_param_lists: List[List[str]] = kwargs.pop("required_param_lists", []) self.required_params_hint: str = kwargs.pop("required_params_hint", "") self.incompatible_params: List[str] = kwargs.pop("incompatible_params", []) self.incompatible_params_hint: str = kwargs.pop("incompatible_params_hint", "") super().__init__(*args, **kwargs) def handle_parse_result( self, ctx: click.Context, opts: Mapping[str, Any], args: List[str] ) -> Tuple[Any, List[str]]: """ Checks whether any option is in self.incompatible_params If one is found, prompt and throw an UsageError Then checks any combination in self.required_param_lists is satisfied. With option = "a" and required_param_lists = [["b", "c"], ["c", "d"]] It is valid to specify --a --b --c, --a --c --d, or --a --b --c --d but not --a --b --d """ if self.name not in opts: return super().handle_parse_result(ctx, opts, args) option_name: str = cast(str, self.name) # Check for parameters not compatible with each other for incompatible_param in self.incompatible_params: if incompatible_param in opts: msg = ( f"You must not provide both the {ClickMutex._to_param_name(option_name)} and " f"{ClickMutex._to_param_name(incompatible_param)} parameters.\n" ) msg += self.incompatible_params_hint raise click.UsageError(msg) # Check for required parameters if self.required_param_lists: # Loop through all combinations of required params for required_params in self.required_param_lists: # Test whether all required params exist in one combination/required_params has_all_required_params = False not in [required_param in opts for required_param in required_params] # Break to skip over "else" block if any one combination is satisfied if has_all_required_params: break else: msg = ( f"Missing required parameters, with --{option_name.replace('_', '-')} set.\n" "Must provide one of the following required parameter combinations:\n" ) for required_params in self.required_param_lists: msg += "\t" msg += ", ".join(ClickMutex._to_param_name(param) for param in required_params) msg += "\n" msg += self.required_params_hint raise click.UsageError(msg) self.prompt = "" return super().handle_parse_result(ctx, opts, args) @staticmethod def _to_param_name(param: str): return f"--{param.replace('_', '-')}"