pax_global_header00006660000000000000000000000064144631316040014514gustar00rootroot0000000000000052 comment=429c066978162c2cfabb14ea287aa6131413c324 click-default-group-1.2.4/000077500000000000000000000000001446313160400153615ustar00rootroot00000000000000click-default-group-1.2.4/.github/000077500000000000000000000000001446313160400167215ustar00rootroot00000000000000click-default-group-1.2.4/.github/workflows/000077500000000000000000000000001446313160400207565ustar00rootroot00000000000000click-default-group-1.2.4/.github/workflows/test.yaml000066400000000000000000000011761446313160400226260ustar00rootroot00000000000000name: Test on: push jobs: test: runs-on: ubuntu-22.04 strategy: matrix: click-version: ["4.1", "5.1", "6.7", "7.1.2", "8.1.6"] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install '.[test'] pip install click==${{ matrix.click-version }} - name: Test with pytest run: pytest click-default-group-1.2.4/.gitignore000066400000000000000000000012761446313160400173570ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ click-default-group-1.2.4/.travis.yml000066400000000000000000000012401446313160400174670ustar00rootroot00000000000000dist: xenial language: python sudo: false python: - "2.7" - "3.5" - "3.6" - "3.7" - "3.8-dev" - pypy - pypy3 env: - CLICK_VERSION=7.0 - CLICK_VERSION=6.7 - CLICK_VERSION=6.6 - CLICK_VERSION=6.4 - CLICK_VERSION=6.3 - CLICK_VERSION=6.2 - CLICK_VERSION=6.1 - CLICK_VERSION=6.0 - CLICK_VERSION=5.1 - CLICK_VERSION=5.0 - CLICK_VERSION=4.1 - CLICK_VERSION=4.0 install: - pip install click==$CLICK_VERSION - pip install -e . - pip install flake8 flake8-import-order pytest-cov coveralls script: - | # flake8 flake8 click_default_group.py test.py setup.py -v --show-source - | # pytest py.test -v --cov=click_default_group --cov-report=term-missing after_success: - coveralls click-default-group-1.2.4/LICENSE000066400000000000000000000027271446313160400163760ustar00rootroot00000000000000Copyright (c) 2015-2023, Heungsub Lee All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. click-default-group-1.2.4/README.md000066400000000000000000000023751446313160400166470ustar00rootroot00000000000000Click Default Group =================== `DefaultGroup` is a subclass of [`click.Group`](https://click.pocoo.org/6/api/#click.Group). But it invokes the default subcommand instead of showing a help message when a subcommand is not passed. [![Test Status](https://github.com/click-contrib/click-default-group/actions/workflows/test.yaml/badge.svg)] Usage ----- Define a default subcommand by `default=NAME`: ```python import click from click_default_group import DefaultGroup @click.group(cls=DefaultGroup, default='foo', default_if_no_args=True) def cli(): pass @cli.command() def foo(): click.echo('foo') @cli.command() def bar(): click.echo('bar') ``` Then you can invoke that without explicit subcommand name: ```console $ cli.py --help Usage: cli.py [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Command: foo* bar $ cli.py foo $ cli.py foo foo $ cli.py bar bar ``` Compatibility ------------- `click-default-group` is compatible with these Click versions: - Click-8.x - Click-7.x - Click-6.x - Click-5.x - Click-4.x Licensing --------- Written by [Heungsub Lee], and distributed under the [BSD 3-Clause] license. [Heungsub Lee]: https://subl.ee/ [BSD 3-Clause]: https://opensource.org/licenses/BSD-3-Clause click-default-group-1.2.4/click_default_group.py000066400000000000000000000074761446313160400217560ustar00rootroot00000000000000""" click_default_group ~~~~~~~~~~~~~~~~~~~ Define a default subcommand by `default=True`: .. sourcecode:: python import click from click_default_group import DefaultGroup @click.group(cls=DefaultGroup, default_if_no_args=True) def cli(): pass @cli.command(default=True) def foo(): click.echo('foo') @cli.command() def bar(): click.echo('bar') Then you can invoke that without explicit subcommand name: .. sourcecode:: console $ cli.py --help Usage: cli.py [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Command: foo* bar $ cli.py foo $ cli.py foo foo $ cli.py bar bar """ import warnings import click __all__ = ['DefaultGroup'] __version__ = '1.2.4' class DefaultGroup(click.Group): """Invokes a subcommand marked with `default=True` if any subcommand not chosen. :param default_if_no_args: resolves to the default command if no arguments passed. """ def __init__(self, *args, **kwargs): # To resolve as the default command. if not kwargs.get('ignore_unknown_options', True): raise ValueError('Default group accepts unknown options') self.ignore_unknown_options = True self.default_cmd_name = kwargs.pop('default', None) self.default_if_no_args = kwargs.pop('default_if_no_args', False) super(DefaultGroup, self).__init__(*args, **kwargs) def set_default_command(self, command): """Sets a command function as the default command.""" cmd_name = command.name self.add_command(command) self.default_cmd_name = cmd_name 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 def format_commands(self, ctx, formatter): formatter = DefaultCommandFormatter(self, formatter, mark='*') return super(DefaultGroup, self).format_commands(ctx, formatter) def command(self, *args, **kwargs): default = kwargs.pop('default', False) decorator = super(DefaultGroup, self).command(*args, **kwargs) if not default: return decorator warnings.warn('Use default param of DefaultGroup or ' 'set_default_command() instead', DeprecationWarning) def _decorator(f): cmd = decorator(f) self.set_default_command(cmd) return cmd return _decorator class DefaultCommandFormatter(object): """Wraps a formatter to mark a default command.""" def __init__(self, group, formatter, mark='*'): self.group = group self.formatter = formatter self.mark = mark def __getattr__(self, attr): return getattr(self.formatter, attr) def write_dl(self, rows, *args, **kwargs): rows_ = [] for cmd_name, help in rows: if cmd_name == self.group.default_cmd_name: rows_.insert(0, (cmd_name + self.mark, help)) else: rows_.append((cmd_name, help)) return self.formatter.write_dl(rows_, *args, **kwargs) click-default-group-1.2.4/pyproject.toml000066400000000000000000000023041446313160400202740ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.2,<4"] build-backend = "flit_core.buildapi" [project] name = "click-default-group" authors = [{ name = "Heungsub Lee", email = "heungsub@subl.ee" }] license = { file = "LICENSE" } readme = "README.md" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: Public Domain", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] requires-python = ">=2.7" dependencies = ["click"] dynamic = ["version", "description"] [project.urls] Source = "https://github.com/click-contrib/click-default-group" [project.optional-dependencies] test = ["pytest"] click-default-group-1.2.4/setup.cfg000066400000000000000000000002101446313160400171730ustar00rootroot00000000000000[flake8] ignore = E301 import-order-style = google application-import-names = click_default_group [tool:pytest] python_files = test.py click-default-group-1.2.4/test.py000066400000000000000000000036771446313160400167270ustar00rootroot00000000000000# -*- coding: utf-8 -*- import click from click.testing import CliRunner import pytest 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.') @cli.command() @click.option('--foo', default='foo') def foo(foo): click.echo(foo) @cli.command() def bar(): click.echo('bar') r = CliRunner() 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_group_arguments(): assert r.invoke(cli, ['--group-only']).output == '--group-only passed.\n' def test_explicit_command(): assert r.invoke(cli, ['foo']).output == 'foo\n' assert r.invoke(cli, ['bar']).output == 'bar\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 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()