From 16de31b9d6781def4bc9502639d39966edbe2f39 Mon Sep 17 00:00:00 2001 From: Stef Pletinck Date: Thu, 5 Oct 2017 10:36:51 +0200 Subject: [PATCH 1/5] basic dnf module --- thefuck/rules/dnf_no_such_command.py | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 thefuck/rules/dnf_no_such_command.py diff --git a/thefuck/rules/dnf_no_such_command.py b/thefuck/rules/dnf_no_such_command.py new file mode 100644 index 00000000..227ca03e --- /dev/null +++ b/thefuck/rules/dnf_no_such_command.py @@ -0,0 +1,50 @@ +import re +from thefuck.specific.sudo import sudo_support +from thefuck.utils import for_app, replace_command + +dnf_commands = [ + 'autoremove', + 'check', + 'check-update', + 'clean', + 'deplist', + 'distro-sync', + 'downgrade', + 'group', + 'help', + 'history', + 'info', + 'install', + 'list', + 'makecache', + 'mark', + 'provides', + 'reinstall', + 'remove', + 'repolist', + 'repoquery', + 'repository-packages', + 'search', + 'shell', + 'swap', + 'updateinfo', + 'upgrade', + 'upgrade-minimal' +] + +regex = re.compile(r'No such command: (.*)\.') + + +@for_app('dnf') +@sudo_support +def match(command): + return 'no such command' in command.output.lower() + + +@sudo_support +def get_new_command(command): + misspelled_command = regex.findall(command.output)[0] + return replace_command(command, misspelled_command, dnf_commands) + + +enabled_by_default = True From d6b2c512f7a9e51b61b176659332cb85aed42a16 Mon Sep 17 00:00:00 2001 From: Stef Pletinck Date: Thu, 5 Oct 2017 16:40:17 +0200 Subject: [PATCH 2/5] DNF module only enabled when DNF available and dynamically loads corrections --- thefuck/rules/dnf_no_such_command.py | 49 ++++++++++------------------ thefuck/specific/dnf.py | 3 ++ 2 files changed, 21 insertions(+), 31 deletions(-) create mode 100644 thefuck/specific/dnf.py diff --git a/thefuck/rules/dnf_no_such_command.py b/thefuck/rules/dnf_no_such_command.py index 227ca03e..27d353a0 100644 --- a/thefuck/rules/dnf_no_such_command.py +++ b/thefuck/rules/dnf_no_such_command.py @@ -1,36 +1,9 @@ +import subprocess import re from thefuck.specific.sudo import sudo_support from thefuck.utils import for_app, replace_command +from thefuck.specific.dnf import dnf_available -dnf_commands = [ - 'autoremove', - 'check', - 'check-update', - 'clean', - 'deplist', - 'distro-sync', - 'downgrade', - 'group', - 'help', - 'history', - 'info', - 'install', - 'list', - 'makecache', - 'mark', - 'provides', - 'reinstall', - 'remove', - 'repolist', - 'repoquery', - 'repository-packages', - 'search', - 'shell', - 'swap', - 'updateinfo', - 'upgrade', - 'upgrade-minimal' -] regex = re.compile(r'No such command: (.*)\.') @@ -41,10 +14,24 @@ def match(command): return 'no such command' in command.output.lower() +def _parse_operations(help_text_lines): + operation_regex = re.compile(b'^([a-z-]+) +', re.MULTILINE) + return operation_regex.findall(help_text_lines) + + +def _get_operations(): + proc = subprocess.Popen(["dnf", '--help'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + lines = proc.stdout.read() + + return _parse_operations(lines) + + @sudo_support def get_new_command(command): misspelled_command = regex.findall(command.output)[0] - return replace_command(command, misspelled_command, dnf_commands) + return replace_command(command, misspelled_command, _get_operations()) -enabled_by_default = True +enabled_by_default = dnf_available diff --git a/thefuck/specific/dnf.py b/thefuck/specific/dnf.py new file mode 100644 index 00000000..0e34cfd9 --- /dev/null +++ b/thefuck/specific/dnf.py @@ -0,0 +1,3 @@ +from thefuck.utils import which + +dnf_available = bool(which('dnf')) From f465fb3ed8be496e057b0d9e008dca7e9b96f2d6 Mon Sep 17 00:00:00 2001 From: Stef Pletinck Date: Thu, 5 Oct 2017 16:50:44 +0200 Subject: [PATCH 3/5] Slightly more comments --- thefuck/rules/dnf_no_such_command.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/thefuck/rules/dnf_no_such_command.py b/thefuck/rules/dnf_no_such_command.py index 27d353a0..70c79397 100644 --- a/thefuck/rules/dnf_no_such_command.py +++ b/thefuck/rules/dnf_no_such_command.py @@ -15,6 +15,9 @@ def match(command): def _parse_operations(help_text_lines): + # The regex has to be a bytes-style regex since reading from a file + # like stdin returns a bytes-style object and a string-style regex + # wouldn't work. operation_regex = re.compile(b'^([a-z-]+) +', re.MULTILINE) return operation_regex.findall(help_text_lines) From 449cb9a00693c8b4d97d5fda8d732cf0978e117e Mon Sep 17 00:00:00 2001 From: Stef Pletinck Date: Fri, 6 Oct 2017 17:13:29 +0200 Subject: [PATCH 4/5] Added README line --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7f0757b5..360b1981 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ Enabled by default only on specific platforms: * `brew_uninstall` – adds `--force` to `brew uninstall` if multiple versions were installed; * `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`; * `brew_update_formula` – turns `brew update ` into `brew upgrade `; +* `dnf_no_such_command` – fixes mistyped DNF commands; * `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available); * `pacman_not_found` – fixes package name with `pacman` or `yaourt`. From be48f027847161f907def8987706041c65a1fd58 Mon Sep 17 00:00:00 2001 From: Stef Pletinck Date: Sat, 7 Oct 2017 12:59:21 +0200 Subject: [PATCH 5/5] Tests! Also fixed some bytes-string issues --- tests/rules/test_dnf_no_such_command.py | 190 ++++++++++++++++++++++++ thefuck/rules/dnf_no_such_command.py | 7 +- 2 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 tests/rules/test_dnf_no_such_command.py diff --git a/tests/rules/test_dnf_no_such_command.py b/tests/rules/test_dnf_no_such_command.py new file mode 100644 index 00000000..6229828f --- /dev/null +++ b/tests/rules/test_dnf_no_such_command.py @@ -0,0 +1,190 @@ +from io import BytesIO +import pytest +from thefuck.types import Command +from thefuck.rules.dnf_no_such_command import match, get_new_command, _get_operations + + +help_text = b'''usage: dnf [options] COMMAND + +List of Main Commands: + +autoremove remove all unneeded packages that were originally installed as dependencies +check check for problems in the packagedb +check-update check for available package upgrades +clean remove cached data +deplist List package's dependencies and what packages provide them +distro-sync synchronize installed packages to the latest available versions +downgrade Downgrade a package +group display, or use, the groups information +help display a helpful usage message +history display, or use, the transaction history +info display details about a package or group of packages +install install a package or packages on your system +list list a package or groups of packages +makecache generate the metadata cache +mark mark or unmark installed packages as installed by user. +provides find what package provides the given value +reinstall reinstall a package +remove remove a package or packages from your system +repolist display the configured software repositories +repoquery search for packages matching keyword +repository-packages run commands on top of all packages in given repository +search search package details for the given string +shell run an interactive DNF shell +swap run an interactive dnf mod for remove and install one spec +updateinfo display advisories about packages +upgrade upgrade a package or packages on your system +upgrade-minimal upgrade, but only 'newest' package match which fixes a problem that affects your system + +List of Plugin Commands: + +builddep Install build dependencies for package or spec file +config-manager manage dnf configuration options and repositories +copr Interact with Copr repositories. +debug-dump dump information about installed rpm packages to file +debug-restore restore packages recorded in debug-dump file +debuginfo-install install debuginfo packages +download Download package to current directory +needs-restarting determine updated binaries that need restarting +playground Interact with Playground repository. +repoclosure Display a list of unresolved dependencies for repositories +repograph Output a full package dependency graph in dot format +repomanage Manage a directory of rpm packages +reposync download all packages from remote repo + +Optional arguments: + -c [config file], --config [config file] + config file location + -q, --quiet quiet operation + -v, --verbose verbose operation + --version show DNF version and exit + --installroot [path] set install root + --nodocs do not install documentations + --noplugins disable all plugins + --enableplugin [plugin] + enable plugins by name + --disableplugin [plugin] + disable plugins by name + --releasever RELEASEVER + override the value of $releasever in config and repo + files + --setopt SETOPTS set arbitrary config and repo options + --skip-broken resolve depsolve problems by skipping packages + -h, --help, --help-cmd + show command help + --allowerasing allow erasing of installed packages to resolve + dependencies + -b, --best try the best available package versions in + transactions. + -C, --cacheonly run entirely from system cache, don't update cache + -R [minutes], --randomwait [minutes] + maximum command wait time + -d [debug level], --debuglevel [debug level] + debugging output level + --debugsolver dumps detailed solving results into files + --showduplicates show duplicates, in repos, in list/search commands + -e ERRORLEVEL, --errorlevel ERRORLEVEL + error output level + --obsoletes enables dnf's obsoletes processing logic for upgrade + or display capabilities that the package obsoletes for + info, list and repoquery + --rpmverbosity [debug level name] + debugging output level for rpm + -y, --assumeyes automatically answer yes for all questions + --assumeno automatically answer no for all questions + --enablerepo [repo] + --disablerepo [repo] + --repo [repo], --repoid [repo] + enable just specific repositories by an id or a glob, + can be specified multiple times + -x [package], --exclude [package], --excludepkgs [package] + exclude packages by name or glob + --disableexcludes [repo], --disableexcludepkgs [repo] + disable excludepkgs + --repofrompath [repo,path] + label and path to additional repository, can be + specified multiple times. + --noautoremove disable removal of dependencies that are no longer + used + --nogpgcheck disable gpg signature checking + --color COLOR control whether colour is used + --refresh set metadata as expired before running the command + -4 resolve to IPv4 addresses only + -6 resolve to IPv6 addresses only + --destdir DESTDIR, --downloaddir DESTDIR + set directory to copy packages to + --downloadonly only download packages + --bugfix Include bugfix relevant packages, in updates + --enhancement Include enhancement relevant packages, in updates + --newpackage Include newpackage relevant packages, in updates + --security Include security relevant packages, in updates + --advisory ADVISORY, --advisories ADVISORY + Include packages needed to fix the given advisory, in + updates + --bzs BUGZILLA Include packages needed to fix the given BZ, in + updates + --cves CVES Include packages needed to fix the given CVE, in + updates + --sec-severity {Critical,Important,Moderate,Low}, --secseverity {Critical,Important,Moderate,Low} + Include security relevant packages matching the + severity, in updates + --forcearch ARCH Force the use of an architecture +''' + +dnf_operations = ['autoremove', 'check', 'check-update', 'clean', 'deplist', + 'distro-sync', 'downgrade', 'group', 'help', 'history', + 'info', 'install', 'list', 'makecache', 'mark', 'provides', + 'reinstall', 'remove', 'repolist', 'repoquery', + 'repository-packages', 'search', 'shell', 'swap', 'updateinfo', + 'upgrade', 'upgrade-minimal', 'builddep', 'config-manager', + 'copr', 'debug-dump', 'debug-restore', 'debuginfo-install', + 'download', 'needs-restarting', 'playground', 'repoclosure', + 'repograph', 'repomanage', 'reposync'] + + +def invalid_command(command): + return """No such command: %s. Please use /usr/bin/dnf --help +It could be a DNF plugin command, try: "dnf install 'dnf-command(%s)'" +""" % (command, command) + + +@pytest.mark.parametrize('output', [ + (invalid_command('saerch')), + (invalid_command('isntall')) +]) +def test_match(output): + assert match(Command('dnf', output)) + + +@pytest.mark.parametrize('script, output', [ + ('pip', invalid_command('isntall')), + ('vim', "") +]) +def test_not_match(script, output): + assert not match(Command(script, output)) + + +@pytest.fixture +def set_help(mocker): + mock = mocker.patch('subprocess.Popen') + + def _set_text(text): + mock.return_value.stdout = BytesIO(text) + + return _set_text + + +def test_get_operations(set_help): + set_help(help_text) + assert _get_operations() == dnf_operations + + +@pytest.mark.parametrize('script, output, result', [ + ('dnf isntall vim', invalid_command('isntall'), + 'dnf install vim'), + ('dnf saerch vim', invalid_command('saerch'), + 'dnf search vim'), +]) +def test_get_new_command(set_help, output, script, result): + set_help(help_text) + assert result in get_new_command(Command(script, output)) diff --git a/thefuck/rules/dnf_no_such_command.py b/thefuck/rules/dnf_no_such_command.py index 70c79397..9a70c4f1 100644 --- a/thefuck/rules/dnf_no_such_command.py +++ b/thefuck/rules/dnf_no_such_command.py @@ -15,10 +15,7 @@ def match(command): def _parse_operations(help_text_lines): - # The regex has to be a bytes-style regex since reading from a file - # like stdin returns a bytes-style object and a string-style regex - # wouldn't work. - operation_regex = re.compile(b'^([a-z-]+) +', re.MULTILINE) + operation_regex = re.compile(r'^([a-z-]+) +', re.MULTILINE) return operation_regex.findall(help_text_lines) @@ -26,7 +23,7 @@ def _get_operations(): proc = subprocess.Popen(["dnf", '--help'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - lines = proc.stdout.read() + lines = proc.stdout.read().decode("utf-8") return _parse_operations(lines)