From 794e04421a1114859e8d40406668d97dbdd43bbe Mon Sep 17 00:00:00 2001 From: Gyanu Date: Mon, 31 Aug 2026 00:10:18 +0530 Subject: [PATCH] Don't treat core flags as unfilled positional values. --help already jumped to the initial context when it appeared after a task name. Other core flags like --dry did not, so a task still waiting on a positional would swallow them. Fixes #1084. --- invoke/parser/parser.py | 24 +++++++++++++----------- sites/www/changelog.rst | 3 +++ tests/parser_parser.py | 12 ++++++++++++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/invoke/parser/parser.py b/invoke/parser/parser.py index 43e95df04..a37d93346 100644 --- a/invoke/parser/parser.py +++ b/invoke/parser/parser.py @@ -292,17 +292,9 @@ def handle(self, token: str) -> None: ) ) # noqa self.see_value(token) - # Positional args (must come above context-name check in case we still - # need a posarg and the user legitimately wants to give it a value that - # just happens to be a valid context name.) - elif self.context and self.context.missing_positional_args: - msg = "Context {!r} requires positional args, eating {!r}" - debug(msg.format(self.context, token)) - self.see_positional_arg(token) - # New context - elif token in self.contexts: - self.see_context(token) - # Initial-context flag being given as per-task flag (e.g. --help) + # Core flags after a task name (inv mytask --dry). Must run before the + # positional-arg eater, otherwise unfilled posargs swallow e.g. --dry. + # --help already had this path; other core flags did not. elif self.initial and token in self.initial.flags: debug("Saw (initial-context) flag {!r}".format(token)) flag = self.initial.flags[token] @@ -318,6 +310,16 @@ def handle(self, token: str) -> None: # default-False 'dedupe') and it's up to us whether we actually # put any in place. self.switch_to_flag(token) + # Positional args (must come above context-name check in case we still + # need a posarg and the user legitimately wants to give it a value that + # just happens to be a valid context name.) + elif self.context and self.context.missing_positional_args: + msg = "Context {!r} requires positional args, eating {!r}" + debug(msg.format(self.context, token)) + self.see_positional_arg(token) + # New context + elif token in self.contexts: + self.see_context(token) # Unknown else: if not self.ignore_unknown: diff --git a/sites/www/changelog.rst b/sites/www/changelog.rst index c34b253ec..d2038f8e5 100644 --- a/sites/www/changelog.rst +++ b/sites/www/changelog.rst @@ -2,6 +2,9 @@ Changelog ========= +- :bug:`1084` Core flags given after a task name are no longer swallowed when + that task still has unfilled positional arguments. ``--help`` already had + this treatment; ``--dry`` and the rest of the core flags now do too. - :release:`3.0.3 <2026-04-07>` - :support:`- backported` Reverted the `@task ` return value type hint change; it actually just makes diff --git a/tests/parser_parser.py b/tests/parser_parser.py index 9842aaac5..d7f70728d 100644 --- a/tests/parser_parser.py +++ b/tests/parser_parser.py @@ -491,6 +491,18 @@ def value_requiring_core_flags_also_work_correctly(self) -> None: result = parser.parse_argv(["mytask", "--hide", "both"]) assert result[0].args.hide.value == "both" + def core_flags_are_not_eaten_as_unfilled_positionals(self) -> None: + initial = Context( + args=[Argument("dry", kind=bool, default=False)] + ) + task1 = Context( + "mytask", args=[Argument("name", positional=True)] + ) + parser = Parser(initial=initial, contexts=[task1]) + result = parser.parse_argv(["mytask", "--dry", "alice"]) + assert result[0].args.dry.value is True + assert result[1].args.name.value == "alice" + class edge_cases: def core_bool_but_per_task_string(self) -> None: # Initial parse context with bool --hide, and a task with a