Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #17 #23

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
46 changes: 30 additions & 16 deletions click_default_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,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='*')
Expand Down
92 changes: 50 additions & 42 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,77 +6,85 @@
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_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_command_with_arguments():
assert r.invoke(cli, ['--foo', 'foooo']).output == 'foooo\n'
assert 'no such option' in r.invoke(cli, ['-x']).output.lower()

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()
pytest.deprecated_call(cli.command, default=True)


if __name__ == '__main__':
cli()
cli_group()