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

Vendor click_default_group to make us installable again #540

Merged
merged 2 commits into from
Aug 4, 2023
Merged
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
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ classifiers = [
requires-python = ">=3.8"
dependencies = [
"click",
"click-default-group",
"importlib-resources>=5; python_version<'3.10'",
"incremental",
"jinja2",
Expand Down Expand Up @@ -146,9 +145,9 @@ strict = true
exclude = '^src/towncrier/test/test_.*\.py$'

[[tool.mypy.overrides]]
module = 'click_default_group'
# 2022-09-04: This library has no type annotations.
ignore_missing_imports = true
module = 'towncrier.click_default_group'
# Vendored module without type annotations.
ignore_errors = true

[[tool.mypy.overrides]]
module = 'incremental'
Expand All @@ -174,4 +173,5 @@ exclude_lines = [
omit = [
"src/towncrier/__main__.py",
"src/towncrier/test/*",
"src/towncrier/click_default_group.py",
]
3 changes: 1 addition & 2 deletions src/towncrier/_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@

import click

from click_default_group import DefaultGroup

from ._version import __version__
from .build import _main as _build_cmd
from .check import _main as _check_cmd
from .click_default_group import DefaultGroup
from .create import _main as _create_cmd


Expand Down
148 changes: 148 additions & 0 deletions src/towncrier/click_default_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# SPDX-FileCopyrightText: 2015 Heungsub Lee <19982+sublee@users.noreply.github.com>
#
# SPDX-License-Identifier: BSD-3-Clause

# Vendored from
# https://github.com/click-contrib/click-default-group/tree/b671ae5325d186fe5ea7abb584f15852a1e931aa
# Because the PyPI package could not be installed on modern Pips anymore and
# the project looks unmaintaintained.

"""
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.2"


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().__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().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().get_command(ctx, cmd_name)

def resolve_command(self, ctx, args):
base = super()
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().format_commands(ctx, formatter)

def command(self, *args, **kwargs):
default = kwargs.pop("default", False)
decorator = super().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:
"""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)
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/540.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Towncrier now vendors the click-default-group package that prevented installations on modern Pips.