Skip to content

Commit 31dd3fc

Browse files
committed
fix: parser: detect '--help' if supplied to task
This fixes the invocation of a my_task which has mandatory/positional arguments: ```bash > inv my_task --help ``` When detecting a missing positional arg, we check if the token is '--help/-h'. If so, we store the task/context name to print help for it later and flag it the task/context to be completed without checks. Signed-off-by: laurensmiers <[email protected]>
1 parent 65dd896 commit 31dd3fc

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed

invoke/parser/context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def __init__(
9696
self.aliases = aliases
9797
for arg in args:
9898
self.add_arg(arg)
99+
self.skip_checks = False
99100

100101
def __repr__(self) -> str:
101102
aliases = ""

invoke/parser/parser.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ def handle(self, token: str) -> None:
296296
# need a posarg and the user legitimately wants to give it a value that
297297
# just happens to be a valid context name.)
298298
elif self.context and self.context.missing_positional_args:
299+
if self._is_token_help(token):
300+
debug("--help passed, skip positional arg")
301+
self._force_help()
302+
return
299303
msg = "Context {!r} requires positional args, eating {!r}"
300304
debug(msg.format(self.context, token))
301305
self.see_positional_arg(token)
@@ -307,10 +311,10 @@ def handle(self, token: str) -> None:
307311
debug("Saw (initial-context) flag {!r}".format(token))
308312
flag = self.initial.flags[token]
309313
# Special-case for core --help flag: context name is used as value.
310-
if flag.name == "help":
311-
flag.value = self.context.name
312-
msg = "Saw --help in a per-task context, setting task name ({!r}) as its value" # noqa
313-
debug(msg.format(flag.value))
314+
if self._is_token_help(token):
315+
debug("--help passed")
316+
self._force_help()
317+
return
314318
# All others: just enter the 'switch to flag' parser state
315319
else:
316320
# TODO: handle inverse core flags too? There are none at the
@@ -338,15 +342,18 @@ def complete_context(self) -> None:
338342
self.context.name if self.context else self.context
339343
)
340344
)
341-
# Ensure all of context's positional args have been given.
342-
if self.context and self.context.missing_positional_args:
345+
if not self.context:
346+
return
347+
348+
# Ensure all of context's positional args have been given
349+
if not self.context.skip_checks and self.context.missing_positional_args:
343350
err = "'{}' did not receive required positional arguments: {}"
344351
names = ", ".join(
345352
"'{}'".format(x.name)
346353
for x in self.context.missing_positional_args
347354
)
348355
self.error(err.format(self.context.name, names))
349-
if self.context and self.context not in self.result:
356+
if self.context not in self.result:
350357
self.result.append(self.context)
351358

352359
def switch_to_context(self, name: str) -> None:
@@ -453,3 +460,18 @@ def see_positional_arg(self, value: Any) -> None:
453460

454461
def error(self, msg: str) -> None:
455462
raise ParseError(msg, self.context)
463+
464+
def _is_token_help(self, token: str) -> bool:
465+
try:
466+
flag = self.initial.flags[token]
467+
if flag.name == "help":
468+
return True
469+
except KeyError:
470+
pass
471+
except AttributeError:
472+
pass
473+
return False
474+
475+
def _force_help(self) -> None:
476+
self.initial.flags["--help"].value = self.context.name
477+
self.context.skip_checks = True

tests/parser_parser.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,22 @@ def core_bool_but_per_task_string(self):
501501
assert result[0].args.hide.value is False
502502
assert result[1].args.hide.value == "both"
503503

504+
def help_passed_when_task_expects_one_positional_arg(self):
505+
init = Context(args=[Argument(names=("help", "h"), optional=True)])
506+
task1 = Context("mytask", args=[Argument(names=("name", "n"), kind=str, positional=True)])
507+
parser = Parser(initial=init, contexts=[task1])
508+
result = parser.parse_argv(["mytask", "--help"])
509+
assert result[0].flags['--help'].value == "mytask"
510+
511+
def help_passed_when_task_expects_multiple_positional_arg(self):
512+
init = Context(args=[Argument(names=("help", "h"), optional=True)])
513+
task1 = Context("mytask", args=[Argument(names=("pos_arg_one", "o"), kind=str, positional=True),
514+
Argument(names=("pos_arg_two", "t"), kind=str, positional=True)
515+
])
516+
parser = Parser(initial=init, contexts=[task1])
517+
result = parser.parse_argv(["mytask", "--help"])
518+
assert result[0].flags['--help'].value == "mytask"
519+
504520
class help_treats_context_name_as_its_value:
505521
def by_itself_base_case(self):
506522
task1 = Context("mytask")

0 commit comments

Comments
 (0)