mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	| @@ -6,4 +6,4 @@ python: | |||||||
| install: | install: | ||||||
|   - python setup.py develop |   - python setup.py develop | ||||||
|   - pip install -r requirements.txt |   - 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' | 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`: | Alternatively, you can redirect the output of `thefuck-alias`: | ||||||
|  |  | ||||||
| ```bash | ```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 | 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: | 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`; | * `cd_correction` – spellchecks and correct failed cd commands; | ||||||
| * `cpp11` – add missing `-std=c++11` to `g++` or `clang++`; |  | ||||||
| * `cd_parent` – changes `cd..` to `cd ..`; |  | ||||||
| * `cd_mkdir` – creates directories before cd'ing into them; | * `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; | * `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"; | * `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; | * `fix_alt_space` – replaces Alt+Space with Space character; | ||||||
| * `git_add` – fix *"Did you forget to 'git add'?"*; | * `git_add` – fix *"Did you forget to 'git add'?"*; | ||||||
| * `git_checkout` – creates the branch before checking-out; | * `git_checkout` – creates the branch before checking-out; | ||||||
| * `git_no_command` – fixes wrong git commands like `git brnch`; | * `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_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; | * `has_exists_script` – prepends `./` when script/binary exists; | ||||||
| * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; | * `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; | * `mkdir_p` – adds `-p` when you trying to create directory without parent; | ||||||
| * `no_command` – fixes wrong console commands, for example `vom/vim`; | * `no_command` – fixes wrong console commands, for example `vom/vim`; | ||||||
| * `man_no_space` – fixes man commands without spaces, for example `mandiff`; | * `no_such_file` – creates missing directories with `mv` and `cp` commands; | ||||||
| * `pacman` – installs app with `pacman` or `yaourt` if it is not installed; | * `open` – prepends `http` to address passed to `open`; | ||||||
| * `pip_unknown_command` – fixes wrong pip commands, for example `pip instatl/pip install`; | * `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; | * `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; | * `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; | * `ssh_known_hosts` – removes host from `known_hosts` on warning; | ||||||
| * `sudo` – prepends `sudo` to previous command if it failed because of permissions; | * `sudo` – prepends `sudo` to previous command if it failed because of permissions; | ||||||
| * `switch_layout` – switches command from your local layout to en; | * `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; | * `apt_get` – installs app from apt if it not installed; | ||||||
| * `brew_install` – fixes formula name for `brew install`; | * `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: | Bundled, but not enabled by default: | ||||||
|  |  | ||||||
| * `ls_lah` – adds -lah to ls; |  | ||||||
| * `rm_root` – adds `--no-preserve-root` to `rm -rf /` command. | * `rm_root` – adds `--no-preserve-root` to `rm -rf /` command. | ||||||
|  |  | ||||||
| ## Creating your own rules | ## Creating your own rules | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||||
|  |  | ||||||
|  |  | ||||||
| VERSION = '1.39' | VERSION = '1.43' | ||||||
|  |  | ||||||
|  |  | ||||||
| setup(name='thefuck', | 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(): | 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']): |                return_value=['vim', 'apt-get']): | ||||||
|         assert match(Mock(stderr='vom: not found', script='vom file.py'), None) |         assert match(Mock(stderr='vom: not found', script='vom file.py'), None) | ||||||
|         assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None) |         assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None) | ||||||
| @@ -11,7 +11,7 @@ def test_match(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(): | 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']): |                return_value=['vim', 'apt-get']): | ||||||
|         assert get_new_command( |         assert get_new_command( | ||||||
|             Mock(stderr='vom: not found', |             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): | class TestGeneric(object): | ||||||
|     def test_from_shell(self): |     @pytest.fixture | ||||||
|         assert shells.Generic().from_shell('pwd') == 'pwd' |     def shell(self): | ||||||
|  |         return shells.Generic() | ||||||
|  |  | ||||||
|     def test_to_shell(self): |     def test_from_shell(self, shell): | ||||||
|         assert shells.Generic().to_shell('pwd') == 'pwd' |         assert shell.from_shell('pwd') == 'pwd' | ||||||
|  |  | ||||||
|     def test_put_to_history(self, builtins_open): |     def test_to_shell(self, shell): | ||||||
|         assert shells.Generic().put_to_history('ls') is None |         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 |         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') | @pytest.mark.usefixtures('isfile') | ||||||
| class TestBash(object): | class TestBash(object): | ||||||
|  |     @pytest.fixture | ||||||
|  |     def shell(self): | ||||||
|  |         return shells.Bash() | ||||||
|  |  | ||||||
|     @pytest.fixture(autouse=True) |     @pytest.fixture(autouse=True) | ||||||
|     def Popen(self, mocker): |     def Popen(self, mocker): | ||||||
|         mock = mocker.patch('thefuck.shells.Popen') |         mock = mocker.patch('thefuck.shells.Popen') | ||||||
| @@ -38,20 +52,69 @@ class TestBash(object): | |||||||
|     @pytest.mark.parametrize('before, after', [ |     @pytest.mark.parametrize('before, after', [ | ||||||
|         ('pwd', 'pwd'), |         ('pwd', 'pwd'), | ||||||
|         ('ll', 'ls -alF')]) |         ('ll', 'ls -alF')]) | ||||||
|     def test_from_shell(self, before, after): |     def test_from_shell(self, before, after, shell): | ||||||
|         assert shells.Bash().from_shell(before) == after |         assert shell.from_shell(before) == after | ||||||
|  |  | ||||||
|     def test_to_shell(self): |     def test_to_shell(self, shell): | ||||||
|         assert shells.Bash().to_shell('pwd') == 'pwd' |         assert shell.to_shell('pwd') == 'pwd' | ||||||
|  |  | ||||||
|     def test_put_to_history(self, builtins_open): |     def test_put_to_history(self, builtins_open, shell): | ||||||
|         shells.Bash().put_to_history('ls') |         shell.put_to_history('ls') | ||||||
|         builtins_open.return_value.__enter__.return_value. \ |         builtins_open.return_value.__enter__.return_value. \ | ||||||
|             write.assert_called_once_with('ls\n') |             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') | @pytest.mark.usefixtures('isfile') | ||||||
| class TestZsh(object): | class TestZsh(object): | ||||||
|  |     @pytest.fixture | ||||||
|  |     def shell(self): | ||||||
|  |         return shells.Zsh() | ||||||
|  |  | ||||||
|     @pytest.fixture(autouse=True) |     @pytest.fixture(autouse=True) | ||||||
|     def Popen(self, mocker): |     def Popen(self, mocker): | ||||||
|         mock = mocker.patch('thefuck.shells.Popen') |         mock = mocker.patch('thefuck.shells.Popen') | ||||||
| @@ -64,15 +127,23 @@ class TestZsh(object): | |||||||
|     @pytest.mark.parametrize('before, after', [ |     @pytest.mark.parametrize('before, after', [ | ||||||
|         ('pwd', 'pwd'), |         ('pwd', 'pwd'), | ||||||
|         ('ll', 'ls -alF')]) |         ('ll', 'ls -alF')]) | ||||||
|     def test_from_shell(self, before, after): |     def test_from_shell(self, before, after, shell): | ||||||
|         assert shells.Zsh().from_shell(before) == after |         assert shell.from_shell(before) == after | ||||||
|  |  | ||||||
|     def test_to_shell(self): |     def test_to_shell(self, shell): | ||||||
|         assert shells.Zsh().to_shell('pwd') == 'pwd' |         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', |         mocker.patch('thefuck.shells.time', | ||||||
|                      return_value=1430707243.3517463) |                      return_value=1430707243.3517463) | ||||||
|         shells.Zsh().put_to_history('ls') |         shell.put_to_history('ls') | ||||||
|         builtins_open.return_value.__enter__.return_value. \ |         builtins_open.return_value.__enter__.return_value. \ | ||||||
|             write.assert_called_once_with(': 1430707243:0;ls\n') |             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 | import pytest | ||||||
| from mock import Mock | 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 thefuck.types import Settings | ||||||
| from tests.utils import Command | 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__='') |     fn = Mock(return_value=return_value, __name__='') | ||||||
|     assert sudo_support(fn)(Command(command), None) == result |     assert sudo_support(fn)(Command(command), None) == result | ||||||
|     fn.assert_called_once_with(Command(called), None) |     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: | try: | ||||||
|     import CommandNotFound |     import CommandNotFound | ||||||
| except ImportError: | except ImportError: | ||||||
| @@ -20,4 +22,5 @@ def get_new_command(command, settings): | |||||||
|     c = CommandNotFound.CommandNotFound() |     c = CommandNotFound.CommandNotFound() | ||||||
|     pkgs = c.getPackages(command.script.split(" ")[0]) |     pkgs = c.getPackages(command.script.split(" ")[0]) | ||||||
|     name, _ = pkgs[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 | import re | ||||||
| from subprocess import check_output | from subprocess import check_output | ||||||
|  |  | ||||||
| import thefuck.logs |  | ||||||
|  |  | ||||||
| # Formulars are base on each local system's status | # Formulars are base on each local system's status | ||||||
| brew_formulas = [] | brew_formulas = [] | ||||||
| try: | 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' |     brew_formula_path = brew_path_prefix + '/Library/Formula' | ||||||
|  |  | ||||||
|     for file_name in os.listdir(brew_formula_path): |     for file_name in os.listdir(brew_formula_path): | ||||||
|   | |||||||
| @@ -12,7 +12,8 @@ TAP_CMD_PATH = '/%s/%s/cmd' | |||||||
| def _get_brew_path_prefix(): | def _get_brew_path_prefix(): | ||||||
|     """To get brew path""" |     """To get brew path""" | ||||||
|     try: |     try: | ||||||
|         return subprocess.check_output(['brew', '--prefix']).strip() |         return subprocess.check_output(['brew', '--prefix'], | ||||||
|  |                                        universal_newlines=True).strip() | ||||||
|     except: |     except: | ||||||
|         return None |         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 | import re | ||||||
|  | from thefuck import shells | ||||||
| from thefuck.utils import sudo_support | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -11,4 +12,5 @@ def match(command, settings): | |||||||
|  |  | ||||||
| @sudo_support | @sudo_support | ||||||
| def get_new_command(command, settings): | 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 | import re | ||||||
|  | from thefuck import shells | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
| @@ -12,4 +13,5 @@ def get_new_command(command, settings): | |||||||
|             r"error: pathspec '([^']*)' " |             r"error: pathspec '([^']*)' " | ||||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] |             "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 | import re | ||||||
|  | from thefuck import shells | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
| @@ -12,4 +13,5 @@ def get_new_command(command, settings): | |||||||
|             r"error: pathspec '([^']*)' " |             r"error: pathspec '([^']*)' " | ||||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] |             "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): | def match(command, settings): | ||||||
|     return 'ls' in command.script and not ('ls -' in command.script) |     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 | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from thefuck.utils import sudo_support | from thefuck.utils import sudo_support | ||||||
|  | from thefuck.shells import get_aliases | ||||||
|  |  | ||||||
|  |  | ||||||
| def _safe(fn, fallback): | def _safe(fn, fallback): | ||||||
| @@ -11,25 +12,25 @@ def _safe(fn, fallback): | |||||||
|         return fallback |         return fallback | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_all_bins(): | def _get_all_callables(): | ||||||
|     return [exe.name |     return [exe.name | ||||||
|             for path in os.environ.get('PATH', '').split(':') |             for path in os.environ.get('PATH', '').split(':') | ||||||
|             for exe in _safe(lambda: list(Path(path).iterdir()), []) |             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 | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return 'not found' in command.stderr and \ |     return 'not found' in command.stderr and \ | ||||||
|            bool(get_close_matches(command.script.split(' ')[0], |            bool(get_close_matches(command.script.split(' ')[0], | ||||||
|                                   _get_all_bins())) |                                   _get_all_callables())) | ||||||
|  |  | ||||||
|  |  | ||||||
| @sudo_support | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     old_command = command.script.split(' ')[0] |     old_command = command.script.split(' ')[0] | ||||||
|     new_command = get_close_matches(old_command, |     new_command = get_close_matches(old_command, | ||||||
|                                     _get_all_bins())[0] |                                     _get_all_callables())[0] | ||||||
|     return ' '.join([new_command] + command.script.split(' ')[1:]) |     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 | import subprocess | ||||||
| from thefuck.utils import DEVNULL | from thefuck.utils import DEVNULL, which | ||||||
|  | from thefuck import shells | ||||||
|  |  | ||||||
| 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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def __get_pkgfile(command): | def __get_pkgfile(command): | ||||||
|     try: |     try: | ||||||
|         return subprocess.check_output( |         return subprocess.check_output( | ||||||
|             ['pkgfile', '-b', '-v', command.script.split(" ")[0]], |             ['pkgfile', '-b', '-v', command.script.split(" ")[0]], | ||||||
|             universal_newlines=True, stderr=subprocess.DEVNULL |             universal_newlines=True, stderr=DEVNULL | ||||||
|         ).split() |         ).split() | ||||||
|     except subprocess.CalledProcessError: |     except subprocess.CalledProcessError: | ||||||
|         return None |         return None | ||||||
| @@ -30,14 +20,15 @@ def match(command, settings): | |||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     package = __get_pkgfile(command)[0] |     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 |     enabled_by_default = False | ||||||
| elif __command_available('yaourt'): | elif which('yaourt'): | ||||||
|     pacman = 'yaourt' |     pacman = 'yaourt' | ||||||
| elif __command_available('pacman'): | elif which('pacman'): | ||||||
|     pacman = 'sudo pacman' |     pacman = 'sudo pacman' | ||||||
| else: | else: | ||||||
|     enabled_by_default = False |     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 | """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. | methods. | ||||||
|  |  | ||||||
| """ | """ | ||||||
| @@ -8,15 +8,16 @@ from subprocess import Popen, PIPE | |||||||
| from time import time | from time import time | ||||||
| import os | import os | ||||||
| from psutil import Process | from psutil import Process | ||||||
| from .utils import DEVNULL | from .utils import DEVNULL, memoize | ||||||
|  |  | ||||||
|  |  | ||||||
| class Generic(object): | class Generic(object): | ||||||
|     def _get_aliases(self): |  | ||||||
|  |     def get_aliases(self): | ||||||
|         return {} |         return {} | ||||||
|  |  | ||||||
|     def _expand_aliases(self, command_script): |     def _expand_aliases(self, command_script): | ||||||
|         aliases = self._get_aliases() |         aliases = self.get_aliases() | ||||||
|         binary = command_script.split(' ')[0] |         binary = command_script.split(' ')[0] | ||||||
|         if binary in aliases: |         if binary in aliases: | ||||||
|             return command_script.replace(binary, aliases[binary], 1) |             return command_script.replace(binary, aliases[binary], 1) | ||||||
| @@ -47,16 +48,24 @@ class Generic(object): | |||||||
|             with open(history_file_name, 'a') as history: |             with open(history_file_name, 'a') as history: | ||||||
|                 history.write(self._get_history_line(command_script)) |                 history.write(self._get_history_line(command_script)) | ||||||
|  |  | ||||||
|  |     def and_(self, *commands): | ||||||
|  |         return ' && '.join(commands) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Bash(Generic): | class Bash(Generic): | ||||||
|  |     def app_alias(self): | ||||||
|  |         return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n" | ||||||
|  |  | ||||||
|     def _parse_alias(self, alias): |     def _parse_alias(self, alias): | ||||||
|         name, value = alias.replace('alias ', '', 1).split('=', 1) |         name, value = alias.replace('alias ', '', 1).split('=', 1) | ||||||
|         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": |         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": | ||||||
|             value = value[1:-1] |             value = value[1:-1] | ||||||
|         return name, value |         return name, value | ||||||
|  |  | ||||||
|     def _get_aliases(self): |     @memoize | ||||||
|         proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) |     def get_aliases(self): | ||||||
|  |         proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, | ||||||
|  |                      shell=True) | ||||||
|         return dict( |         return dict( | ||||||
|             self._parse_alias(alias) |             self._parse_alias(alias) | ||||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') |             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||||
| @@ -70,15 +79,52 @@ class Bash(Generic): | |||||||
|         return u'{}\n'.format(command_script) |         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): | 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): |     def _parse_alias(self, alias): | ||||||
|         name, value = alias.split('=', 1) |         name, value = alias.split('=', 1) | ||||||
|         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": |         if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": | ||||||
|             value = value[1:-1] |             value = value[1:-1] | ||||||
|         return name, value |         return name, value | ||||||
|  |  | ||||||
|     def _get_aliases(self): |     @memoize | ||||||
|         proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) |     def get_aliases(self): | ||||||
|  |         proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, | ||||||
|  |                      shell=True) | ||||||
|         return dict( |         return dict( | ||||||
|             self._parse_alias(alias) |             self._parse_alias(alias) | ||||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') |             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) |         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(), { | shells = defaultdict(lambda: Generic(), { | ||||||
|     'bash': Bash(), |     'bash': Bash(), | ||||||
|     'zsh': Zsh()}) |     'fish': Fish(), | ||||||
|  |     'zsh': Zsh(), | ||||||
|  |     'csh': Tcsh(), | ||||||
|  |     'tcsh': Tcsh()}) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_shell(): | 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] |     return shells[shell] | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -111,8 +188,16 @@ def to_shell(command): | |||||||
|  |  | ||||||
|  |  | ||||||
| def app_alias(): | def app_alias(): | ||||||
|     return _get_shell().app_alias() |     print(_get_shell().app_alias()) | ||||||
|  |  | ||||||
|  |  | ||||||
| def put_to_history(command): | def put_to_history(command): | ||||||
|     return _get_shell().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 | from functools import wraps | ||||||
| import os | import os | ||||||
|  | import pickle | ||||||
| import six | import six | ||||||
| from .types import Command | from .types import Command | ||||||
|  |  | ||||||
| @@ -62,3 +63,19 @@ def sudo_support(fn): | |||||||
|         else: |         else: | ||||||
|             return result |             return result | ||||||
|     return wrapper |     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