From 0300d81bfb458ff3d51d3a13cc9555052211f24c Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Wed, 14 Feb 2024 16:47:45 +1100
Subject: [PATCH 1/8] Add type hints

and related changes to get mypy to pass
---
 click_default_group.py | 87 +++++++++++++++++++++++++++---------------
 1 file changed, 57 insertions(+), 30 deletions(-)

diff --git a/click_default_group.py b/click_default_group.py
index 735c54b..ebbb06f 100644
--- a/click_default_group.py
+++ b/click_default_group.py
@@ -43,11 +43,12 @@ def bar():
       bar
 
 """
+
+import typing as t
 import warnings
 
 import click
 
-
 __all__ = ['DefaultGroup']
 __version__ = '1.2.4'
 
@@ -61,77 +62,103 @@ class DefaultGroup(click.Group):
 
     """
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
         # 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)
+        super().__init__(*args, **kwargs)
 
-    def set_default_command(self, command):
+    def set_default_command(self, command: t.Any) -> None:
         """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):
+    def parse_args(self, ctx: click.core.Context, args: t.List[str]) -> t.List[str]:
         if not args and self.default_if_no_args:
             args.insert(0, self.default_cmd_name)
-        return super(DefaultGroup, self).parse_args(ctx, args)
+        return super().parse_args(ctx, args)
 
-    def get_command(self, ctx, cmd_name):
+    def get_command(
+        self, ctx: click.core.Context, cmd_name: str
+    ) -> t.Optional[click.core.Command]:
         if cmd_name not in self.commands:
             # No command name matched.
-            ctx.arg0 = cmd_name
+            ctx.arg0 = cmd_name  # type: ignore
             cmd_name = self.default_cmd_name
-        return super(DefaultGroup, self).get_command(ctx, cmd_name)
+        return super().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'):
+    def resolve_command(
+        self, ctx: click.core.Context, args: t.List[str]
+    ) -> t.Tuple[t.Optional[str], t.Optional[click.core.Command], t.List[str]]:
+        cmd_name, cmd, args = super().resolve_command(ctx, args)
+        if cmd and 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)
+    def format_commands(
+        self, ctx: click.core.Context, formatter: click.formatting.HelpFormatter
+    ) -> None:
+        new_formatter = DefaultCommandFormatter(self, formatter, mark="*")
+        return super().format_commands(ctx, new_formatter)
+
+    @t.overload
+    def command(self, __func: t.Callable[..., t.Any]) -> click.core.Command: ...
+
+    @t.overload
+    def command(
+        self, *args: t.Any, **kwargs: t.Any
+    ) -> t.Callable[[t.Callable[..., t.Any]], click.core.Command]: ...
+
+    def command(
+        self, *args: t.Any, **kwargs: t.Any
+    ) -> t.Union[
+        t.Callable[[t.Callable[..., t.Any]], click.core.Command], click.core.Command
+    ]:
+        default = kwargs.pop("default", False)
+        decorator: t.Callable[[t.Callable[..., t.Any]], click.core.Command] = 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)
+        def _decorator(f: t.Callable[..., t.Any]) -> click.core.Command:
+            cmd: click.core.Command = decorator(f)
             self.set_default_command(cmd)
             return cmd
 
         return _decorator
 
 
-class DefaultCommandFormatter(object):
+class DefaultCommandFormatter(click.formatting.HelpFormatter):
     """Wraps a formatter to mark a default command."""
 
-    def __init__(self, group, formatter, mark='*'):
+    def __init__(
+        self,
+        group: DefaultGroup,
+        formatter: click.formatting.HelpFormatter,
+        mark: str = "*",
+    ):
         self.group = group
         self.formatter = formatter
         self.mark = mark
 
-    def __getattr__(self, attr):
+        super().__init__()
+
+    def __getattr__(self, attr: str) -> t.Any:
         return getattr(self.formatter, attr)
 
-    def write_dl(self, rows, *args, **kwargs):
-        rows_ = []
-        for cmd_name, help in rows:
+    def write_dl(
+        self, rows: t.Sequence[t.Tuple[str, str]], *args: t.Any, **kwargs: t.Any
+    ) -> None:
+        rows_: t.List[t.Tuple[str, str]] = []
+        for cmd_name, text in rows:
             if cmd_name == self.group.default_cmd_name:
-                rows_.insert(0, (cmd_name + self.mark, help))
+                rows_.insert(0, (cmd_name + self.mark, text))
             else:
-                rows_.append((cmd_name, help))
+                rows_.append((cmd_name, text))
         return self.formatter.write_dl(rows_, *args, **kwargs)

From 7383622eae149ce75e4564ebf33cf3462289d4da Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Wed, 14 Feb 2024 16:48:23 +1100
Subject: [PATCH 2/8] Add marker to indicate type hints

---
 py.typed | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 py.typed

diff --git a/py.typed b/py.typed
new file mode 100644
index 0000000..e69de29

From c8fb5ae2e2a6c3485d950832901cdc0d8631df06 Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Wed, 14 Feb 2024 16:58:26 +1100
Subject: [PATCH 3/8] Add mypy to the github actions workflow

---
 .github/workflows/build.yaml | 4 +++-
 click_default_group.py       | 6 ++++--
 pyproject.toml               | 2 +-
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 240b669..eef1887 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -20,10 +20,12 @@ jobs:
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        pip install '.[test']
+        pip install '.[test]'
         pip install click==${{ matrix.click-version }}
     - name: Test with pytest
       run: pytest
+    - name: Check type hints
+      run: python3 -m mypy --follow-imports=silent --python-version "${{ matrix.python-version }}" --strict --implicit-reexport -- click_default_group.py
 
   build:
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
diff --git a/click_default_group.py b/click_default_group.py
index ebbb06f..adfe4a4 100644
--- a/click_default_group.py
+++ b/click_default_group.py
@@ -123,8 +123,10 @@ def command(
         decorator: t.Callable[[t.Callable[..., t.Any]], click.core.Command] = super().command(*args, **kwargs)
         if not default:
             return decorator
-        warnings.warn('Use default param of DefaultGroup or '
-                      'set_default_command() instead', DeprecationWarning)
+        warnings.warn(
+            "Use default param of DefaultGroup or set_default_command() instead",
+            DeprecationWarning,
+        )
 
         def _decorator(f: t.Callable[..., t.Any]) -> click.core.Command:
             cmd: click.core.Command = decorator(f)
diff --git a/pyproject.toml b/pyproject.toml
index 8f3772d..67a77f0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -33,4 +33,4 @@ dynamic = ["version", "description"]
 Source = "https://github.com/click-contrib/click-default-group"
 
 [project.optional-dependencies]
-test = ["pytest"]
+test = ["pytest", "mypy"]

From c10c413d3c6cd6b886c0f578f873af40d77a4960 Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Wed, 14 Feb 2024 17:02:39 +1100
Subject: [PATCH 4/8] Limit the click versions to check types on

---
 .github/workflows/build.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index eef1887..767b234 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -8,7 +8,7 @@ jobs:
 
     strategy:
       matrix:
-        click-version: ["4.1", "5.1", "6.7", "7.1.2", "8.1.6"]
+        click-version: ["8.1.3", "8.1.7"]
         python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
 
     steps:

From b00df0bfa06e017addd347f9ee0580b312472324 Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Thu, 15 Feb 2024 09:29:45 +1100
Subject: [PATCH 5/8] Add flake8 to github actions workflow

---
 .github/workflows/build.yaml |  2 ++
 click_default_group.py       | 10 +++++++---
 pyproject.toml               |  2 +-
 setup.cfg                    |  1 +
 4 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 767b234..3dbc7ec 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -24,6 +24,8 @@ jobs:
         pip install click==${{ matrix.click-version }}
     - name: Test with pytest
       run: pytest
+    - name: Check with flake8
+      run: flake8 click_default_group.py test.py -v --show-source
     - name: Check type hints
       run: python3 -m mypy --follow-imports=silent --python-version "${{ matrix.python-version }}" --strict --implicit-reexport -- click_default_group.py
 
diff --git a/click_default_group.py b/click_default_group.py
index adfe4a4..a52eda8 100644
--- a/click_default_group.py
+++ b/click_default_group.py
@@ -107,12 +107,14 @@ def format_commands(
         return super().format_commands(ctx, new_formatter)
 
     @t.overload
-    def command(self, __func: t.Callable[..., t.Any]) -> click.core.Command: ...
+    def command(self, __func: t.Callable[..., t.Any]) -> click.core.Command:
+        ...
 
     @t.overload
     def command(
         self, *args: t.Any, **kwargs: t.Any
-    ) -> t.Callable[[t.Callable[..., t.Any]], click.core.Command]: ...
+    ) -> t.Callable[[t.Callable[..., t.Any]], click.core.Command]:
+        ...
 
     def command(
         self, *args: t.Any, **kwargs: t.Any
@@ -120,7 +122,9 @@ def command(
         t.Callable[[t.Callable[..., t.Any]], click.core.Command], click.core.Command
     ]:
         default = kwargs.pop("default", False)
-        decorator: t.Callable[[t.Callable[..., t.Any]], click.core.Command] = super().command(*args, **kwargs)
+        decorator: t.Callable[[t.Callable[..., t.Any]], click.core.Command] = (
+            super().command(*args, **kwargs)
+        )
         if not default:
             return decorator
         warnings.warn(
diff --git a/pyproject.toml b/pyproject.toml
index 67a77f0..513ab68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -33,4 +33,4 @@ dynamic = ["version", "description"]
 Source = "https://github.com/click-contrib/click-default-group"
 
 [project.optional-dependencies]
-test = ["pytest", "mypy"]
+test = ["pytest", "mypy", "flake8", "flake8-import-order", "pytest-cov", "coveralls"]
diff --git a/setup.cfg b/setup.cfg
index f5ca88b..e1d7f5e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,6 +2,7 @@
 ignore = E301
 import-order-style = google
 application-import-names = click_default_group
+max-line-length = 88
 
 [tool:pytest]
 python_files = test.py

From a4eb72bd462e4ef6ff37f7c71f55ebf521c9c82c Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Thu, 15 Feb 2024 09:30:42 +1100
Subject: [PATCH 6/8] We are not testing for these python versions so let's not
 advertise that they are supported

---
 pyproject.toml | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 513ab68..18a8978 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,11 +12,7 @@ classifiers = [
     "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",
@@ -25,7 +21,7 @@ classifiers = [
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
 ]
-requires-python = ">=2.7"
+requires-python = ">=3.7"
 dependencies = ["click"]
 dynamic = ["version", "description"]
 

From aa5ab61e460cdf488a9b17c01cb05ea0052e6dca Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Thu, 15 Feb 2024 09:34:04 +1100
Subject: [PATCH 7/8] Re-test on older versions of click

---
 .github/workflows/build.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 3dbc7ec..db6449e 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -8,7 +8,7 @@ jobs:
 
     strategy:
       matrix:
-        click-version: ["8.1.3", "8.1.7"]
+        click-version: ["8.1.3", "8.1.7", "7.1.2", "6.7", "5.1"]
         python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
 
     steps:

From 94d6e7e9a988915c6eccdd458b418d37640e586a Mon Sep 17 00:00:00 2001
From: John Newbigin <john.newbigin@rea-group.com>
Date: Thu, 15 Feb 2024 09:43:35 +1100
Subject: [PATCH 8/8] Only type check click version 8

---
 .github/workflows/build.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index db6449e..f610a72 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -27,6 +27,7 @@ jobs:
     - name: Check with flake8
       run: flake8 click_default_group.py test.py -v --show-source
     - name: Check type hints
+      if: ${{ startsWith( matrix.click-version, '8.' ) }}
       run: python3 -m mypy --follow-imports=silent --python-version "${{ matrix.python-version }}" --strict --implicit-reexport -- click_default_group.py
 
   build: