From 0c283ff2b8fe1bea14d5b1e268226fc0f6e2a4d6 Mon Sep 17 00:00:00 2001 From: nvbn Date: Thu, 27 Aug 2015 16:42:09 +0300 Subject: [PATCH] #334 Speed-up rules with caching `for_app` decorator --- tests/rules/test_lein_not_task.py | 8 ++++---- tests/rules/test_ls_lah.py | 18 +++++++++--------- thefuck/rules/apt_get_search.py | 2 ++ thefuck/rules/cargo_no_command.py | 6 +++--- thefuck/rules/cd_correction.py | 2 ++ thefuck/rules/cd_mkdir.py | 7 ++++--- thefuck/rules/composer_not_command.py | 8 ++++---- thefuck/rules/cp_omitting_directory.py | 5 +++-- thefuck/rules/cpp11.py | 11 +++++++---- thefuck/rules/dirty_untar.py | 11 ++++++----- thefuck/rules/dirty_unzip.py | 5 +++-- thefuck/rules/docker_not_command.py | 6 +++--- thefuck/rules/git_push_force.py | 1 - thefuck/rules/go_run.py | 2 ++ thefuck/rules/grep_recursive.py | 7 +++++-- thefuck/rules/gulp_not_task.py | 6 +++--- thefuck/rules/heroku_not_command.py | 6 +++--- thefuck/rules/java.py | 17 ++++++++++------- thefuck/rules/javac.py | 19 +++++++++++-------- thefuck/rules/lein_not_task.py | 3 ++- thefuck/rules/ls_lah.py | 8 +++++--- thefuck/rules/mercurial.py | 14 ++++++-------- thefuck/rules/open.py | 24 ++++++++++++------------ thefuck/rules/pip_unknown_command.py | 5 ++++- thefuck/rules/python_execute.py | 5 +++-- thefuck/rules/sed_unterminated_s.py | 6 +++--- thefuck/rules/ssh_known_hosts.py | 2 ++ thefuck/rules/systemctl.py | 5 +++-- thefuck/rules/tmux.py | 6 +++--- thefuck/rules/tsuru_login.py | 5 +++-- thefuck/rules/tsuru_not_command.py | 6 +++--- thefuck/rules/vagrant_up.py | 4 +++- thefuck/specific/git.py | 15 ++++++--------- 33 files changed, 142 insertions(+), 113 deletions(-) diff --git a/tests/rules/test_lein_not_task.py b/tests/rules/test_lein_not_task.py index 9eef9b44..9069fcd0 100644 --- a/tests/rules/test_lein_not_task.py +++ b/tests/rules/test_lein_not_task.py @@ -1,6 +1,6 @@ import pytest -from mock import Mock from thefuck.rules.lein_not_task import match, get_new_command +from tests.utils import Command @pytest.fixture @@ -14,10 +14,10 @@ Did you mean this? def test_match(is_not_task): - assert match(Mock(script='lein rpl', stderr=is_not_task), None) - assert not match(Mock(script='ls', stderr=is_not_task), None) + assert match(Command(script='lein rpl', stderr=is_not_task), None) + assert not match(Command(script='ls', stderr=is_not_task), None) def test_get_new_command(is_not_task): - assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task), + assert get_new_command(Command(script='lein rpl --help', stderr=is_not_task), None) == ['lein repl --help', 'lein jar --help'] diff --git a/tests/rules/test_ls_lah.py b/tests/rules/test_ls_lah.py index 66bc8365..97325258 100644 --- a/tests/rules/test_ls_lah.py +++ b/tests/rules/test_ls_lah.py @@ -1,16 +1,16 @@ -from mock import patch, Mock from thefuck.rules.ls_lah import match, get_new_command +from tests.utils import Command def test_match(): - assert match(Mock(script='ls'), None) - assert match(Mock(script='ls file.py'), None) - assert match(Mock(script='ls /opt'), None) - assert not match(Mock(script='ls -lah /opt'), None) - assert not match(Mock(script='pacman -S binutils'), None) - assert not match(Mock(script='lsof'), None) + assert match(Command(script='ls'), None) + assert match(Command(script='ls file.py'), None) + assert match(Command(script='ls /opt'), None) + assert not match(Command(script='ls -lah /opt'), None) + assert not match(Command(script='pacman -S binutils'), None) + assert not match(Command(script='lsof'), None) def test_get_new_command(): - assert get_new_command(Mock(script='ls file.py'), None) == 'ls -lah file.py' - assert get_new_command(Mock(script='ls'), None) == 'ls -lah' + assert get_new_command(Command(script='ls file.py'), None) == 'ls -lah file.py' + assert get_new_command(Command(script='ls'), None) == 'ls -lah' diff --git a/thefuck/rules/apt_get_search.py b/thefuck/rules/apt_get_search.py index 6c06ddde..4454e85f 100644 --- a/thefuck/rules/apt_get_search.py +++ b/thefuck/rules/apt_get_search.py @@ -1,6 +1,8 @@ import re +from thefuck.utils import for_app +@for_app('apt-get') def match(command, settings): return command.script.startswith('apt-get search') diff --git a/thefuck/rules/cargo_no_command.py b/thefuck/rules/cargo_no_command.py index c4f6c072..ce77fe6e 100644 --- a/thefuck/rules/cargo_no_command.py +++ b/thefuck/rules/cargo_no_command.py @@ -1,10 +1,10 @@ import re -from thefuck.utils import replace_argument +from thefuck.utils import replace_argument, for_app +@for_app('cargo') def match(command, settings): - return ('cargo' in command.script - and 'No such subcommand' in command.stderr + return ('No such subcommand' in command.stderr and 'Did you mean' in command.stderr) diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py index 1567ff09..33c4fc30 100644 --- a/thefuck/rules/cd_correction.py +++ b/thefuck/rules/cd_correction.py @@ -4,6 +4,7 @@ import os from difflib import get_close_matches from thefuck.specific.sudo import sudo_support from thefuck.rules import cd_mkdir +from thefuck.utils import for_app __author__ = "mmussomele" @@ -16,6 +17,7 @@ def _get_sub_dirs(parent): @sudo_support +@for_app('cd') def match(command, settings): """Match function copied from cd_mkdir.py""" return (command.script.startswith('cd ') diff --git a/thefuck/rules/cd_mkdir.py b/thefuck/rules/cd_mkdir.py index c9ad1f7e..2262af74 100644 --- a/thefuck/rules/cd_mkdir.py +++ b/thefuck/rules/cd_mkdir.py @@ -1,13 +1,14 @@ import re from thefuck import shells +from thefuck.utils import for_app from thefuck.specific.sudo import sudo_support @sudo_support +@for_app('cd') def match(command, settings): - return (command.script.startswith('cd ') - and ('no such file or directory' in command.stderr.lower() - or 'cd: can\'t cd to' in command.stderr.lower())) + return (('no such file or directory' in command.stderr.lower() + or 'cd: can\'t cd to' in command.stderr.lower())) @sudo_support diff --git a/thefuck/rules/composer_not_command.py b/thefuck/rules/composer_not_command.py index 115ca180..abea7b34 100644 --- a/thefuck/rules/composer_not_command.py +++ b/thefuck/rules/composer_not_command.py @@ -1,11 +1,11 @@ import re -from thefuck.utils import replace_argument +from thefuck.utils import replace_argument, for_app +@for_app('composer') def match(command, settings): - return ('composer' in command.script - and ('did you mean this?' in command.stderr.lower() - or 'did you mean one of these?' in command.stderr.lower())) + return (('did you mean this?' in command.stderr.lower() + or 'did you mean one of these?' in command.stderr.lower())) def get_new_command(command, settings): diff --git a/thefuck/rules/cp_omitting_directory.py b/thefuck/rules/cp_omitting_directory.py index 9a110e0c..51fa1294 100644 --- a/thefuck/rules/cp_omitting_directory.py +++ b/thefuck/rules/cp_omitting_directory.py @@ -1,12 +1,13 @@ import re from thefuck.specific.sudo import sudo_support +from thefuck.utils import for_app @sudo_support +@for_app('cp') def match(command, settings): stderr = command.stderr.lower() - return command.script.startswith('cp ') \ - and ('omitting directory' in stderr or 'is a directory' in stderr) + return 'omitting directory' in stderr or 'is a directory' in stderr @sudo_support diff --git a/thefuck/rules/cpp11.py b/thefuck/rules/cpp11.py index 154ababc..200bf4d9 100644 --- a/thefuck/rules/cpp11.py +++ b/thefuck/rules/cpp11.py @@ -1,8 +1,11 @@ +from thefuck.utils import for_app + + +@for_app(['g++', 'clang++']) def match(command, settings): - return (('g++' in command.script or 'clang++' in command.script) and - ('This file requires compiler and library support for the ' - 'ISO C++ 2011 standard.' in command.stderr or - '-Wc++11-extensions' in command.stderr)) + return ('This file requires compiler and library support for the ' + 'ISO C++ 2011 standard.' in command.stderr or + '-Wc++11-extensions' in command.stderr) def get_new_command(command, settings): diff --git a/thefuck/rules/dirty_untar.py b/thefuck/rules/dirty_untar.py index 25300e7b..4fdf4cf6 100644 --- a/thefuck/rules/dirty_untar.py +++ b/thefuck/rules/dirty_untar.py @@ -1,6 +1,7 @@ -from thefuck import shells import os import tarfile +from thefuck import shells +from thefuck.utils import for_app def _is_tar_extract(cmd): @@ -20,19 +21,19 @@ def _tar_file(cmd): for c in cmd.split(): for ext in tar_extensions: if c.endswith(ext): - return (c, c[0:len(c)-len(ext)]) + return (c, c[0:len(c) - len(ext)]) +@for_app('tar') def match(command, settings): - return (command.script.startswith('tar') - and '-C' not in command.script + return ('-C' not in command.script and _is_tar_extract(command.script) and _tar_file(command.script) is not None) def get_new_command(command, settings): return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \ - .format(dir=_tar_file(command.script)[1], cmd=command.script) + .format(dir=_tar_file(command.script)[1], cmd=command.script) def side_effect(old_cmd, command, settings): diff --git a/thefuck/rules/dirty_unzip.py b/thefuck/rules/dirty_unzip.py index 738cf82f..bd2d5945 100644 --- a/thefuck/rules/dirty_unzip.py +++ b/thefuck/rules/dirty_unzip.py @@ -1,5 +1,6 @@ import os import zipfile +from thefuck.utils import for_app def _is_bad_zip(file): @@ -20,9 +21,9 @@ def _zip_file(command): return '{}.zip'.format(c) +@for_app('unzip') def match(command, settings): - return (command.script.startswith('unzip') - and '-d' not in command.script + return ('-d' not in command.script and _is_bad_zip(_zip_file(command))) diff --git a/thefuck/rules/docker_not_command.py b/thefuck/rules/docker_not_command.py index 73cb8611..44578e39 100644 --- a/thefuck/rules/docker_not_command.py +++ b/thefuck/rules/docker_not_command.py @@ -1,14 +1,14 @@ from itertools import dropwhile, takewhile, islice import re import subprocess -from thefuck.utils import replace_command +from thefuck.utils import replace_command, for_app from thefuck.specific.sudo import sudo_support @sudo_support +@for_app('docker') def match(command, settings): - return command.script.startswith('docker') \ - and 'is not a docker command' in command.stderr + return 'is not a docker command' in command.stderr def get_docker_commands(): diff --git a/thefuck/rules/git_push_force.py b/thefuck/rules/git_push_force.py index 52ecfe32..45a3085a 100644 --- a/thefuck/rules/git_push_force.py +++ b/thefuck/rules/git_push_force.py @@ -1,4 +1,3 @@ -from thefuck import utils from thefuck.utils import replace_argument from thefuck.specific.git import git_support diff --git a/thefuck/rules/go_run.py b/thefuck/rules/go_run.py index b32c646a..b009324b 100644 --- a/thefuck/rules/go_run.py +++ b/thefuck/rules/go_run.py @@ -1,3 +1,4 @@ +from thefuck.utils import for_app # Appends .go when compiling go files # # Example: @@ -5,6 +6,7 @@ # error: go run: no go files listed +@for_app('go') def match(command, settings): return (command.script.startswith('go run ') and not command.script.endswith('.go')) diff --git a/thefuck/rules/grep_recursive.py b/thefuck/rules/grep_recursive.py index f2876fe1..94547b26 100644 --- a/thefuck/rules/grep_recursive.py +++ b/thefuck/rules/grep_recursive.py @@ -1,6 +1,9 @@ +from thefuck.utils import for_app + + +@for_app('grep') def match(command, settings): - return (command.script.startswith('grep') - and 'is a directory' in command.stderr.lower()) + return 'is a directory' in command.stderr.lower() def get_new_command(command, settings): diff --git a/thefuck/rules/gulp_not_task.py b/thefuck/rules/gulp_not_task.py index c1a548c1..853fa609 100644 --- a/thefuck/rules/gulp_not_task.py +++ b/thefuck/rules/gulp_not_task.py @@ -1,11 +1,11 @@ import re import subprocess -from thefuck.utils import replace_command +from thefuck.utils import replace_command, for_app +@for_app('gulp') def match(command, script): - return command.script.startswith('gulp')\ - and 'is not in your gulpfile' in command.stdout + return 'is not in your gulpfile' in command.stdout def get_gulp_tasks(): diff --git a/thefuck/rules/heroku_not_command.py b/thefuck/rules/heroku_not_command.py index 87360bc8..a01e1577 100644 --- a/thefuck/rules/heroku_not_command.py +++ b/thefuck/rules/heroku_not_command.py @@ -1,10 +1,10 @@ import re -from thefuck.utils import replace_command +from thefuck.utils import replace_command, for_app +@for_app('heroku') def match(command, settings): - return command.script.startswith('heroku') and \ - 'is not a heroku command' in command.stderr and \ + return 'is not a heroku command' in command.stderr and \ 'Perhaps you meant' in command.stderr diff --git a/thefuck/rules/java.py b/thefuck/rules/java.py index d2852328..f7a33c74 100644 --- a/thefuck/rules/java.py +++ b/thefuck/rules/java.py @@ -1,13 +1,16 @@ -# Fixes common java command mistake -# -# Example: -# > java foo.java -# Error: Could not find or load main class foo.java +"""Fixes common java command mistake + +Example: +> java foo.java +Error: Could not find or load main class foo.java + +""" +from thefuck.utils import for_app +@for_app('java') def match(command, settings): - return (command.script.startswith('java ') - and command.script.endswith('.java')) + return command.script.endswith('.java') def get_new_command(command, settings): diff --git a/thefuck/rules/javac.py b/thefuck/rules/javac.py index 80e6b258..be40a5e7 100644 --- a/thefuck/rules/javac.py +++ b/thefuck/rules/javac.py @@ -1,14 +1,17 @@ -# Appends .java when compiling java files -# -# Example: -# > javac foo -# error: Class names, 'foo', are only accepted if annotation -# processing is explicitly requested +"""Appends .java when compiling java files + +Example: + > javac foo + error: Class names, 'foo', are only accepted if annotation + processing is explicitly requested + +""" +from thefuck.utils import for_app +@for_app('javac') def match(command, settings): - return (command.script.startswith('javac ') - and not command.script.endswith('.java')) + return not command.script.endswith('.java') def get_new_command(command, settings): diff --git a/thefuck/rules/lein_not_task.py b/thefuck/rules/lein_not_task.py index db98c951..3849ac55 100644 --- a/thefuck/rules/lein_not_task.py +++ b/thefuck/rules/lein_not_task.py @@ -1,9 +1,10 @@ import re -from thefuck.utils import replace_command, get_all_matched_commands +from thefuck.utils import replace_command, get_all_matched_commands, for_app from thefuck.specific.sudo import sudo_support @sudo_support +@for_app('lein') def match(command, settings): return (command.script.startswith('lein') and "is not a task. See 'lein help'" in command.stderr diff --git a/thefuck/rules/ls_lah.py b/thefuck/rules/ls_lah.py index 580744bb..b8e6590b 100644 --- a/thefuck/rules/ls_lah.py +++ b/thefuck/rules/ls_lah.py @@ -1,7 +1,9 @@ +from thefuck.utils import for_app + + +@for_app('ls') def match(command, settings): - return (command.script == 'ls' - or command.script.startswith('ls ') - and 'ls -' not in command.script) + return 'ls -' not in command.script def get_new_command(command, settings): diff --git a/thefuck/rules/mercurial.py b/thefuck/rules/mercurial.py index c2e9aa6e..338629aa 100644 --- a/thefuck/rules/mercurial.py +++ b/thefuck/rules/mercurial.py @@ -1,5 +1,5 @@ import re -from thefuck.utils import get_closest +from thefuck.utils import get_closest, for_app def extract_possibilities(command): @@ -12,14 +12,12 @@ def extract_possibilities(command): return possib +@for_app('hg') def match(command, settings): - return (command.script.startswith('hg ') - and ('hg: unknown command' in command.stderr - and '(did you mean one of ' in command.stderr - or "hg: command '" in command.stderr - and "' is ambiguous:" in command.stderr - ) - ) + return ('hg: unknown command' in command.stderr + and '(did you mean one of ' in command.stderr + or "hg: command '" in command.stderr + and "' is ambiguous:" in command.stderr) def get_new_command(command, settings): diff --git a/thefuck/rules/open.py b/thefuck/rules/open.py index 22aaea37..6de2c963 100644 --- a/thefuck/rules/open.py +++ b/thefuck/rules/open.py @@ -5,21 +5,21 @@ # The file ~/github.com does not exist. # Perhaps you meant 'http://github.com'? # +from thefuck.utils import for_app +@for_app('open', 'xdg-open', 'gnome-open', 'kde-open') def match(command, settings): - return (command.script.startswith(('open', 'xdg-open', 'gnome-open', 'kde-open')) - and ( - '.com' in command.script - or '.net' in command.script - or '.org' in command.script - or '.ly' in command.script - or '.io' in command.script - or '.se' in command.script - or '.edu' in command.script - or '.info' in command.script - or '.me' in command.script - or 'www.' in command.script)) + return ('.com' in command.script + or '.net' in command.script + or '.org' in command.script + or '.ly' in command.script + or '.io' in command.script + or '.se' in command.script + or '.edu' in command.script + or '.info' in command.script + or '.me' in command.script + or 'www.' in command.script) def get_new_command(command, settings): diff --git a/thefuck/rules/pip_unknown_command.py b/thefuck/rules/pip_unknown_command.py index 9ae185d3..61293f80 100644 --- a/thefuck/rules/pip_unknown_command.py +++ b/thefuck/rules/pip_unknown_command.py @@ -1,7 +1,10 @@ import re -from thefuck.utils import replace_argument +from thefuck.utils import replace_argument, for_app +from thefuck.specific.sudo import sudo_support +@sudo_support +@for_app('pip') def match(command, settings): return ('pip' in command.script and 'unknown command' in command.stderr and diff --git a/thefuck/rules/python_execute.py b/thefuck/rules/python_execute.py index d4d9d266..2de751e9 100644 --- a/thefuck/rules/python_execute.py +++ b/thefuck/rules/python_execute.py @@ -3,11 +3,12 @@ # Example: # > python foo # error: python: can't open file 'foo': [Errno 2] No such file or directory +from thefuck.utils import for_app +@for_app('python') def match(command, settings): - return (command.script.startswith('python ') - and not command.script.endswith('.py')) + return not command.script.endswith('.py') def get_new_command(command, settings): diff --git a/thefuck/rules/sed_unterminated_s.py b/thefuck/rules/sed_unterminated_s.py index 80334f94..5a8ea6d3 100644 --- a/thefuck/rules/sed_unterminated_s.py +++ b/thefuck/rules/sed_unterminated_s.py @@ -1,10 +1,10 @@ import shlex -from thefuck.utils import quote +from thefuck.utils import quote, for_app +@for_app('sed') def match(command, settings): - return ('sed' in command.script - and "unterminated `s' command" in command.stderr) + return "unterminated `s' command" in command.stderr def get_new_command(command, settings): diff --git a/thefuck/rules/ssh_known_hosts.py b/thefuck/rules/ssh_known_hosts.py index df908d00..b14533bb 100644 --- a/thefuck/rules/ssh_known_hosts.py +++ b/thefuck/rules/ssh_known_hosts.py @@ -1,4 +1,5 @@ import re +from thefuck.utils import for_app patterns = [ r'WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!', @@ -12,6 +13,7 @@ offending_pattern = re.compile( commands = ['ssh', 'scp'] +@for_app(*commands) def match(command, settings): if not command.script: return False diff --git a/thefuck/rules/systemctl.py b/thefuck/rules/systemctl.py index ef8c0ca1..3edca9fc 100644 --- a/thefuck/rules/systemctl.py +++ b/thefuck/rules/systemctl.py @@ -2,15 +2,16 @@ The confusion in systemctl's param order is massive. """ from thefuck.specific.sudo import sudo_support +from thefuck.utils import for_app @sudo_support +@for_app('systemctl') def match(command, settings): # Catches 'Unknown operation 'service'.' when executing systemctl with # misordered arguments cmd = command.script.split() - return ('systemctl' in command.script and - 'Unknown operation \'' in command.stderr and + return ('Unknown operation \'' in command.stderr and len(cmd) - cmd.index('systemctl') == 3) diff --git a/thefuck/rules/tmux.py b/thefuck/rules/tmux.py index 2ba446e3..09acb578 100644 --- a/thefuck/rules/tmux.py +++ b/thefuck/rules/tmux.py @@ -1,10 +1,10 @@ -from thefuck.utils import replace_command import re +from thefuck.utils import replace_command, for_app +@for_app('tmux') def match(command, settings): - return ('tmux' in command.script - and 'ambiguous command:' in command.stderr + return ('ambiguous command:' in command.stderr and 'could be:' in command.stderr) diff --git a/thefuck/rules/tsuru_login.py b/thefuck/rules/tsuru_login.py index b71803fd..fc917159 100644 --- a/thefuck/rules/tsuru_login.py +++ b/thefuck/rules/tsuru_login.py @@ -1,9 +1,10 @@ from thefuck import shells +from thefuck.utils import for_app +@for_app('tsuru') def match(command, settings): - return (command.script.startswith('tsuru') - and 'not authenticated' in command.stderr + return ('not authenticated' in command.stderr and 'session has expired' in command.stderr) diff --git a/thefuck/rules/tsuru_not_command.py b/thefuck/rules/tsuru_not_command.py index 86b4d15f..498a4930 100644 --- a/thefuck/rules/tsuru_not_command.py +++ b/thefuck/rules/tsuru_not_command.py @@ -1,10 +1,10 @@ import re -from thefuck.utils import get_all_matched_commands, replace_command +from thefuck.utils import get_all_matched_commands, replace_command, for_app +@for_app('tsuru') def match(command, settings): - return (command.script.startswith('tsuru ') - and ' is not a tsuru command. See "tsuru help".' in command.stderr + return (' is not a tsuru command. See "tsuru help".' in command.stderr and '\nDid you mean?\n\t' in command.stderr) diff --git a/thefuck/rules/vagrant_up.py b/thefuck/rules/vagrant_up.py index 9c0a1e40..7830f301 100644 --- a/thefuck/rules/vagrant_up.py +++ b/thefuck/rules/vagrant_up.py @@ -1,8 +1,10 @@ from thefuck import shells +from thefuck.utils import for_app +@for_app('vagrant') def match(command, settings): - return command.script.startswith('vagrant ') and 'run `vagrant up`' in command.stderr.lower() + return 'run `vagrant up`' in command.stderr.lower() def get_new_command(command, settings): diff --git a/thefuck/specific/git.py b/thefuck/specific/git.py index b6cd22ff..4c415987 100644 --- a/thefuck/specific/git.py +++ b/thefuck/specific/git.py @@ -2,21 +2,18 @@ from functools import wraps import re from shlex import split from ..types import Command -from ..utils import quote +from ..utils import quote, for_app def git_support(fn): """Resolves git aliases and supports testing for both git and hub.""" + # supports GitHub's `hub` command + # which is recommended to be used with `alias git=hub` + # but at this point, shell aliases have already been resolved + + @for_app('git', 'hub') @wraps(fn) def wrapper(command, settings): - # supports GitHub's `hub` command - # which is recommended to be used with `alias git=hub` - # but at this point, shell aliases have already been resolved - is_git_cmd = command.script.startswith(('git', 'hub')) - - if not is_git_cmd: - return False - # perform git aliases expansion if 'trace: alias expansion:' in command.stderr: search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",