mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	| @@ -6,4 +6,4 @@ python: | ||||
| install: | ||||
|   - python setup.py develop | ||||
|   - pip install -r requirements.txt | ||||
| script: py.test | ||||
| script: py.test -v | ||||
|   | ||||
							
								
								
									
										34
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.md
									
									
									
									
									
								
							| @@ -118,6 +118,11 @@ Or in your `.zshrc`: | ||||
| alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R' | ||||
| ``` | ||||
|  | ||||
| If you are using `tcsh`: | ||||
| ```tcsh | ||||
| alias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`' | ||||
| ``` | ||||
|  | ||||
| Alternatively, you can redirect the output of `thefuck-alias`: | ||||
|  | ||||
| ```bash | ||||
| @@ -140,37 +145,50 @@ sudo pip install thefuck --upgrade | ||||
| The Fuck tries to match a rule for the previous command, creates a new command | ||||
| using the matched rule and runs it. Rules enabled by default are as follows: | ||||
|  | ||||
| * `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`; | ||||
| * `cpp11` – add missing `-std=c++11` to `g++` or `clang++`; | ||||
| * `cd_parent` – changes `cd..` to `cd ..`; | ||||
| * `cd_correction` – spellchecks and correct failed cd commands; | ||||
| * `cd_mkdir` – creates directories before cd'ing into them; | ||||
| * `cd_parent` – changes `cd..` to `cd ..`; | ||||
| * `composer_not_command` – fixes composer command name; | ||||
| * `cp_omitting_directory` – adds `-a` when you `cp` directory; | ||||
| * `cpp11` – add missing `-std=c++11` to `g++` or `clang++`; | ||||
| * `dry` – fix repetitions like "git git push"; | ||||
| * `django_south_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration; | ||||
| * `django_south_merge` – adds `--merge` to inconsistent django south migration; | ||||
| * `fix_alt_space` – replaces Alt+Space with Space character; | ||||
| * `git_add` – fix *"Did you forget to 'git add'?"*; | ||||
| * `git_checkout` – creates the branch before checking-out; | ||||
| * `git_no_command` – fixes wrong git commands like `git brnch`; | ||||
| * `git_pull` – sets upstream before executing previous `git pull`; | ||||
| * `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`; | ||||
| * `git_stash` – stashes you local modifications before rebasing or switching branch; | ||||
| * `grep_recursive` – adds `-r` when you trying to grep directory;  | ||||
| * `has_exists_script` – prepends `./` when script/binary exists; | ||||
| * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; | ||||
| * `ls_lah` – adds -lah to ls; | ||||
| * `man` – change manual section; | ||||
| * `man_no_space` – fixes man commands without spaces, for example `mandiff`; | ||||
| * `mkdir_p` – adds `-p` when you trying to create directory without parent; | ||||
| * `no_command` – fixes wrong console commands, for example `vom/vim`; | ||||
| * `man_no_space` – fixes man commands without spaces, for example `mandiff`; | ||||
| * `pacman` – installs app with `pacman` or `yaourt` if it is not installed; | ||||
| * `no_such_file` – creates missing directories with `mv` and `cp` commands; | ||||
| * `open` – prepends `http` to address passed to `open`; | ||||
| * `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`; | ||||
| * `python_command` – prepends `python` when you trying to run not executable/without `./` python script; | ||||
| * `sl_ls` – changes `sl` to `ls`; | ||||
| * `rm_dir` – adds `-rf` when you trying to remove directory; | ||||
| * `sl_ls` – changes `sl` to `ls`; | ||||
| * `ssh_known_hosts` – removes host from `known_hosts` on warning; | ||||
| * `sudo` – prepends `sudo` to previous command if it failed because of permissions; | ||||
| * `switch_layout` – switches command from your local layout to en; | ||||
| * `whois` – fixes `whois` command. | ||||
|  | ||||
| Enabled by default only on specific platforms: | ||||
|  | ||||
| * `apt_get` – installs app from apt if it not installed; | ||||
| * `brew_install` – fixes formula name for `brew install`; | ||||
| * `composer_not_command` – fixes composer command name. | ||||
| * `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`; | ||||
| * `pacman` – installs app with `pacman` or `yaourt` if it is not installed. | ||||
|  | ||||
| Bundled, but not enabled by default: | ||||
|  | ||||
| * `ls_lah` – adds -lah to ls; | ||||
| * `rm_root` – adds `--no-preserve-root` to `rm -rf /` command. | ||||
|  | ||||
| ## Creating your own rules | ||||
|   | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| from setuptools import setup, find_packages | ||||
|  | ||||
|  | ||||
| VERSION = '1.39' | ||||
| VERSION = '1.43' | ||||
|  | ||||
|  | ||||
| setup(name='thefuck', | ||||
|   | ||||
							
								
								
									
										0
									
								
								tests/rules/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/rules/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								tests/rules/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tests/rules/conftest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| @pytest.fixture(autouse=True) | ||||
| def generic_shell(monkeypatch): | ||||
|     monkeypatch.setattr('thefuck.shells.and_', lambda *x: ' && '.join(x)) | ||||
							
								
								
									
										59
									
								
								tests/rules/test_apt_get.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								tests/rules/test_apt_get.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import pytest | ||||
| from mock import Mock, patch | ||||
| from thefuck.rules import apt_get | ||||
| from thefuck.rules.apt_get import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| # python-commandnotfound is available in ubuntu 14.04+ | ||||
| @pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True), | ||||
|                     reason='Skip if python-commandnotfound is not available') | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='vim', stderr='vim: command not found')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, return_value', [ | ||||
|     (Command(script='vim', stderr='vim: command not found'), | ||||
|      [('vim', 'main'), ('vim-tiny', 'main')])]) | ||||
| @patch('thefuck.rules.apt_get.CommandNotFound', create=True) | ||||
| @patch.multiple(apt_get, create=True, apt_get='apt_get') | ||||
| def test_match_mocked(cmdnf_mock, command, return_value): | ||||
|     get_packages = Mock(return_value=return_value) | ||||
|     cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages) | ||||
|     assert match(command, None) | ||||
|     assert cmdnf_mock.CommandNotFound.called | ||||
|     assert get_packages.called | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='vim', stderr=''), Command()]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| # python-commandnotfound is available in ubuntu 14.04+ | ||||
| @pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True), | ||||
|                     reason='Skip if python-commandnotfound is not available') | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('vim'), 'sudo apt-get install vim && vim'), | ||||
|     (Command('convert'), 'sudo apt-get install imagemagick && convert')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command, return_value', [ | ||||
|     (Command('vim'), 'sudo apt-get install vim && vim', | ||||
|      [('vim', 'main'), ('vim-tiny', 'main')]), | ||||
|     (Command('convert'), 'sudo apt-get install imagemagick && convert', | ||||
|      [('imagemagick', 'main'), | ||||
|       ('graphicsmagick-imagemagick-compat', 'universe')])]) | ||||
| @patch('thefuck.rules.apt_get.CommandNotFound', create=True) | ||||
| @patch.multiple(apt_get, create=True, apt_get='apt_get') | ||||
| def test_get_new_command_mocked(cmdnf_mock, command, new_command, return_value): | ||||
|     get_packages = Mock(return_value=return_value) | ||||
|     cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages) | ||||
|     assert get_new_command(command, None) == new_command | ||||
|     assert cmdnf_mock.CommandNotFound.called | ||||
|     assert get_packages.called | ||||
							
								
								
									
										53
									
								
								tests/rules/test_django_south_ghost.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/rules/test_django_south_ghost.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import pytest | ||||
| from thefuck.rules.django_south_ghost import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def stderr(): | ||||
|     return '''Traceback (most recent call last): | ||||
|   File "/home/nvbn/work/.../bin/python", line 42, in <module> | ||||
|     exec(compile(__file__f.read(), __file__, "exec")) | ||||
|   File "/home/nvbn/work/.../app/manage.py", line 34, in <module> | ||||
|     execute_from_command_line(sys.argv) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 443, in execute_from_command_line | ||||
|     utility.execute() | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 382, in execute | ||||
|     self.fetch_command(subcommand).run_from_argv(self.argv) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/base.py", line 196, in run_from_argv | ||||
|     self.execute(*args, **options.__dict__) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/base.py", line 232, in execute | ||||
|     output = self.handle(*args, **options) | ||||
|   File "/home/nvbn/work/.../app/lib/south/management/commands/migrate.py", line 108, in handle | ||||
|     ignore_ghosts = ignore_ghosts, | ||||
|   File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 193, in migrate_app | ||||
|     applied_all = check_migration_histories(applied_all, delete_ghosts, ignore_ghosts) | ||||
|   File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 88, in check_migration_histories | ||||
|     raise exceptions.GhostMigrations(ghosts) | ||||
| south.exceptions.GhostMigrations:  | ||||
|  | ||||
|  ! These migrations are in the database but not on disk: | ||||
|     <app1: 0033_auto__...> | ||||
|     <app1: 0034_fill_...> | ||||
|     <app1: 0035_rename_...> | ||||
|     <app2: 0003_add_...> | ||||
|     <app2: 0004_denormalize_...> | ||||
|     <app1: 0033_auto....> | ||||
|     <app1: 0034_fill...> | ||||
|  ! I'm not trusting myself; either fix this yourself by fiddling | ||||
|  ! with the south_migrationhistory table, or pass --delete-ghost-migrations | ||||
|  ! to South to have it delete ALL of these records (this may not be good). | ||||
| ''' | ||||
|  | ||||
|  | ||||
| def test_match(stderr): | ||||
|     assert match(Command('./manage.py migrate', stderr=stderr), None) | ||||
|     assert match(Command('python manage.py migrate', stderr=stderr), None) | ||||
|     assert not match(Command('./manage.py migrate'), None) | ||||
|     assert not match(Command('app migrate', stderr=stderr), None) | ||||
|     assert not match(Command('./manage.py test', stderr=stderr), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('./manage.py migrate auth'), None)\ | ||||
|         == './manage.py migrate auth --delete-ghost-migrations' | ||||
							
								
								
									
										43
									
								
								tests/rules/test_django_south_merge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								tests/rules/test_django_south_merge.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import pytest | ||||
| from thefuck.rules.django_south_merge import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def stderr(): | ||||
|     return '''Running migrations for app: | ||||
|  ! Migration app:0003_auto... should not have been applied before app:0002_auto__add_field_query_due_date_ but was. | ||||
| Traceback (most recent call last): | ||||
|   File "/home/nvbn/work/.../bin/python", line 42, in <module> | ||||
|     exec(compile(__file__f.read(), __file__, "exec")) | ||||
|   File "/home/nvbn/work/.../app/manage.py", line 34, in <module> | ||||
|     execute_from_command_line(sys.argv) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 443, in execute_from_command_line | ||||
|     utility.execute() | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 382, in execute | ||||
|     self.fetch_command(subcommand).run_from_argv(self.argv) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/base.py", line 196, in run_from_argv | ||||
|     self.execute(*args, **options.__dict__) | ||||
|   File "/home/nvbn/work/.../lib/django/core/management/base.py", line 232, in execute | ||||
|     output = self.handle(*args, **options) | ||||
|   File "/home/nvbn/work/.../app/lib/south/management/commands/migrate.py", line 108, in handle | ||||
|     ignore_ghosts = ignore_ghosts, | ||||
|   File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 207, in migrate_app | ||||
|     raise exceptions.InconsistentMigrationHistory(problems) | ||||
| south.exceptions.InconsistentMigrationHistory: Inconsistent migration history | ||||
| The following options are available: | ||||
|     --merge: will just attempt the migration ignoring any potential dependency conflicts. | ||||
| ''' | ||||
|  | ||||
|  | ||||
| def test_match(stderr): | ||||
|     assert match(Command('./manage.py migrate', stderr=stderr), None) | ||||
|     assert match(Command('python manage.py migrate', stderr=stderr), None) | ||||
|     assert not match(Command('./manage.py migrate'), None) | ||||
|     assert not match(Command('app migrate', stderr=stderr), None) | ||||
|     assert not match(Command('./manage.py test', stderr=stderr), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('./manage.py migrate auth'), None) \ | ||||
|            == './manage.py migrate auth --merge' | ||||
							
								
								
									
										39
									
								
								tests/rules/test_git_add.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tests/rules/test_git_add.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import pytest | ||||
| from thefuck.rules.git_add import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def did_not_match(target, did_you_forget=True): | ||||
|     error = ("error: pathspec '{}' did not match any " | ||||
|              "file(s) known to git.".format(target)) | ||||
|     if did_you_forget: | ||||
|         error = ("{}\nDid you forget to 'git add'?'".format(error)) | ||||
|     return error | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git submodule update unknown', | ||||
|             stderr=did_not_match('unknown')), | ||||
|     Command(script='git commit unknown', | ||||
|             stderr=did_not_match('unknown'))])  # Older versions of Git | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git submodule update known', stderr=('')), | ||||
|     Command(script='git commit known', stderr=('')), | ||||
|     Command(script='git commit unknown',  # Newer versions of Git | ||||
|             stderr=did_not_match('unknown', False))]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('git submodule update unknown', stderr=did_not_match('unknown')), | ||||
|      'git add -- unknown && git submodule update unknown'), | ||||
|     (Command('git commit unknown', stderr=did_not_match('unknown')),  # Old Git | ||||
|      'git add -- unknown && git commit unknown')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										37
									
								
								tests/rules/test_git_checkout.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								tests/rules/test_git_checkout.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| import pytest | ||||
| from thefuck.rules.git_checkout import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def did_not_match(target, did_you_forget=False): | ||||
|     error = ("error: pathspec '{}' did not match any " | ||||
|              "file(s) known to git.".format(target)) | ||||
|     if did_you_forget: | ||||
|         error = ("{}\nDid you forget to 'git add'?'".format(error)) | ||||
|     return error | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git checkout unknown', stderr=did_not_match('unknown')), | ||||
|     Command(script='git commit unknown', stderr=did_not_match('unknown'))]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git submodule update unknown', | ||||
|             stderr=did_not_match('unknown', True)), | ||||
|     Command(script='git checkout known', stderr=('')), | ||||
|     Command(script='git commit known', stderr=(''))]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command(script='git checkout unknown', stderr=did_not_match('unknown')), | ||||
|      'git branch unknown && git checkout unknown'), | ||||
|     (Command('git commit unknown', stderr=did_not_match('unknown')), | ||||
|      'git branch unknown && git commit unknown')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										29
									
								
								tests/rules/test_git_pull.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								tests/rules/test_git_pull.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import pytest | ||||
| from thefuck.rules.git_pull import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def stderr(): | ||||
|     return '''There is no tracking information for the current branch. | ||||
| Please specify which branch you want to merge with. | ||||
| See git-pull(1) for details | ||||
|  | ||||
|     git pull <remote> <branch> | ||||
|  | ||||
| If you wish to set tracking information for this branch you can do so with: | ||||
|  | ||||
|     git branch --set-upstream-to=<remote>/<branch> master | ||||
|  | ||||
| ''' | ||||
|  | ||||
|  | ||||
| def test_match(stderr): | ||||
|     assert match(Command('git pull', stderr=stderr), None) | ||||
|     assert not match(Command('git pull'), None) | ||||
|     assert not match(Command('ls', stderr=stderr), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(stderr): | ||||
|     assert get_new_command(Command('git pull', stderr=stderr), None) \ | ||||
|            == "git branch --set-upstream-to=origin/master master && git pull" | ||||
							
								
								
									
										39
									
								
								tests/rules/test_git_stash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tests/rules/test_git_stash.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import pytest | ||||
| from thefuck.rules.git_stash import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def cherry_pick_error(): | ||||
|     return ('error: Your local changes would be overwritten by cherry-pick.\n' | ||||
|             'hint: Commit your changes or stash them to proceed.\n' | ||||
|             'fatal: cherry-pick failed') | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def rebase_error(): | ||||
|     return ('Cannot rebase: Your index contains uncommitted changes.\n' | ||||
|             'Please commit or stash them.') | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error()), | ||||
|     Command(script='git rebase -i HEAD~7', stderr=rebase_error())]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='git cherry-pick a1b2c3d', stderr=('')), | ||||
|     Command(script='git rebase -i HEAD~7', stderr=(''))]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error), | ||||
|      'git stash && git cherry-pick a1b2c3d'), | ||||
|     (Command('git rebase -i HEAD~7', stderr=rebase_error), | ||||
|      'git stash && git rebase -i HEAD~7')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										12
									
								
								tests/rules/test_grep_recursive.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/rules/test_grep_recursive.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from thefuck.rules.grep_recursive import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('grep blah .', stderr='grep: .: Is a directory'), None) | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command( | ||||
|         Command('grep blah .'), None) == 'grep -r blah .' | ||||
							
								
								
									
										34
									
								
								tests/rules/test_man.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								tests/rules/test_man.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import pytest | ||||
| from thefuck.rules.man import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command('man read'), | ||||
|     Command('man 2 read'), | ||||
|     Command('man 3 read'), | ||||
|     Command('man -s2 read'), | ||||
|     Command('man -s3 read'), | ||||
|     Command('man -s 2 read'), | ||||
|     Command('man -s 3 read')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command('man'), | ||||
|     Command('man ')]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('man read'), 'man 3 read'), | ||||
|     (Command('man 2 read'), 'man 3 read'), | ||||
|     (Command('man 3 read'), 'man 2 read'), | ||||
|     (Command('man -s2 read'), 'man -s3 read'), | ||||
|     (Command('man -s3 read'), 'man -s2 read'), | ||||
|     (Command('man -s 2 read'), 'man -s 3 read'), | ||||
|     (Command('man -s 3 read'), 'man -s 2 read')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
| @@ -3,7 +3,7 @@ from thefuck.rules.no_command import match, get_new_command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     with patch('thefuck.rules.no_command._get_all_bins', | ||||
|     with patch('thefuck.rules.no_command._get_all_callables', | ||||
|                return_value=['vim', 'apt-get']): | ||||
|         assert match(Mock(stderr='vom: not found', script='vom file.py'), None) | ||||
|         assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None) | ||||
| @@ -11,7 +11,7 @@ def test_match(): | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     with patch('thefuck.rules.no_command._get_all_bins', | ||||
|     with patch('thefuck.rules.no_command._get_all_callables', | ||||
|                return_value=['vim', 'apt-get']): | ||||
|         assert get_new_command( | ||||
|             Mock(stderr='vom: not found', | ||||
|   | ||||
							
								
								
									
										19
									
								
								tests/rules/test_no_such_file.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/rules/test_no_such_file.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import pytest | ||||
| from thefuck.rules.no_such_file import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"), | ||||
|     Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"), | ||||
|     ]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"), 'mkdir -p bar && mv foo bar/foo'), | ||||
|     (Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"), 'mkdir -p bar && mv foo bar/'), | ||||
|     ]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										25
									
								
								tests/rules/test_open.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/rules/test_open.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import pytest | ||||
| from thefuck.rules.open import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
| 	Command(script='open foo.com'),  | ||||
| 	Command(script='open foo.ly'),  | ||||
| 	Command(script='open foo.org'),  | ||||
| 	Command(script='open foo.net'),  | ||||
| 	Command(script='open foo.se'),  | ||||
| 	Command(script='open foo.io')]) | ||||
| def test_match(command): | ||||
| 	assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
| 	(Command('open foo.com'), 'open http://foo.com'), | ||||
| 	(Command('open foo.ly'), 'open http://foo.ly'), | ||||
| 	(Command('open foo.org'), 'open http://foo.org'), | ||||
| 	(Command('open foo.net'), 'open http://foo.net'), | ||||
| 	(Command('open foo.se'), 'open http://foo.se'), | ||||
| 	(Command('open foo.io'), 'open http://foo.io')]) | ||||
| def test_get_new_command(command, new_command): | ||||
| 	assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										66
									
								
								tests/rules/test_pacman.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								tests/rules/test_pacman.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| import pytest | ||||
| from mock import patch | ||||
| from thefuck.rules import pacman | ||||
| from thefuck.rules.pacman import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| pacman_cmd = getattr(pacman, 'pacman', 'pacman') | ||||
|  | ||||
| PKGFILE_OUTPUT_CONVERT = ''' | ||||
| extra/imagemagick 6.9.1.0-1\t/usr/bin/convert | ||||
| ''' | ||||
|  | ||||
| PKGFILE_OUTPUT_VIM = ''' | ||||
| extra/gvim 7.4.712-1        \t/usr/bin/vim | ||||
| extra/gvim-python3 7.4.712-1\t/usr/bin/vim | ||||
| extra/vim 7.4.712-1         \t/usr/bin/vim | ||||
| extra/vim-minimal 7.4.712-1 \t/usr/bin/vim | ||||
| extra/vim-python3 7.4.712-1 \t/usr/bin/vim | ||||
| ''' | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True), | ||||
|                     reason='Skip if pacman is not available') | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='vim', stderr='vim: command not found')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, return_value', [ | ||||
|     (Command(script='vim', stderr='vim: command not found'), PKGFILE_OUTPUT_VIM)]) | ||||
| @patch('thefuck.rules.pacman.subprocess') | ||||
| @patch.multiple(pacman, create=True, pacman=pacman_cmd) | ||||
| def test_match_mocked(subp_mock, command, return_value): | ||||
|     subp_mock.check_output.return_value = return_value | ||||
|     assert match(command, None) | ||||
|     assert subp_mock.check_output.called | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='vim', stderr=''), Command()]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True), | ||||
|                     reason='Skip if pacman is not available') | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd)), | ||||
|     (Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd))]) | ||||
| def test_get_new_command(command, new_command, mocker): | ||||
|     assert get_new_command(command, None) == new_command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command, return_value', [ | ||||
|     (Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd), | ||||
|         PKGFILE_OUTPUT_VIM), | ||||
|     (Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd), | ||||
|         PKGFILE_OUTPUT_CONVERT)]) | ||||
| @patch('thefuck.rules.pacman.subprocess') | ||||
| @patch.multiple(pacman, create=True, pacman=pacman_cmd) | ||||
| def test_get_new_command_mocked(subp_mock, command, new_command, return_value): | ||||
|     subp_mock.check_output.return_value = return_value | ||||
|     assert get_new_command(command, None) == new_command | ||||
|     assert subp_mock.check_output.called | ||||
							
								
								
									
										23
									
								
								tests/rules/test_whois.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/rules/test_whois.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| import pytest | ||||
| from thefuck.rules.whois import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='whois https://en.wikipedia.org/wiki/Main_Page'), | ||||
|     Command(script='whois https://en.wikipedia.org/'), | ||||
|     Command(script='whois en.wikipedia.org')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| def test_not_match(): | ||||
|     assert not match(Command(script='whois'), None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'), | ||||
|     (Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'), | ||||
|     (Command('whois en.wikipedia.org'), 'whois wikipedia.org')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
| @@ -13,19 +13,33 @@ def isfile(mocker): | ||||
|  | ||||
|  | ||||
| class TestGeneric(object): | ||||
|     def test_from_shell(self): | ||||
|         assert shells.Generic().from_shell('pwd') == 'pwd' | ||||
|     @pytest.fixture | ||||
|     def shell(self): | ||||
|         return shells.Generic() | ||||
|  | ||||
|     def test_to_shell(self): | ||||
|         assert shells.Generic().to_shell('pwd') == 'pwd' | ||||
|     def test_from_shell(self, shell): | ||||
|         assert shell.from_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open): | ||||
|         assert shells.Generic().put_to_history('ls') is None | ||||
|     def test_to_shell(self, shell): | ||||
|         assert shell.to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open, shell): | ||||
|         assert shell.put_to_history('ls') is None | ||||
|         assert builtins_open.call_count == 0 | ||||
|  | ||||
|     def test_and_(self, shell): | ||||
|         assert shell.and_('ls', 'cd') == 'ls && cd' | ||||
|  | ||||
|     def test_get_aliases(self, shell): | ||||
|         assert shell.get_aliases() == {} | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixtures('isfile') | ||||
| class TestBash(object): | ||||
|     @pytest.fixture | ||||
|     def shell(self): | ||||
|         return shells.Bash() | ||||
|  | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, mocker): | ||||
|         mock = mocker.patch('thefuck.shells.Popen') | ||||
| @@ -38,20 +52,69 @@ class TestBash(object): | ||||
|     @pytest.mark.parametrize('before, after', [ | ||||
|         ('pwd', 'pwd'), | ||||
|         ('ll', 'ls -alF')]) | ||||
|     def test_from_shell(self, before, after): | ||||
|         assert shells.Bash().from_shell(before) == after | ||||
|     def test_from_shell(self, before, after, shell): | ||||
|         assert shell.from_shell(before) == after | ||||
|  | ||||
|     def test_to_shell(self): | ||||
|         assert shells.Bash().to_shell('pwd') == 'pwd' | ||||
|     def test_to_shell(self, shell): | ||||
|         assert shell.to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open): | ||||
|         shells.Bash().put_to_history('ls') | ||||
|     def test_put_to_history(self, builtins_open, shell): | ||||
|         shell.put_to_history('ls') | ||||
|         builtins_open.return_value.__enter__.return_value. \ | ||||
|             write.assert_called_once_with('ls\n') | ||||
|  | ||||
|     def test_and_(self, shell): | ||||
|         assert shell.and_('ls', 'cd') == 'ls && cd' | ||||
|  | ||||
|     def test_get_aliases(self, shell): | ||||
|         assert shell.get_aliases() == {'l': 'ls -CF', | ||||
|                                        'la': 'ls -A', | ||||
|                                        'll': 'ls -alF'} | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixtures('isfile') | ||||
| class TestFish(object): | ||||
|     @pytest.fixture | ||||
|     def shell(self): | ||||
|         return shells.Fish() | ||||
|  | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, mocker): | ||||
|         mock = mocker.patch('thefuck.shells.Popen') | ||||
|         mock.return_value.stdout.read.return_value = (b'funced\nfuncsave\ngrep') | ||||
|         return mock | ||||
|  | ||||
|     @pytest.mark.parametrize('before, after', [ | ||||
|         ('pwd', 'pwd'), | ||||
|         ('ll', 'll')])  # Fish has no aliases but functions | ||||
|     def test_from_shell(self, before, after, shell): | ||||
|         assert shell.from_shell(before) == after | ||||
|  | ||||
|     def test_to_shell(self, shell): | ||||
|         assert shell.to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open, mocker, shell): | ||||
|         mocker.patch('thefuck.shells.time', | ||||
|                      return_value=1430707243.3517463) | ||||
|         shell.put_to_history('ls') | ||||
|         builtins_open.return_value.__enter__.return_value. \ | ||||
|             write.assert_called_once_with('- cmd: ls\n   when: 1430707243\n') | ||||
|  | ||||
|     def test_and_(self, shell): | ||||
|         assert shell.and_('foo', 'bar') == 'foo; and bar' | ||||
|  | ||||
|     def test_get_aliases(self, shell): | ||||
|         assert shell.get_aliases() == {'funced': 'funced', | ||||
|                                        'funcsave': 'funcsave', | ||||
|                                        'grep': 'grep'} | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixtures('isfile') | ||||
| class TestZsh(object): | ||||
|     @pytest.fixture | ||||
|     def shell(self): | ||||
|         return shells.Zsh() | ||||
|  | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, mocker): | ||||
|         mock = mocker.patch('thefuck.shells.Popen') | ||||
| @@ -64,15 +127,23 @@ class TestZsh(object): | ||||
|     @pytest.mark.parametrize('before, after', [ | ||||
|         ('pwd', 'pwd'), | ||||
|         ('ll', 'ls -alF')]) | ||||
|     def test_from_shell(self, before, after): | ||||
|         assert shells.Zsh().from_shell(before) == after | ||||
|     def test_from_shell(self, before, after, shell): | ||||
|         assert shell.from_shell(before) == after | ||||
|  | ||||
|     def test_to_shell(self): | ||||
|         assert shells.Zsh().to_shell('pwd') == 'pwd' | ||||
|     def test_to_shell(self, shell): | ||||
|         assert shell.to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open, mocker): | ||||
|     def test_put_to_history(self, builtins_open, mocker, shell): | ||||
|         mocker.patch('thefuck.shells.time', | ||||
|                      return_value=1430707243.3517463) | ||||
|         shells.Zsh().put_to_history('ls') | ||||
|         shell.put_to_history('ls') | ||||
|         builtins_open.return_value.__enter__.return_value. \ | ||||
|             write.assert_called_once_with(': 1430707243:0;ls\n') | ||||
|  | ||||
|     def test_and_(self, shell): | ||||
|         assert shell.and_('ls', 'cd') == 'ls && cd' | ||||
|  | ||||
|     def test_get_aliases(self, shell): | ||||
|         assert shell.get_aliases() == {'l': 'ls -CF', | ||||
|                                        'la': 'ls -A', | ||||
|                                        'll': 'ls -alF'} | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import pytest | ||||
| from mock import Mock | ||||
| from thefuck.utils import sudo_support, wrap_settings | ||||
| from thefuck.utils import sudo_support, wrap_settings, memoize | ||||
| from thefuck.types import Settings | ||||
| from tests.utils import Command | ||||
|  | ||||
| @@ -24,3 +24,11 @@ def test_sudo_support(return_value, command, called, result): | ||||
|     fn = Mock(return_value=return_value, __name__='') | ||||
|     assert sudo_support(fn)(Command(command), None) == result | ||||
|     fn.assert_called_once_with(Command(called), None) | ||||
|  | ||||
|  | ||||
| def test_memoize(): | ||||
|     fn = Mock(__name__='fn') | ||||
|     memoized = memoize(fn) | ||||
|     memoized() | ||||
|     memoized() | ||||
|     fn.assert_called_once_with() | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| from thefuck import shells | ||||
|  | ||||
| try: | ||||
|     import CommandNotFound | ||||
| except ImportError: | ||||
| @@ -20,4 +22,5 @@ def get_new_command(command, settings): | ||||
|     c = CommandNotFound.CommandNotFound() | ||||
|     pkgs = c.getPackages(command.script.split(" ")[0]) | ||||
|     name, _ = pkgs[0] | ||||
|     return "sudo apt-get install {} && {}".format(name, command.script) | ||||
|     formatme = shells.and_('sudo apt-get install {}', '{}') | ||||
|     return formatme.format(name, command.script) | ||||
|   | ||||
| @@ -3,12 +3,11 @@ import os | ||||
| import re | ||||
| from subprocess import check_output | ||||
|  | ||||
| import thefuck.logs | ||||
|  | ||||
| # Formulars are base on each local system's status | ||||
| brew_formulas = [] | ||||
| try: | ||||
|     brew_path_prefix = check_output(['brew', '--prefix']).strip() | ||||
|     brew_path_prefix = check_output(['brew', '--prefix'], | ||||
|                                     universal_newlines=True).strip() | ||||
|     brew_formula_path = brew_path_prefix + '/Library/Formula' | ||||
|  | ||||
|     for file_name in os.listdir(brew_formula_path): | ||||
|   | ||||
| @@ -12,7 +12,8 @@ TAP_CMD_PATH = '/%s/%s/cmd' | ||||
| def _get_brew_path_prefix(): | ||||
|     """To get brew path""" | ||||
|     try: | ||||
|         return subprocess.check_output(['brew', '--prefix']).strip() | ||||
|         return subprocess.check_output(['brew', '--prefix'], | ||||
|                                        universal_newlines=True).strip() | ||||
|     except: | ||||
|         return None | ||||
|  | ||||
|   | ||||
							
								
								
									
										53
									
								
								thefuck/rules/cd_correction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								thefuck/rules/cd_correction.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| #!/usr/bin/env python | ||||
| __author__ = "mmussomele" | ||||
|  | ||||
| """Attempts to spellcheck and correct failed cd commands""" | ||||
|  | ||||
| import os | ||||
| from difflib import get_close_matches | ||||
| from thefuck.utils import sudo_support | ||||
| from thefuck.rules import cd_mkdir | ||||
|  | ||||
| MAX_ALLOWED_DIFF = 0.6 | ||||
|  | ||||
|  | ||||
| def _get_sub_dirs(parent): | ||||
|     """Returns a list of the child directories of the given parent directory""" | ||||
|     return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))] | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     """Match function copied from cd_mkdir.py""" | ||||
|     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())) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     """ | ||||
|     Attempt to rebuild the path string by spellchecking the directories. | ||||
|     If it fails (i.e. no directories are a close enough match), then it  | ||||
|     defaults to the rules of cd_mkdir.  | ||||
|     Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6 | ||||
|     """ | ||||
|     dest = command.script.split()[1].split(os.sep) | ||||
|     if dest[-1] == '': | ||||
|         dest = dest[:-1] | ||||
|     cwd = os.getcwd() | ||||
|     for directory in dest: | ||||
|         if directory == ".": | ||||
|             continue | ||||
|         elif directory == "..": | ||||
|             cwd = os.path.split(cwd)[0] | ||||
|             continue | ||||
|         best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF) | ||||
|         if best_matches: | ||||
|             cwd = os.path.join(cwd, best_matches[0]) | ||||
|         else: | ||||
|             return cd_mkdir.get_new_command(command, settings) | ||||
|     return "cd {0}".format(cwd) | ||||
|  | ||||
|  | ||||
| enabled_by_default = True | ||||
| @@ -1,4 +1,5 @@ | ||||
| import re | ||||
| from thefuck import shells | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @@ -11,4 +12,5 @@ def match(command, settings): | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub(r'^cd (.*)', 'mkdir -p \\1 && cd \\1', command.script) | ||||
|     repl = shells.and_('mkdir -p \\1', 'cd \\1') | ||||
|     return re.sub(r'^cd (.*)', repl, command.script) | ||||
|   | ||||
							
								
								
									
										8
									
								
								thefuck/rules/django_south_ghost.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								thefuck/rules/django_south_ghost.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| def match(command, settings): | ||||
|     return 'manage.py' in command.script and \ | ||||
|            'migrate' in command.script \ | ||||
|            and 'or pass --delete-ghost-migrations' in command.stderr | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return u'{} --delete-ghost-migrations'.format(command.script) | ||||
							
								
								
									
										8
									
								
								thefuck/rules/django_south_merge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								thefuck/rules/django_south_merge.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| def match(command, settings): | ||||
|     return 'manage.py' in command.script and \ | ||||
|            'migrate' in command.script \ | ||||
|            and '--merge: will just attempt the migration' in command.stderr | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return u'{} --merge'.format(command.script) | ||||
| @@ -1,4 +1,5 @@ | ||||
| import re | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
| @@ -12,4 +13,5 @@ def get_new_command(command, settings): | ||||
|             r"error: pathspec '([^']*)' " | ||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] | ||||
|  | ||||
|     return 'git add -- {} && {}'.format(missing_file, command.script) | ||||
|     formatme = shells.and_('git add -- {}', '{}') | ||||
|     return formatme.format(missing_file, command.script) | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import re | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
| @@ -12,4 +13,5 @@ def get_new_command(command, settings): | ||||
|             r"error: pathspec '([^']*)' " | ||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] | ||||
|  | ||||
|     return 'git branch {} && {}'.format(missing_file, command.script) | ||||
|     formatme = shells.and_('git branch {}', '{}') | ||||
|     return formatme.format(missing_file, command.script) | ||||
|   | ||||
							
								
								
									
										12
									
								
								thefuck/rules/git_pull.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								thefuck/rules/git_pull.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| def match(command, settings): | ||||
|     return ('git' in command.script | ||||
|             and 'pull' in command.script | ||||
|             and 'set-upstream' in command.stderr) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     line = command.stderr.split('\n')[-3].strip() | ||||
|     branch = line.split(' ')[-1] | ||||
|     set_upstream = line.replace('<remote>', 'origin')\ | ||||
|                        .replace('<branch>', branch) | ||||
|     return u'{} && {}'.format(set_upstream, command.script) | ||||
							
								
								
									
										12
									
								
								thefuck/rules/git_stash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								thefuck/rules/git_stash.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     # catches "Please commit or stash them" and "Please, commit your changes or | ||||
|     # stash them before you can switch branches." | ||||
|     return 'git' in command.script and 'or stash them' in command.stderr | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     formatme = shells.and_('git stash', '{}') | ||||
|     return formatme.format(command.script) | ||||
							
								
								
									
										7
									
								
								thefuck/rules/grep_recursive.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								thefuck/rules/grep_recursive.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| def match(command, settings): | ||||
|     return (command.script.startswith('grep') | ||||
|             and 'is a directory' in command.stderr.lower()) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return 'grep -r {}'.format(command.script[5:]) | ||||
| @@ -1,6 +1,3 @@ | ||||
| enabled_by_default = False | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return 'ls' in command.script and not ('ls -' in command.script) | ||||
|  | ||||
|   | ||||
							
								
								
									
										13
									
								
								thefuck/rules/man.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								thefuck/rules/man.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| def match(command, settings): | ||||
|     return command.script.strip().startswith('man ') | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     if '3' in command.script: | ||||
|         return command.script.replace("3", "2") | ||||
|     if '2' in command.script: | ||||
|         return command.script.replace("2", "3") | ||||
|  | ||||
|     split_cmd = command.script.split() | ||||
|     split_cmd.insert(1, ' 3 ') | ||||
|     return "".join(split_cmd) | ||||
| @@ -2,6 +2,7 @@ from difflib import get_close_matches | ||||
| import os | ||||
| from pathlib import Path | ||||
| from thefuck.utils import sudo_support | ||||
| from thefuck.shells import get_aliases | ||||
|  | ||||
|  | ||||
| def _safe(fn, fallback): | ||||
| @@ -11,25 +12,25 @@ def _safe(fn, fallback): | ||||
|         return fallback | ||||
|  | ||||
|  | ||||
| def _get_all_bins(): | ||||
| def _get_all_callables(): | ||||
|     return [exe.name | ||||
|             for path in os.environ.get('PATH', '').split(':') | ||||
|             for exe in _safe(lambda: list(Path(path).iterdir()), []) | ||||
|             if not _safe(exe.is_dir, True)] | ||||
|             if not _safe(exe.is_dir, True)] + get_aliases() | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return 'not found' in command.stderr and \ | ||||
|            bool(get_close_matches(command.script.split(' ')[0], | ||||
|                                   _get_all_bins())) | ||||
|                                   _get_all_callables())) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     old_command = command.script.split(' ')[0] | ||||
|     new_command = get_close_matches(old_command, | ||||
|                                     _get_all_bins())[0] | ||||
|                                     _get_all_callables())[0] | ||||
|     return ' '.join([new_command] + command.script.split(' ')[1:]) | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								thefuck/rules/no_such_file.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								thefuck/rules/no_such_file.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import re | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| patterns = ( | ||||
|     r"mv: cannot move '[^']*' to '([^']*)': No such file or directory", | ||||
|     r"mv: cannot move '[^']*' to '([^']*)': Not a directory", | ||||
|     r"cp: cannot create regular file '([^']*)': No such file or directory", | ||||
|     r"cp: cannot create regular file '([^']*)': Not a directory", | ||||
| ) | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     for pattern in patterns: | ||||
|         if re.search(pattern, command.stderr): | ||||
|             return True | ||||
|  | ||||
|     return False | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     for pattern in patterns: | ||||
|         file = re.findall(pattern, command.stderr) | ||||
|  | ||||
|         if file: | ||||
|             file = file[0] | ||||
|             dir = file[0:file.rfind('/')] | ||||
|  | ||||
|             formatme = shells.and_('mkdir -p {}', '{}') | ||||
|             return formatme.format(dir, command.script) | ||||
							
								
								
									
										24
									
								
								thefuck/rules/open.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								thefuck/rules/open.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| # Opens URL's in the default web browser | ||||
| #  | ||||
| # Example: | ||||
| # > open github.com | ||||
| # The file ~/github.com does not exist. | ||||
| # Perhaps you meant 'http://github.com'? | ||||
| # | ||||
| #  | ||||
|  | ||||
| def match(command, settings): | ||||
| 	return (command.script.startswith ('open') | ||||
| 			and ( | ||||
| 			# Wanted to use this: | ||||
| 			# 'http' in command.stderr | ||||
| 			'.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)) | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
| 	return 'open http://' + command.script[5:] | ||||
| @@ -1,23 +1,13 @@ | ||||
| import subprocess | ||||
| from thefuck.utils import DEVNULL | ||||
|  | ||||
|  | ||||
| def __command_available(command): | ||||
|     try: | ||||
|         subprocess.check_output([command], stderr=DEVNULL) | ||||
|         return True | ||||
|     except subprocess.CalledProcessError: | ||||
|         # command exists but is not happy to be called without any argument | ||||
|         return True | ||||
|     except OSError: | ||||
|         return False | ||||
| from thefuck.utils import DEVNULL, which | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| def __get_pkgfile(command): | ||||
|     try: | ||||
|         return subprocess.check_output( | ||||
|             ['pkgfile', '-b', '-v', command.script.split(" ")[0]], | ||||
|             universal_newlines=True, stderr=subprocess.DEVNULL | ||||
|             universal_newlines=True, stderr=DEVNULL | ||||
|         ).split() | ||||
|     except subprocess.CalledProcessError: | ||||
|         return None | ||||
| @@ -30,14 +20,15 @@ def match(command, settings): | ||||
| def get_new_command(command, settings): | ||||
|     package = __get_pkgfile(command)[0] | ||||
|  | ||||
|     return '{} -S {} && {}'.format(pacman, package, command.script) | ||||
|     formatme = shells.and_('{} -S {}', '{}') | ||||
|     return formatme.format(pacman, package, command.script) | ||||
|  | ||||
|  | ||||
| if not __command_available('pkgfile'): | ||||
| if not which('pkgfile'): | ||||
|     enabled_by_default = False | ||||
| elif __command_available('yaourt'): | ||||
| elif which('yaourt'): | ||||
|     pacman = 'yaourt' | ||||
| elif __command_available('pacman'): | ||||
| elif which('pacman'): | ||||
|     pacman = 'sudo pacman' | ||||
| else: | ||||
|     enabled_by_default = False | ||||
|   | ||||
							
								
								
									
										31
									
								
								thefuck/rules/whois.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								thefuck/rules/whois.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
| from six.moves.urllib.parse import urlparse | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     """ | ||||
|     What the `whois` command returns depends on the 'Whois server' it contacted | ||||
|     and is not consistent through different servers. But there can be only two | ||||
|     types of errors I can think of with `whois`: | ||||
|         - `whois https://en.wikipedia.org/` → `whois en.wikipedia.org`; | ||||
|         - `whois en.wikipedia.org` → `whois wikipedia.org`. | ||||
|     So we match any `whois` command and then: | ||||
|         - if there is a slash: keep only the FQDN; | ||||
|         - if there is no slash but there is a point: removes the left-most | ||||
|           subdomain. | ||||
|  | ||||
|     We cannot either remove all subdomains because we cannot know which part is | ||||
|     the subdomains and which is the domain, consider: | ||||
|         - www.google.fr → subdomain: www, domain: 'google.fr'; | ||||
|         - google.co.uk → subdomain: None, domain; 'google.co.uk'. | ||||
|     """ | ||||
|     return 'whois' in command.script and len(command.script.split()) > 1 | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     url = command.script.split()[1] | ||||
|  | ||||
|     if '/' in command.script: | ||||
|         return 'whois ' + urlparse(url).netloc | ||||
|     elif '.' in command.script: | ||||
|         return 'whois ' + '.'.join(urlparse(url).path.split('.')[1:]) | ||||
| @@ -1,5 +1,5 @@ | ||||
| """Module with shell specific actions, each shell class should | ||||
| implement `from_shell`, `to_shell`, `app_alias` and `put_to_history` | ||||
| implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and `get_aliases` | ||||
| methods. | ||||
|  | ||||
| """ | ||||
| @@ -8,15 +8,16 @@ from subprocess import Popen, PIPE | ||||
| from time import time | ||||
| import os | ||||
| from psutil import Process | ||||
| from .utils import DEVNULL | ||||
| from .utils import DEVNULL, memoize | ||||
|  | ||||
|  | ||||
| class Generic(object): | ||||
|     def _get_aliases(self): | ||||
|  | ||||
|     def get_aliases(self): | ||||
|         return {} | ||||
|  | ||||
|     def _expand_aliases(self, command_script): | ||||
|         aliases = self._get_aliases() | ||||
|         aliases = self.get_aliases() | ||||
|         binary = command_script.split(' ')[0] | ||||
|         if binary in aliases: | ||||
|             return command_script.replace(binary, aliases[binary], 1) | ||||
| @@ -47,16 +48,24 @@ class Generic(object): | ||||
|             with open(history_file_name, 'a') as history: | ||||
|                 history.write(self._get_history_line(command_script)) | ||||
|  | ||||
|     def and_(self, *commands): | ||||
|         return ' && '.join(commands) | ||||
|  | ||||
|  | ||||
| class Bash(Generic): | ||||
|     def app_alias(self): | ||||
|         return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n" | ||||
|  | ||||
|     def _parse_alias(self, alias): | ||||
|         name, value = alias.replace('alias ', '', 1).split('=', 1) | ||||
|         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": | ||||
|             value = value[1:-1] | ||||
|         return name, value | ||||
|  | ||||
|     def _get_aliases(self): | ||||
|         proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) | ||||
|     @memoize | ||||
|     def get_aliases(self): | ||||
|         proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, | ||||
|                      shell=True) | ||||
|         return dict( | ||||
|             self._parse_alias(alias) | ||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||
| @@ -70,15 +79,52 @@ class Bash(Generic): | ||||
|         return u'{}\n'.format(command_script) | ||||
|  | ||||
|  | ||||
| class Fish(Generic): | ||||
|     def app_alias(self): | ||||
|         return ("function fuck -d 'Correct your previous console command'\n" | ||||
|                 "    set -l exit_code $status\n" | ||||
|                 "    set -l eval_script" | ||||
|                 " (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n" | ||||
|                 "    set -l fucked_up_commandd $history[1]\n" | ||||
|                 "    thefuck $fucked_up_commandd > $eval_script\n" | ||||
|                 "    . $eval_script\n" | ||||
|                 "    rm $eval_script\n" | ||||
|                 "    if test $exit_code -ne 0\n" | ||||
|                 "        history --delete $fucked_up_commandd\n" | ||||
|                 "    end\n" | ||||
|                 "end") | ||||
|  | ||||
|     @memoize | ||||
|     def get_aliases(self): | ||||
|         proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL, | ||||
|                      shell=True) | ||||
|         functions = proc.stdout.read().decode('utf-8').strip().split('\n') | ||||
|         return {function: function for function in functions} | ||||
|  | ||||
|     def _get_history_file_name(self): | ||||
|         return os.path.expanduser('~/.config/fish/fish_history') | ||||
|  | ||||
|     def _get_history_line(self, command_script): | ||||
|         return u'- cmd: {}\n   when: {}\n'.format(command_script, int(time())) | ||||
|  | ||||
|     def and_(self, *commands): | ||||
|         return '; and '.join(commands) | ||||
|  | ||||
|  | ||||
| class Zsh(Generic): | ||||
|     def app_alias(self): | ||||
|         return "\nalias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'\n" | ||||
|  | ||||
|     def _parse_alias(self, alias): | ||||
|         name, value = alias.split('=', 1) | ||||
|         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": | ||||
|             value = value[1:-1] | ||||
|         return name, value | ||||
|  | ||||
|     def _get_aliases(self): | ||||
|         proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) | ||||
|     @memoize | ||||
|     def get_aliases(self): | ||||
|         proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, | ||||
|                      shell=True) | ||||
|         return dict( | ||||
|             self._parse_alias(alias) | ||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||
| @@ -92,13 +138,44 @@ class Zsh(Generic): | ||||
|         return u': {}:0;{}\n'.format(int(time()), command_script) | ||||
|  | ||||
|  | ||||
| class Tcsh(Generic): | ||||
|     def app_alias(self): | ||||
|         return "\nalias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'\n" | ||||
|  | ||||
|     def _parse_alias(self, alias): | ||||
|         name, value = alias.split("\t", 1) | ||||
|         return name, value | ||||
|  | ||||
|     @memoize | ||||
|     def get_aliases(self): | ||||
|         proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL, | ||||
|                      shell=True) | ||||
|         return dict( | ||||
|             self._parse_alias(alias) | ||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||
|             if alias and '\t' in alias) | ||||
|  | ||||
|     def _get_history_file_name(self): | ||||
|         return os.environ.get("HISTFILE", | ||||
|                               os.path.expanduser('~/.history')) | ||||
|  | ||||
|     def _get_history_line(self, command_script): | ||||
|         return u'#+{}\n{}\n'.format(int(time()), command_script) | ||||
|  | ||||
|  | ||||
| shells = defaultdict(lambda: Generic(), { | ||||
|     'bash': Bash(), | ||||
|     'zsh': Zsh()}) | ||||
|     'fish': Fish(), | ||||
|     'zsh': Zsh(), | ||||
|     'csh': Tcsh(), | ||||
|     'tcsh': Tcsh()}) | ||||
|  | ||||
|  | ||||
| def _get_shell(): | ||||
|     shell = Process(os.getpid()).parent().cmdline()[0] | ||||
|     try: | ||||
|         shell = Process(os.getpid()).parent().name() | ||||
|     except TypeError: | ||||
|         shell = Process(os.getpid()).parent.name | ||||
|     return shells[shell] | ||||
|  | ||||
|  | ||||
| @@ -111,8 +188,16 @@ def to_shell(command): | ||||
|  | ||||
|  | ||||
| def app_alias(): | ||||
|     return _get_shell().app_alias() | ||||
|     print(_get_shell().app_alias()) | ||||
|  | ||||
|  | ||||
| def put_to_history(command): | ||||
|     return _get_shell().put_to_history(command) | ||||
|  | ||||
|  | ||||
| def and_(*commands): | ||||
|     return _get_shell().and_(*commands) | ||||
|  | ||||
|  | ||||
| def get_aliases(): | ||||
|     return list(_get_shell().get_aliases().keys()) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| from functools import wraps | ||||
| import os | ||||
| import pickle | ||||
| import six | ||||
| from .types import Command | ||||
|  | ||||
| @@ -62,3 +63,19 @@ def sudo_support(fn): | ||||
|         else: | ||||
|             return result | ||||
|     return wrapper | ||||
|  | ||||
|  | ||||
| def memoize(fn): | ||||
|     """Caches previous calls to the function.""" | ||||
|     memo = {} | ||||
|  | ||||
|     @wraps(fn) | ||||
|     def wrapper(*args, **kwargs): | ||||
|         key = pickle.dumps((args, kwargs)) | ||||
|         if key not in memo: | ||||
|             memo[key] = fn(*args, **kwargs) | ||||
|  | ||||
|         return memo[key] | ||||
|  | ||||
|     return wrapper | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user