Skip to content
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

### Added

- Add support for `click.Command`-like, `click.Group`-like and `click.Context`-like objects without requiring them to be actual subclasses. (Pull #82)

### Fixed

- Remove explicit reference to `click.BaseCommand` and `click.MultiCommand` objects in anticipation of their deprecation. (Pull #82)

## 0.8.1 - 2023-09-18

### Fixed
Expand Down
28 changes: 22 additions & 6 deletions mkdocs_click/_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

def make_command_docs(
prog_name: str,
command: click.BaseCommand,
command: click.Command,
depth: int = 0,
style: str = "plain",
remove_ascii_art: bool = False,
Expand All @@ -42,7 +42,7 @@ def make_command_docs(

def _recursively_make_command_docs(
prog_name: str,
command: click.BaseCommand,
command: click.Command,
parent: click.Context | None = None,
depth: int = 0,
style: str = "plain",
Expand Down Expand Up @@ -90,20 +90,26 @@ def _recursively_make_command_docs(


def _build_command_context(
prog_name: str, command: click.BaseCommand, parent: click.Context | None
prog_name: str, command: click.Command, parent: click.Context | None
) -> click.Context:
return click.Context(cast(click.Command, command), info_name=prog_name, parent=parent)
return _get_context_class(command)(
cast(click.Command, command), info_name=prog_name, parent=parent
)


def _get_sub_commands(command: click.Command, ctx: click.Context) -> list[click.Command]:
def _get_sub_commands(
command: click.Command | click.Group, ctx: click.Context
) -> list[click.Command]:
"""Return subcommands of a Click command."""
subcommands = getattr(command, "commands", {})
if subcommands:
return list(subcommands.values())

if not isinstance(command, click.MultiCommand):
if not _is_command_group(command):
return []

command = cast(click.Group, command)

subcommands = []

for name in command.list_commands(ctx):
Expand Down Expand Up @@ -360,3 +366,13 @@ def _make_subcommands_links(
help_string = "*No description was provided with this command.*"
yield f"- *{command_bullet}*: {help_string}"
yield ""


def _get_context_class(command: click.Command) -> type[click.Context]:
# https://github.com/pallets/click/blob/8.1.8/src/click/core.py#L859-L862
return command.context_class


def _is_command_group(command: click.Command) -> bool:
# https://github.com/pallets/click/blob/8.1.8/src/click/core.py#L1806-L1811
return isinstance(command, click.Group) or hasattr(command, "command_class")
2 changes: 1 addition & 1 deletion mkdocs_click/_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def replace_command_docs(has_attr_list: bool = False, **options: Any) -> Iterato

module = options["module"]
command = options["command"]
prog_name = options.get("prog_name", None)
prog_name = options.get("prog_name")
depth = int(options.get("depth", 0))
style = options.get("style", "plain")
remove_ascii_art = options.get("remove_ascii_art", False)
Expand Down
6 changes: 3 additions & 3 deletions mkdocs_click/_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
from ._exceptions import MkDocsClickException


def load_command(module: str, attribute: str) -> click.BaseCommand:
def load_command(module: str, attribute: str) -> click.Command:
"""
Load and return the Click command object located at '<module>:<attribute>'.
"""
command = _load_obj(module, attribute)

if not isinstance(command, click.BaseCommand):
if not (isinstance(command, click.Command) or hasattr(command, "context_class")):
raise MkDocsClickException(
f"{attribute!r} must be a 'click.BaseCommand' object, got {type(command)}"
f"{attribute!r} must be a 'click.Command'-like object, got {type(command)}"
)

return command
Expand Down
6 changes: 3 additions & 3 deletions tests/app/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def hidden():
cli_named.add_command(hidden)


class MultiCLI(click.MultiCommand):
class GroupCLI(click.Group):
def list_commands(self, ctx):
return ["foo", "bar"]

Expand All @@ -59,5 +59,5 @@ def get_command(self, ctx, name):
return cmds.get(name)


multi_named = MultiCLI(name="multi", help="Main entrypoint for this dummy program")
multi = MultiCLI(help="Main entrypoint for this dummy program")
group_named = GroupCLI(name="group", help="Main entrypoint for this dummy program")
group = GroupCLI(help="Main entrypoint for this dummy program")
42 changes: 21 additions & 21 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def test_style_table(command, expected):
assert output == expected


class MultiCLI(click.MultiCommand):
class GroupCLI(click.Group):
def list_commands(self, ctx):
return ["single-command"]

Expand All @@ -184,26 +184,26 @@ def get_command(self, ctx, name):


@pytest.mark.parametrize(
"multi",
"group",
[
pytest.param(MultiCLI("multi", help="Multi help"), id="explicit-name"),
pytest.param(MultiCLI(help="Multi help"), id="no-name"),
pytest.param(GroupCLI("group", help="Group help"), id="explicit-name"),
pytest.param(GroupCLI(help="Group help"), id="no-name"),
],
)
def test_custom_multicommand(multi):
def test_custom_group(group):
"""
Custom `MultiCommand` objects are supported (i.e. not just `Group` multi-commands).
Custom `Group` objects are supported.
"""
expected = dedent(
"""
# multi
# group

Multi help
Group help

**Usage:**

```text
multi [OPTIONS] COMMAND [ARGS]...
group [OPTIONS] COMMAND [ARGS]...
```

**Options:**
Expand All @@ -219,7 +219,7 @@ def test_custom_multicommand(multi):
**Usage:**

```text
multi hello [OPTIONS]
group hello [OPTIONS]
```

**Options:**
Expand All @@ -231,31 +231,31 @@ def test_custom_multicommand(multi):
"""
).lstrip()

output = "\n".join(make_command_docs("multi", multi))
output = "\n".join(make_command_docs("group", group))
assert output == expected


@pytest.mark.parametrize(
"multi",
"group",
[
pytest.param(MultiCLI("multi", help="Multi help"), id="explicit-name"),
pytest.param(MultiCLI(help="Multi help"), id="no-name"),
pytest.param(GroupCLI("group", help="Group help"), id="explicit-name"),
pytest.param(GroupCLI(help="Group help"), id="no-name"),
],
)
def test_custom_multicommand_with_list_subcommands(multi):
def test_custom_group_with_list_subcommands(group):
"""
Custom `MultiCommand` objects are supported (i.e. not just `Group` multi-commands).
Custom `Group` objects are supported.
"""
expected = dedent(
"""
# multi
# group

Multi help
Group help

**Usage:**

```text
multi [OPTIONS] COMMAND [ARGS]...
group [OPTIONS] COMMAND [ARGS]...
```

**Options:**
Expand All @@ -275,7 +275,7 @@ def test_custom_multicommand_with_list_subcommands(multi):
**Usage:**

```text
multi hello [OPTIONS]
group hello [OPTIONS]
```

**Options:**
Expand All @@ -287,7 +287,7 @@ def test_custom_multicommand_with_list_subcommands(multi):
"""
).lstrip()

output = "\n".join(make_command_docs("multi", multi, list_subcommands=True))
output = "\n".join(make_command_docs("group", group, list_subcommands=True))
assert output == expected


Expand Down
12 changes: 6 additions & 6 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
[
pytest.param("cli", "cli", id="cli-simple"),
pytest.param("cli_named", "cli", id="cli-explicit-name"),
pytest.param("multi_named", "multi", id="multi-explicit-name"),
pytest.param("multi", "multi", id="no-name"),
pytest.param("group_named", "group", id="group-explicit-name"),
pytest.param("group", "group", id="no-name"),
],
)
def test_extension(command, expected_name):
Expand Down Expand Up @@ -133,8 +133,8 @@ def test_enhanced_titles():
[
pytest.param("cli", "cli", id="cli-simple"),
pytest.param("cli_named", "cli", id="cli-explicit-name"),
pytest.param("multi_named", "multi", id="multi-explicit-name"),
pytest.param("multi", "multi", id="no-name"),
pytest.param("group_named", "group", id="group-explicit-name"),
pytest.param("group", "group", id="no-name"),
],
)
def test_extension_with_subcommand(command, expected_name):
Expand Down Expand Up @@ -162,8 +162,8 @@ def test_extension_with_subcommand(command, expected_name):
[
pytest.param("cli", "cli", id="cli-simple"),
pytest.param("cli_named", "cli", id="cli-explicit-name"),
pytest.param("multi_named", "multi", id="multi-explicit-name"),
pytest.param("multi", "multi", id="no-name"),
pytest.param("group_named", "group", id="group-explicit-name"),
pytest.param("group", "group", id="no-name"),
],
)
def test_enhanced_titles_with_subcommand(command, expected_name):
Expand Down