Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions smart_tests/args4p/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,14 @@ def __call__(self, *_args: str) -> Any:
if a in ["--help", "-h"]:
print(invoker.command.format_help())
raise Exit(0)
has_inline = False
if a.startswith("--") and '=' in a:
# --long-format=value
a, val = a.split('=', 1)
args.insert_front(val)
has_inline = True

invoker.eat_options(a, args)
invoker.eat_options(a, args, has_inline)
elif isinstance(invoker.command, Group):
invoker = invoker.sub_command(a)
else:
Expand Down Expand Up @@ -613,13 +615,13 @@ def eat_arg(self, arg: str):
self.kwargs[a.name] = a.append(self.kwargs.get(a.name), arg)
self.nargs += 1

def eat_options(self, option_name: str, args: ArgList):
def eat_options(self, option_name: str, args: ArgList, has_inline: bool = False):
inv: _Invoker | None = self
option_names = []
while inv is not None:
for o in inv.command.options:
if option_name in o.option_names:
inv.kwargs[o.name] = o.append(inv.kwargs.get(o.name), option_name, args)
inv.kwargs[o.name] = o.append(inv.kwargs.get(o.name), option_name, args, has_inline)
return
else:
option_names += o.option_names
Expand Down
7 changes: 5 additions & 2 deletions smart_tests/args4p/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def group(name: Optional[str] = None, help: Optional[str] = None) -> Callable[..
def option(
*param_decls: str,
help: str | None = None, type: type | Callable | None = None, default: Any = NO_DEFAULT, required: bool = False,
metavar: str | None = None, multiple: bool = False, hidden: bool = False
metavar: str | None = None, multiple: bool = False, hidden: bool = False,
optional_value: bool = False, flag_value: Any = None
) -> Callable:
'''
See README.md for usage
Expand All @@ -60,7 +61,9 @@ def decorator(f: Callable) -> Callable:
required=required,
metavar=metavar,
multiple=multiple,
hidden=hidden)
hidden=hidden,
optional_value=optional_value,
flag_value=flag_value)

return _attach(f, o)

Expand Down
26 changes: 23 additions & 3 deletions smart_tests/args4p/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ def __init__(
required: bool = False,
metavar: str | None = None,
multiple: bool = False,
hidden: bool = False):
hidden: bool = False,
optional_value: bool = False,
flag_value: Any = None):
self.name = name # type: ignore[assignment] # once properly constructed, name is never None
self.option_names = option_names
self.help = help
Expand All @@ -39,15 +41,33 @@ def __init__(
self.metavar = metavar
self.multiple = multiple
self.hidden = hidden
# When True, the value is taken ONLY from an inline `--opt=value` form; a bare `--opt`
# (no `=`) binds `flag_value` instead of consuming the following argument. This lets a
# single option behave both as a flag (`--opt`) and as a valued option (`--opt=value`).
self.optional_value = optional_value
self.flag_value = flag_value

def append(self, existing: Any, option_name: str, args): # args is ArgList, but typing it creates a circular import
# 'args' is ArgList, but typing it creates a circular import
def append(self, existing: Any, option_name: str, args, has_inline: bool = False):
'''
Given the current value 'existing' that represents the present value to invoke the user function with,
this method is called when this option was specified as 'option_name' on the command line.
'args' is pointing at the next argument after 'option_name', which may be the value for this option.
'has_inline' is True when the option was given as `--opt=value` (the value has been pushed to
the front of 'args' by the parser).
'''

if self.type == bool or self.type == Optional[bool]:
if self.optional_value:
if has_inline:
v = args.eat(option_name)
try:
v = self.type(v)
except ValueError as e:
raise BadCmdLineException(f"Invalid value '{v}' for option '{option_name}': {str(e)}") from e
else:
# bare `--opt`: bind the configured flag value, do NOT consume the next argument.
v = self.flag_value
elif self.type == bool or self.type == Optional[bool]:
v = True
else:
v = args.eat(option_name)
Expand Down
6 changes: 4 additions & 2 deletions smart_tests/args4p/typer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
def Option(
*option_names: str,
help: str | None = None, type: type | Callable | None = None, default: Any = NO_DEFAULT, required: bool = False,
metavar: str | None = None, multiple: bool = False, hidden: bool = False
metavar: str | None = None, multiple: bool = False, hidden: bool = False,
optional_value: bool = False, flag_value: Any = None
) -> _Option:
'''
See README.md for usage
'''

return _Option(name=None, option_names=list(option_names), help=help, type=type,
default=default, required=required, metavar=metavar, multiple=multiple, hidden=hidden)
default=default, required=required, metavar=metavar, multiple=multiple, hidden=hidden,
optional_value=optional_value, flag_value=flag_value)


def Argument(
Expand Down
Loading
Loading