Skip to content

Commit

Permalink
fix: Only apply --no-* handling when there is both a positive and n…
Browse files Browse the repository at this point in the history
…egative variant.
  • Loading branch information
DanCardin committed Sep 16, 2024
1 parent e9ebe64 commit 6de0b62
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### 0.22.5

- fix: Refactor parser combinators into dedicated module, and document the behavior more thoroughly.
- refactor: Pull handling of `--no-*` bool arguments out of the parser
- fix: Only apply `--no-*` handling when there is both a positive and negative variant

### 0.22.4

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cappa"
version = "0.22.4"
version = "0.22.5"
description = "Declarative CLI argument parser."

urls = {repository = "https://github.com/dancardin/cappa"}
Expand Down
21 changes: 11 additions & 10 deletions src/cappa/arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,19 +619,20 @@ def explode_negated_bool_args(args: typing.Sequence[Arg]) -> typing.Iterable[Arg
for arg in args:
yielded = False
if isinstance(arg.action, ArgAction) and arg.action.is_bool_action and arg.long:
long = typing.cast(list[str], arg.long)
long = typing.cast(typing.List[str], arg.long)

negatives = [item for item in long if "--no-" in item]
positives = [item for item in long if "--no-" not in item]
positive_arg = dataclasses.replace(
arg, long=positives, action=ArgAction.store_true
)
negative_arg = dataclasses.replace(
arg, long=negatives, action=ArgAction.store_false
)
yield positive_arg
yield negative_arg
yielded = True
if negatives and positives:
positive_arg = dataclasses.replace(
arg, long=positives, action=ArgAction.store_true
)
negative_arg = dataclasses.replace(
arg, long=negatives, action=ArgAction.store_false
)
yield positive_arg
yield negative_arg
yielded = True

if not yielded:
yield arg
Expand Down
16 changes: 0 additions & 16 deletions src/cappa/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,6 @@ def print_help(self, file=None):
raise HelpExit(self.command.help_formatter(self.command, self.prog))


class BooleanOptionalAction(argparse.Action):
"""Simplified backport of same-named class from 3.9 onward.
We know more about the called context here, and thus need much less of the
logic. Also, we support 3.8, which does not have the original class, so we
couldn't use it anyway.
"""

def __init__(self, **kwargs):
super().__init__(nargs=0, **kwargs)

def __call__(self, parser, namespace, values, option_string=None):
assert isinstance(option_string, str)
setattr(namespace, self.dest, not option_string.startswith("--no-"))


def custom_action(arg: Arg, action: Callable):
class CustomAction(argparse.Action):
def __call__( # type: ignore
Expand Down
15 changes: 8 additions & 7 deletions src/cappa/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,11 +624,12 @@ class Value(typing.Generic[T]):
value: T


def store_bool(val: bool):
def store():
return val
def store_true():
return True

return store

def store_false():
return False


def store_count(context: ParseContext, arg: Arg):
Expand All @@ -645,13 +646,13 @@ def store_append(context: ParseContext, arg: Arg, value: Value[typing.Any]):
return result


process_options = {
process_options: dict[ArgAction, typing.Callable] = {
ArgAction.help: HelpAction.from_context,
ArgAction.version: VersionAction.from_arg,
ArgAction.completion: CompletionAction.from_value,
ArgAction.set: store_set,
ArgAction.store_true: store_bool(True),
ArgAction.store_false: store_bool(False),
ArgAction.store_true: store_true,
ArgAction.store_false: store_false,
ArgAction.count: store_count,
ArgAction.append: store_append,
}
26 changes: 26 additions & 0 deletions tests/arg/test_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,29 @@ class ArgTest:
with patch("os.environ", new={"ENV_DEFAULT": "1"}):
test = parse(ArgTest, "--env-default", backend=backend)
assert test.env_default is True


@backends
def test_sole_no_arg(backend):
@dataclass
class ArgTest:
no_dry_run: bool = False

test = parse(ArgTest, backend=backend)
assert test.no_dry_run is False

test = parse(ArgTest, "--no-dry-run", backend=backend)
assert test.no_dry_run is True


@backends
def test_sole_no_arg_inverted(backend):
@dataclass
class ArgTest:
no_dry_run: bool = True

test = parse(ArgTest, backend=backend)
assert test.no_dry_run is True

test = parse(ArgTest, "--no-dry-run", backend=backend)
assert test.no_dry_run is False

0 comments on commit 6de0b62

Please sign in to comment.