From aae753ee8c6a8e95c70956699d666a9cd41f0f96 Mon Sep 17 00:00:00 2001 From: Michael Dubner Date: Thu, 27 Oct 2022 21:51:26 +0300 Subject: [PATCH 1/2] Import test script from edobez (patched) --- test.py | 94 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/test.py b/test.py index c464620..680b242 100644 --- a/test.py +++ b/test.py @@ -6,72 +6,76 @@ from click_default_group import DefaultGroup -@click.group(cls=DefaultGroup, default='foo', invoke_without_command=True) -@click.option('--group-only', is_flag=True) -def cli(group_only): - # Called if invoke_without_command=True. - if group_only: - click.echo('--group-only passed.') +@pytest.fixture +def cli_group(): + @click.group(cls=DefaultGroup, default='foo') + @click.option('--verbose', is_flag=True) + def cli(verbose): + if verbose: + click.echo('Verbose!') + @cli.command() + @click.option('--foo_opt') + @click.argument('foo_arg', required=False) + def foo(foo_opt, foo_arg): + click.echo('foo exec') + if foo_opt: + click.echo(f'foo_opt={foo_opt}') + if foo_arg: + click.echo(f'foo_arg={foo_arg}') -@cli.command() -@click.option('--foo', default='foo') -def foo(foo): - click.echo(foo) - + @cli.command() + def bar(): + click.echo('bar exec') -@cli.command() -def bar(): - click.echo('bar') + return cli +@pytest.fixture +def cli_group_with_default(cli_group: DefaultGroup): + cli_group.default_if_no_args = True + return cli_group r = CliRunner() +def test_normal_calling(cli_group: DefaultGroup): + assert r.invoke(cli_group, []).output.startswith('Usage:') + assert r.invoke(cli_group, ['foo']).output == 'foo exec\n' + assert r.invoke(cli_group, ['foo', '--foo_opt', 'opt']).output == 'foo exec\nfoo_opt=opt\n' -def test_default_command_with_arguments(): - assert r.invoke(cli, ['--foo', 'foooo']).output == 'foooo\n' - assert 'no such option' in r.invoke(cli, ['-x']).output +def test_explicit_command(cli_group_with_default: DefaultGroup): + assert r.invoke(cli_group_with_default, ['foo']).output == 'foo exec\n' + assert r.invoke(cli_group_with_default, ['bar']).output == 'bar exec\n' +def test_default_if_no_args(cli_group_with_default: DefaultGroup): + assert r.invoke(cli_group_with_default, []).output == 'foo exec\n' -def test_group_arguments(): - assert r.invoke(cli, ['--group-only']).output == '--group-only passed.\n' +def test_default_command_with_arguments(cli_group_with_default: DefaultGroup): + assert r.invoke(cli_group_with_default, ['--foo_opt', 'opt']).output == 'foo exec\nfoo_opt=opt\n' + assert 'No such option' in r.invoke(cli_group_with_default, ['-x']).output +def test_group_arguments(cli_group: DefaultGroup): + assert 'Error: Missing command' in r.invoke(cli_group, ['--verbose']).output -def test_explicit_command(): - assert r.invoke(cli, ['foo']).output == 'foo\n' - assert r.invoke(cli, ['bar']).output == 'bar\n' +def test_group_arguments_without_cmd(cli_group: DefaultGroup): + cli_group.invoke_without_command = True + assert r.invoke(cli_group, ['--verbose', '--foo_opt=123']).output == 'Verbose!\nfoo exec\nfoo_opt=123\n' + assert r.invoke(cli_group, ['--verbose']).output == 'Verbose!\n' +def test_group_arguments_if_no_args(cli_group: DefaultGroup): + cli_group.default_if_no_args = True + assert r.invoke(cli_group, ['--verbose', '--foo_opt=123']).output == 'Verbose!\nfoo exec\nfoo_opt=123\n' + assert r.invoke(cli_group, ['--verbose']).output == 'Verbose!\nfoo exec\n' def test_set_ignore_unknown_options_to_false(): with pytest.raises(ValueError): DefaultGroup(ignore_unknown_options=False) - -def test_default_if_no_args(): - cli = DefaultGroup() - - @cli.command() - @click.argument('foo', required=False) - @click.option('--bar') - def foobar(foo, bar): - click.echo(foo) - click.echo(bar) - - cli.set_default_command(foobar) - assert r.invoke(cli, []).output.startswith('Usage:') - assert r.invoke(cli, ['foo']).output == 'foo\n\n' - assert r.invoke(cli, ['foo', '--bar', 'bar']).output == 'foo\nbar\n' - cli.default_if_no_args = True - assert r.invoke(cli, []).output == '\n\n' - - -def test_format_commands(): - help = r.invoke(cli, ['--help']).output +def test_format_commands(cli_group_with_default: DefaultGroup): + help = r.invoke(cli_group_with_default, ['--help']).output assert 'foo*' in help assert 'bar*' not in help assert 'bar' in help - def test_deprecation(): # @cli.command(default=True) has been deprecated since 1.2. cli = DefaultGroup() @@ -79,4 +83,4 @@ def test_deprecation(): if __name__ == '__main__': - cli() + cli_group() From c11fcf3e950d005d9e1c305365695edeeea270f3 Mon Sep 17 00:00:00 2001 From: Michael Dubner Date: Thu, 27 Oct 2022 21:52:01 +0300 Subject: [PATCH 2/2] Fix issue click-contrib#17 --- click_default_group.py | 46 +++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/click_default_group.py b/click_default_group.py index c557163..932878a 100644 --- a/click_default_group.py +++ b/click_default_group.py @@ -80,22 +80,36 @@ def set_default_command(self, command): def parse_args(self, ctx, args): if not args and self.default_if_no_args: args.insert(0, self.default_cmd_name) - return super(DefaultGroup, self).parse_args(ctx, args) - - def get_command(self, ctx, cmd_name): - if cmd_name not in self.commands: - # No command name matched. - ctx.arg0 = cmd_name - cmd_name = self.default_cmd_name - return super(DefaultGroup, self).get_command(ctx, cmd_name) - - def resolve_command(self, ctx, args): - base = super(DefaultGroup, self) - cmd_name, cmd, args = base.resolve_command(ctx, args) - if hasattr(ctx, 'arg0'): - args.insert(0, ctx.arg0) - cmd_name = cmd.name - return cmd_name, cmd, args + + if ctx.resilient_parsing: + return super(DefaultGroup, self).parse_args(ctx, args) + + # fixup to allow help work for subcommands + test_ctx = self.make_context(ctx.info_name, ctx.args, resilient_parsing=True) + rest = super(DefaultGroup, self).parse_args(test_ctx, args[:]) + + help_options = self.get_help_option_names(ctx) + if help_options and self.add_help_option and rest and any(s in help_options for s in rest): + return super(DefaultGroup, self).parse_args(ctx, args) + + save_allow_interspersed_args = ctx.allow_interspersed_args + ctx.allow_interspersed_args = True + rest = super(DefaultGroup, self).parse_args(ctx, args) + ctx.allow_interspersed_args = save_allow_interspersed_args + + if not rest and (ctx.protected_args or ['a'])[0][:1].isalnum() and not self.default_if_no_args: + pass # Don't inject default_cmd_name if no command or command-specific options passed + elif not ctx.protected_args: + ctx.protected_args = [self.default_cmd_name] + else: + cmd_name = ctx.protected_args[0] + cmd = self.get_command(ctx, cmd_name) + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + if cmd is None: + ctx.protected_args.insert(0, self.default_cmd_name) + return rest def format_commands(self, ctx, formatter): formatter = DefaultCommandFormatter(self, formatter, mark='*')