Skip to content

Commit

Permalink
feat: Output similar commands or script command when the input comman…
Browse files Browse the repository at this point in the history
…d is not correct (#3274)

* feat: Output similar commands or script command when the input command is not correct

Signed-off-by: Manjusaka <me@manjusaka.me>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix test

Signed-off-by: Manjusaka <me@manjusaka.me>

* fix hint error

Signed-off-by: Manjusaka <me@manjusaka.me>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fiix type check error

Signed-off-by: Manjusaka <me@manjusaka.me>

* fix review idea

Signed-off-by: Manjusaka <me@manjusaka.me>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix lint

Signed-off-by: Manjusaka <me@manjusaka.me>

---------

Signed-off-by: Manjusaka <me@manjusaka.me>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
Zheaoli and pre-commit-ci[bot] authored Nov 12, 2024
1 parent 91e02dd commit 8c1758f
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 15 deletions.
1 change: 1 addition & 0 deletions news/3270.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Output similar commands or script command when the input command is not correct
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ testpaths = [
]

[tool.codespell]
ignore-words-list = "ba,overriden,te"
ignore-words-list = "ba,overriden,te,instal"

[tool.coverage.run]
branch = true
Expand Down
42 changes: 32 additions & 10 deletions src/pdm/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,12 @@
import re
import sys
from collections import OrderedDict
from difflib import SequenceMatcher
from fnmatch import fnmatch
from gettext import gettext as _
from json import dumps
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterable,
Mapping,
MutableMapping,
cast,
no_type_check,
)
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, MutableMapping, cast, no_type_check

from packaging.specifiers import SpecifierSet
from resolvelib.structs import DirectedGraph
Expand Down Expand Up @@ -156,6 +148,36 @@ def parse_known_args(self, args: Any = None, namespace: Any = None) -> Any:
return args, argv


def find_similar_text(origin_name: str, target_names: list[str], ratio: float = 0.5) -> list[str]:
results = []
for name in target_names:
ratio = SequenceMatcher(None, origin_name, name).ratio()
if ratio > 0.4:
results.append((name, ratio))
return [name for name, _ in sorted(results, key=lambda x: x[1], reverse=True)]


def format_similar_command(root_command: str, commands: list[str], script_commands: list[str]) -> str:
similar_commands = find_similar_text(root_command, commands)
similar_script_commands = find_similar_text(root_command, script_commands)
commands_text = "\n".join([f" - {cmd}" for cmd in similar_commands])
script_commands_text = "\n".join([f" - {cmd}" for cmd in similar_script_commands])
message = f"""[red]Command not found: {root_command}[/]
"""
if commands_text:
message += f"""
[green]Did you mean one of these command?
{commands_text}[/]
"""

if script_commands_text:
message += f"""
[yellow]Or one of these script command?
{script_commands_text}[/]
"""
return message


class ErrorArgumentParser(ArgumentParser):
"""A subclass of argparse.ArgumentParser that raises
parsing error rather than exiting.
Expand Down
11 changes: 8 additions & 3 deletions src/pdm/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
from functools import cached_property
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, ClassVar, cast

import tomlkit.exceptions

from pdm import termui
from pdm.__version__ import __version__
from pdm.cli.options import ignore_python_option, no_cache_option, non_interactive_option, pep582_option, verbose_option
from pdm.cli.utils import ArgumentParser, ErrorArgumentParser
from pdm.cli.utils import ArgumentParser, ErrorArgumentParser, format_similar_command
from pdm.compat import importlib_metadata
from pdm.exceptions import PdmArgumentError, PdmUsageError
from pdm.installers import InstallManager
Expand Down Expand Up @@ -70,6 +70,7 @@ class Core:
project_class = Project
repository_class: type[BaseRepository] = PyPIRepository
install_manager_class = InstallManager
commands: ClassVar[list[str]] = []

def __init__(self) -> None:
self.version = __version__
Expand Down Expand Up @@ -256,7 +257,9 @@ def main(

project = self.ensure_project(options, obj)
if root_script and root_script not in project.scripts:
self.parser.error(f"Script unknown: {root_script}")
message = format_similar_command(root_script, self.commands, list(project.scripts.keys()))
message = termui.style(message)
self.parser.error(message)

try:
self.handle(project, options)
Expand Down Expand Up @@ -289,6 +292,8 @@ def register_command(self, command: type[BaseCommand], name: str | None = None)
is used
"""
assert self.subparsers
if name:
self.commands.append(name)
command.register_to(self.subparsers, name)

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion tests/cli/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ def test_run_shortcuts_dont_override_commands(project, pdm, capfd, mocker):
def test_run_shortcut_fail_with_usage_if_script_not_found(project, pdm):
result = pdm(["whatever"], obj=project)
assert result.exit_code != 0
assert "Script unknown: whatever" in result.stderr
assert "Command not found: whatever" in result.stderr
assert "Usage" in result.stderr


Expand Down
6 changes: 6 additions & 0 deletions tests/cli/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ def test_help_with_unknown_arguments(pdm):
result = pdm(["add", "--unknown-args"])
assert "Usage: pdm add " in result.stderr
assert result.exit_code == 2


def test_output_similar_command_when_typo(pdm):
result = pdm(["instal"])
assert "install" in result.stderr
assert result.exit_code == 2

0 comments on commit 8c1758f

Please sign in to comment.