1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
"""
Useful functions used to implement constraints and predicates.
"""
from typing import Any, Dict, Iterable, List, Sequence
from click import Argument, Context, Option, Parameter
def param_value_is_set(param: Parameter, value: Any) -> bool:
"""Define what it means for a parameter of a specific kind to be "set".
All cases are obvious besides that of boolean options:
- (common rule) if the value is ``None``, the parameter is unset;
- a parameter that takes multiple values is set if at least one argument is provided;
- a boolean **flag** is set only if True;
- a boolean option is set if not None, even if it's False.
"""
if value is None:
return False
# checking for param.is_flag is redundant but necessary to work around
# Click 8.0.1 issue: https://github.com/pallets/click/issues/1925
elif isinstance(param, Option) and param.is_flag and param.is_bool_flag:
return bool(value)
elif param.nargs != 1 or param.multiple:
return len(value) > 0
return True
def get_param_name(param: Parameter) -> str:
"""Return the name of a parameter casted to ``str``.
Use this function to avoid typing errors in places where you expect a parameter
having a name.
"""
if param.name is None:
raise TypeError(
'`param.name` is required to be a string in this context.\n'
'Hint: `param.name` is None only when `parameter.expose_value` is False, '
'so you are probably using this option incorrectly.'
)
return param.name
def get_params_whose_value_is_set(
params: Iterable[Parameter], values: Dict[str, Any]
) -> List[Parameter]:
"""Filter ``params``, returning only the parameters that have a value.
Boolean flags are considered "set" if their value is ``True``."""
return [p for p in params
if param_value_is_set(p, values[get_param_name(p)])]
def get_required_params(params: Iterable[Parameter]) -> List[Parameter]:
return [p for p in params if p.required]
def get_param_label(param: Parameter) -> str:
if param.param_type_name == 'argument':
return param.human_readable_name
return sorted(param.opts, key=len)[-1]
def join_param_labels(params: Iterable[Parameter], sep: str = ', ') -> str:
return sep.join(get_param_label(p) for p in params)
def join_with_and(strings: Sequence[str], sep: str = ', ') -> str:
if not strings:
return ''
if len(strings) == 1:
return strings[0]
return sep.join(strings[:-1]) + ' and ' + strings[-1]
def format_param(param: Parameter) -> str:
if isinstance(param, Argument):
return param.human_readable_name
opts = param.opts
if len(opts) == 1:
return opts[0]
# Use the first long opt as the main/canonical one, put all others
# opts between parenthesis as aliases, long opts first, then short ones
long_opts = [opt for opt in opts if opt.startswith("--")]
short_opts = [opt for opt in opts if not opt.startswith("--")]
main_opt = long_opts[0]
aliases = ", ".join(long_opts[1:] + short_opts)
return f'{main_opt} ({aliases})'
def format_param_list(param_list: Iterable[Parameter], indent: int = 2) -> str:
lines = map(format_param, param_list)
indentation = ' ' * indent
return ''.join(indentation + line + '\n'
for line in lines)
def param_label_by_name(ctx: Any, name: str) -> str:
return get_param_label(ctx.command.get_param_by_name(name))
def get_param_labels(ctx: Any, param_names: Iterable[str]) -> List[str]:
params = ctx.command.get_params_by_name(param_names)
return [get_param_label(param) for param in params]
def param_value_by_name(ctx: Context, name: str) -> Any:
try:
return ctx.params[name]
except KeyError:
raise KeyError(f'"{name}" is not the name of a CLI parameter')
|