mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/nvbn/thefuck
This commit is contained in:
		
							
								
								
									
										119
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,5 +1,7 @@ | ||||
| # The Fuck [](https://travis-ci.org/nvbn/thefuck) | ||||
|  | ||||
| **Aliases changed in 1.34.** | ||||
|  | ||||
| Magnificent app which corrects your previous console command, | ||||
| inspired by a [@liamosaur](https://twitter.com/liamosaur/) | ||||
| [tweet](https://twitter.com/liamosaur/status/506975850596536320). | ||||
| @@ -71,7 +73,7 @@ REPL-y 0.3.1 | ||||
| ... | ||||
| ``` | ||||
|  | ||||
| If you are scared to blindly run changed command, there's `require_confirmation` | ||||
| If you are scared to blindly run the changed command, there is a `require_confirmation` | ||||
| [settings](#settings) option: | ||||
|  | ||||
| ```bash | ||||
| @@ -88,8 +90,8 @@ Reading package lists... Done | ||||
|  | ||||
| ## Requirements | ||||
|  | ||||
| - python (2.7+ or 3.3+) | ||||
| - pip | ||||
| - python | ||||
| - python-dev | ||||
|  | ||||
| ## Installation | ||||
| @@ -100,40 +102,30 @@ Install `The Fuck` with `pip`: | ||||
| sudo pip install thefuck | ||||
| ``` | ||||
|  | ||||
| If it fails try to use `easy_install`: | ||||
| [Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation) | ||||
|  | ||||
| And add to the `.bashrc` or `.bash_profile`(for OSX): | ||||
|  | ||||
| ```bash | ||||
| sudo easy_install thefuck | ||||
| ``` | ||||
|  | ||||
| And add to `.bashrc` or `.zshrc` or `.bash_profile`(for OSX): | ||||
|  | ||||
| ```bash | ||||
| alias fuck='$(thefuck $(fc -ln -1))' | ||||
| # You can use whatever you want as an alias, like for mondays: | ||||
| alias fuck='eval $(thefuck $(fc -ln -1)); history -r' | ||||
| # You can use whatever you want as an alias, like for Mondays: | ||||
| alias FUCK='fuck' | ||||
| ``` | ||||
|  | ||||
| Or in `config.fish`: | ||||
| Or in your `.zshrc`: | ||||
|  | ||||
| ```fish | ||||
| function fuck | ||||
|     eval (thefuck $history[1]) | ||||
| end | ||||
| ```bash | ||||
| alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R' | ||||
| ``` | ||||
|  | ||||
| Or in your Powershell `$PROFILE` on Windows: | ||||
| Alternatively, you can redirect the output of `thefuck-alias`: | ||||
|  | ||||
| ```powershell | ||||
| function fuck {  | ||||
|     $fuck = $(thefuck (get-history -count 1).commandline) | ||||
|     if($fuck.startswith("echo")) {  | ||||
|         $fuck.substring(5)  | ||||
|     }  | ||||
|     else { iex "$fuck" }  | ||||
| } | ||||
| ```bash | ||||
| thefuck-alias >> ~/.bashrc | ||||
| ``` | ||||
|  | ||||
| [Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases) | ||||
|  | ||||
| Changes will be available only in a new shell session. | ||||
|  | ||||
|  | ||||
| @@ -145,21 +137,41 @@ sudo pip install thefuck --upgrade | ||||
|  | ||||
| ## How it works | ||||
|  | ||||
| The Fuck tries to match rule for the previous command, create new command | ||||
| using matched rule and run it. Rules enabled by default: | ||||
| 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_mkdir` – creates directories before cd'ing into them; | ||||
| * `cp_omitting_directory` – adds `-a` when you `cp` directory; | ||||
| * `dry` – fix repetitions like "git git push"; | ||||
| * `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_push` – adds `--set-upstream origin $branch` to previous failed `git push`; | ||||
| * `has_exists_script` – prepends `./` when script/binary exists; | ||||
| * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; | ||||
| * `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; | ||||
| * `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; | ||||
| * `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. | ||||
| * `switch_layout` – switches command from your local layout to en; | ||||
| * `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. | ||||
|  | ||||
| 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 | ||||
|  | ||||
| @@ -167,10 +179,13 @@ For adding your own rule you should create `your-rule-name.py` | ||||
| in `~/.thefuck/rules`. Rule should contain two functions: | ||||
| `match(command: Command, settings: Settings) -> bool` | ||||
| and `get_new_command(command: Command, settings: Settings) -> str`. | ||||
| Also the rule can contain optional function | ||||
| `side_effect(command: Command, settings: Settings) -> None` and | ||||
| optional boolean `enabled_by_default` | ||||
|  | ||||
| `Command` has three attributes: `script`, `stdout` and `stderr`. | ||||
|  | ||||
| `Settings` is `~/.thefuck/settings.py`. | ||||
| `Settings` is a special object filled with `~/.thefuck/settings.py` and values from env, [more](#settings). | ||||
|  | ||||
| Simple example of the rule for running script with `sudo`: | ||||
|  | ||||
| @@ -182,6 +197,14 @@ def match(command, settings): | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return 'sudo {}'.format(command.script) | ||||
|  | ||||
| # Optional: | ||||
| enabled_by_default = True | ||||
|  | ||||
| def side_effect(command, settings): | ||||
|     subprocess.call('chmod 777 .', shell=True) | ||||
|  | ||||
| priority = 1000  # Lower first | ||||
| ``` | ||||
|  | ||||
| [More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules), | ||||
| @@ -189,12 +212,42 @@ def get_new_command(command, settings): | ||||
|  | ||||
| ## Settings | ||||
|  | ||||
| The Fuck has a few settings parameters, they can be changed in `~/.thefuck/settings.py`: | ||||
| The Fuck has a few settings parameters which can be changed in `~/.thefuck/settings.py`: | ||||
|  | ||||
| * `rules` – list of enabled rules, by default all; | ||||
| * `require_confirmation` – require confirmation before running new command, by default `False`;  | ||||
| * `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`; | ||||
| * `require_confirmation` – requires confirmation before running new command, by default `False`; | ||||
| * `wait_command` – max amount of time in seconds for getting previous command output; | ||||
| * `no_colors` – disable colored output. | ||||
| * `no_colors` – disable colored output; | ||||
| * `priority` – dict with rules priorities, rule with lower `priority` will be matched first. | ||||
|  | ||||
| Example of `settings.py`: | ||||
|  | ||||
| ```python | ||||
| rules = ['sudo', 'no_command'] | ||||
| require_confirmation = True | ||||
| wait_command = 10 | ||||
| no_colors = False | ||||
| priority = {'sudo': 100, 'no_command': 9999} | ||||
| ``` | ||||
|  | ||||
| Or via environment variables: | ||||
|  | ||||
| * `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`; | ||||
| * `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`; | ||||
| * `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output; | ||||
| * `THEFUCK_NO_COLORS` – disable colored output, `true/false`; | ||||
| * `THEFUCK_PRIORITY` – priority of the rules, like `no_command=9999:apt_get=100`, | ||||
| rule with lower `priority` will be matched first. | ||||
|  | ||||
| For example: | ||||
|  | ||||
| ```bash | ||||
| export THEFUCK_RULES='sudo:no_command' | ||||
| export THEFUCK_REQUIRE_CONFIRMATION='true' | ||||
| export THEFUCK_WAIT_COMMAND=10 | ||||
| export THEFUCK_NO_COLORS='false' | ||||
| export THEFUCK_PRIORITY='no_command=9999:apt_get=100' | ||||
| ``` | ||||
|  | ||||
| ## Developing | ||||
|  | ||||
|   | ||||
| @@ -28,4 +28,4 @@ call('git commit -am "Bump to {}"'.format(version), shell=True) | ||||
| call('git tag {}'.format(version), shell=True) | ||||
| call('git push', shell=True) | ||||
| call('git push --tags', shell=True) | ||||
| call('python setup.py sdist upload', shell=True) | ||||
| call('python setup.py sdist bdist_wheel upload', shell=True) | ||||
|   | ||||
| @@ -1,2 +1,4 @@ | ||||
| pytest | ||||
| mock | ||||
| pytest-mock | ||||
| wheel | ||||
|   | ||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| from setuptools import setup, find_packages | ||||
|  | ||||
|  | ||||
| VERSION = '1.26' | ||||
| VERSION = '1.39' | ||||
|  | ||||
|  | ||||
| setup(name='thefuck', | ||||
| @@ -15,6 +15,7 @@ setup(name='thefuck', | ||||
|                                       'tests', 'release']), | ||||
|       include_package_data=True, | ||||
|       zip_safe=False, | ||||
|       install_requires=['pathlib', 'psutil', 'colorama'], | ||||
|       install_requires=['pathlib', 'psutil', 'colorama', 'six'], | ||||
|       entry_points={'console_scripts': [ | ||||
|           'thefuck = thefuck.main:main']}) | ||||
|           'thefuck = thefuck.main:main', | ||||
|           'thefuck-alias = thefuck.shells:app_alias']}) | ||||
|   | ||||
							
								
								
									
										0
									
								
								tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										49
									
								
								tests/rules/test_brew_install.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								tests/rules/test_brew_install.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| import pytest | ||||
| from thefuck.rules.brew_install import match, get_new_command | ||||
| from thefuck.rules.brew_install import brew_formulas | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def brew_no_available_formula(): | ||||
|     return '''Error: No available formula for elsticsearch ''' | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def brew_install_no_argument(): | ||||
|     return '''This command requires a formula argument''' | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def brew_already_installed(): | ||||
|     return '''Warning: git-2.3.5 already installed''' | ||||
|  | ||||
|  | ||||
| def _is_not_okay_to_test(): | ||||
|     if 'elasticsearch' not in brew_formulas: | ||||
|         return True | ||||
|     return False | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(_is_not_okay_to_test(), | ||||
|                     reason='No need to run if there\'s no formula') | ||||
| def test_match(brew_no_available_formula, brew_already_installed, | ||||
|                brew_install_no_argument): | ||||
|     assert match(Command('brew install elsticsearch', | ||||
|                          stderr=brew_no_available_formula), None) | ||||
|     assert not match(Command('brew install git', | ||||
|                              stderr=brew_already_installed), None) | ||||
|     assert not match(Command('brew install', stderr=brew_install_no_argument), | ||||
|                      None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.skipif(_is_not_okay_to_test(), | ||||
|                     reason='No need to run if there\'s no formula') | ||||
| def test_get_new_command(brew_no_available_formula): | ||||
|     assert get_new_command(Command('brew install elsticsearch', | ||||
|                                    stderr=brew_no_available_formula), None)\ | ||||
|         == 'brew install elasticsearch' | ||||
|  | ||||
|     assert get_new_command(Command('brew install aa', | ||||
|                                    stderr=brew_no_available_formula), | ||||
|                            None) != 'brew install aha' | ||||
							
								
								
									
										28
									
								
								tests/rules/test_brew_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								tests/rules/test_brew_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import pytest | ||||
| from thefuck.rules.brew_unknown_command import match, get_new_command | ||||
| from thefuck.rules.brew_unknown_command import brew_commands | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def brew_unknown_cmd(): | ||||
|     return '''Error: Unknown command: inst''' | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def brew_unknown_cmd2(): | ||||
|     return '''Error: Unknown command: instaa''' | ||||
|  | ||||
|  | ||||
| def test_match(brew_unknown_cmd): | ||||
|     assert match(Command('brew inst', stderr=brew_unknown_cmd), None) | ||||
|     for command in brew_commands: | ||||
|         assert not match(Command('brew ' + command), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2): | ||||
|     assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd), | ||||
|                            None) == 'brew list' | ||||
|  | ||||
|     assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2), | ||||
|                            None) == 'brew install' | ||||
							
								
								
									
										25
									
								
								tests/rules/test_cd_mkdir.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/rules/test_cd_mkdir.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import pytest | ||||
| from thefuck.rules.cd_mkdir import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='cd foo', stderr='cd: foo: No such file or directory'), | ||||
|     Command(script='cd foo/bar/baz', | ||||
|             stderr='cd: foo: No such file or directory'), | ||||
|     Command(script='cd foo/bar/baz', stderr='cd: can\'t cd to foo/bar/baz')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='cd foo', stderr=''), Command()]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('cd foo'), 'mkdir -p foo && cd foo'), | ||||
|     (Command('cd foo/bar/baz'), 'mkdir -p foo/bar/baz && cd foo/bar/baz')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
| @@ -1,12 +1,12 @@ | ||||
| from thefuck.main import Command | ||||
| from thefuck.rules.cd_parent import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('cd..', '', 'cd..: command not found'), None) | ||||
|     assert not match(Command('', '', ''), None) | ||||
|     assert match(Command('cd..', stderr='cd..: command not found'), None) | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command( | ||||
|         Command('cd..', '', ''), None) == 'cd ..' | ||||
|         Command('cd..'), None) == 'cd ..' | ||||
|   | ||||
							
								
								
									
										53
									
								
								tests/rules/test_composer_not_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/rules/test_composer_not_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import pytest | ||||
| from thefuck.rules.composer_not_command import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def composer_not_command(): | ||||
|     return """ | ||||
|  | ||||
|                                      | ||||
|   [InvalidArgumentException]         | ||||
|   Command "udpate" is not defined.   | ||||
|   Did you mean this?                 | ||||
|       update | ||||
|  | ||||
|  | ||||
| """ | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def composer_not_command_one_of_this(): | ||||
|     return """ | ||||
|                              | ||||
|  | ||||
|  | ||||
|   [InvalidArgumentException]        | ||||
|   Command "pdate" is not defined.   | ||||
|   Did you mean one of these?        | ||||
|       selfupdate                    | ||||
|       self-update                   | ||||
|       update                        | ||||
|  | ||||
|  | ||||
|  | ||||
| """ | ||||
|  | ||||
|  | ||||
| def test_match(composer_not_command, composer_not_command_one_of_this): | ||||
|     assert match(Command('composer udpate', | ||||
|                          stderr=composer_not_command), None) | ||||
|     assert match(Command('composer pdate', | ||||
|                          stderr=composer_not_command_one_of_this), None) | ||||
|     assert not match(Command('ls update', stderr=composer_not_command), | ||||
|                      None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(composer_not_command, composer_not_command_one_of_this): | ||||
|     assert get_new_command(Command('composer udpate', | ||||
|                                    stderr=composer_not_command), None) \ | ||||
|            == 'composer update' | ||||
|     assert get_new_command( | ||||
|         Command('composer pdate', stderr=composer_not_command_one_of_this), | ||||
|         None) == 'composer selfupdate' | ||||
							
								
								
									
										17
									
								
								tests/rules/test_dry.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tests/rules/test_dry.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import pytest | ||||
| from thefuck.rules.dry import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='cd cd foo'), | ||||
|     Command(script='git git push origin/master')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command('cd cd foo'), 'cd foo'), | ||||
|     (Command('git git push origin/master'), 'git push origin/master')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert get_new_command(command, None) == new_command | ||||
							
								
								
									
										22
									
								
								tests/rules/test_fix_alt_space.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/rules/test_fix_alt_space.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
|  | ||||
| from thefuck.rules.fix_alt_space import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     """The character before 'grep' is Alt+Space, which happens frequently | ||||
|     on the Mac when typing the pipe character (Alt+7), and holding the Alt | ||||
|     key pressed for longer than necessary. | ||||
|  | ||||
|     """ | ||||
|     assert match(Command(u'ps -ef | grep foo', | ||||
|                          stderr=u'-bash:  grep: command not found'), None) | ||||
|     assert not match(Command('ps -ef | grep foo'), None) | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     """ Replace the Alt+Space character by a simple space """ | ||||
|     assert get_new_command(Command(u'ps -ef | grep foo'), None)\ | ||||
|            == 'ps -ef | grep foo' | ||||
| @@ -1,6 +1,6 @@ | ||||
| import pytest | ||||
| from thefuck.main import Command | ||||
| from thefuck.rules.git_not_command import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| @@ -31,14 +31,14 @@ def git_command(): | ||||
|  | ||||
|  | ||||
| def test_match(git_not_command, git_command, git_not_command_one_of_this): | ||||
|     assert match(Command('git brnch', '', git_not_command), None) | ||||
|     assert match(Command('git st', '', git_not_command_one_of_this), None) | ||||
|     assert not match(Command('ls brnch', '', git_not_command), None) | ||||
|     assert not match(Command('git branch', '', git_command), None) | ||||
|     assert match(Command('git brnch', stderr=git_not_command), None) | ||||
|     assert match(Command('git st', stderr=git_not_command_one_of_this), None) | ||||
|     assert not match(Command('ls brnch', stderr=git_not_command), None) | ||||
|     assert not match(Command('git branch', stderr=git_command), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(git_not_command, git_not_command_one_of_this): | ||||
|     assert get_new_command(Command('git brnch', '', git_not_command), None)\ | ||||
|     assert get_new_command(Command('git brnch', stderr=git_not_command), None)\ | ||||
|         == 'git branch' | ||||
|     assert get_new_command( | ||||
|         Command('git st', '', git_not_command_one_of_this), None) == 'git status' | ||||
|     assert get_new_command(Command('git st', stderr=git_not_command_one_of_this), | ||||
|                            None) == 'git status' | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import pytest | ||||
| from thefuck.main import Command | ||||
| from thefuck.rules.git_push import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| @@ -14,11 +14,11 @@ To push the current branch and set the remote as upstream, use | ||||
|  | ||||
|  | ||||
| def test_match(stderr): | ||||
|     assert match(Command('git push master', '', stderr), None) | ||||
|     assert not match(Command('git push master', '', ''), None) | ||||
|     assert not match(Command('ls', '', stderr), None) | ||||
|     assert match(Command('git push master', stderr=stderr), None) | ||||
|     assert not match(Command('git push master'), None) | ||||
|     assert not match(Command('ls', stderr=stderr), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(stderr): | ||||
|     assert get_new_command(Command('', '', stderr), None)\ | ||||
|     assert get_new_command(Command(stderr=stderr), None)\ | ||||
|         == "git push --set-upstream origin master" | ||||
|   | ||||
							
								
								
									
										13
									
								
								tests/rules/test_ls_lah.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								tests/rules/test_ls_lah.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| from mock import patch, Mock | ||||
| from thefuck.rules.ls_lah import match, get_new_command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Mock(script='ls file.py'), None) | ||||
|     assert match(Mock(script='ls /opt'), None) | ||||
|     assert not match(Mock(script='ls -lah /opt'), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Mock(script='ls file.py'), None) == 'ls -lah file.py' | ||||
|     assert get_new_command(Mock(script='ls'), None) == 'ls -lah' | ||||
							
								
								
									
										12
									
								
								tests/rules/test_man_no_space.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/rules/test_man_no_space.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| from thefuck.rules.man_no_space import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('mandiff', stderr='mandiff: command not found'), None) | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command( | ||||
|         Command('mandiff'), None) == 'man diff' | ||||
| @@ -1,13 +1,22 @@ | ||||
| from thefuck.main import Command | ||||
| import pytest | ||||
| from thefuck.rules.mkdir_p import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('mkdir foo/bar/baz', '', 'mkdir: foo/bar: No such file or directory'), None) | ||||
|     assert not match(Command('mkdir foo/bar/baz', '', ''), None) | ||||
|     assert not match(Command('mkdir foo/bar/baz', '', 'foo bar baz'), None) | ||||
|     assert not match(Command('', '', ''), None) | ||||
|     assert match(Command('mkdir foo/bar/baz', | ||||
|                          stderr='mkdir: foo/bar: No such file or directory'), | ||||
|                  None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command('mkdir foo/bar/baz'), | ||||
|     Command('mkdir foo/bar/baz', stderr='foo bar baz'), | ||||
|     Command()]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('mkdir foo/bar/baz', '', ''), None) == 'mkdir -p foo/bar/baz' | ||||
|     assert get_new_command(Command('mkdir foo/bar/baz'), None)\ | ||||
|            == 'mkdir -p foo/bar/baz' | ||||
|   | ||||
							
								
								
									
										25
									
								
								tests/rules/test_pip_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/rules/test_pip_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import pytest | ||||
| from thefuck.rules.pip_unknown_command import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def pip_unknown_cmd(): | ||||
|     return '''ERROR: unknown command "instatl" - maybe you meant "install"''' | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def pip_unknown_cmd_without_recommend(): | ||||
|     return '''ERROR: unknown command "i"''' | ||||
|  | ||||
|  | ||||
| def test_match(pip_unknown_cmd, pip_unknown_cmd_without_recommend): | ||||
|     assert match(Command('pip instatl', stderr=pip_unknown_cmd), None) | ||||
|     assert not match(Command('pip i', | ||||
|                              stderr=pip_unknown_cmd_without_recommend), | ||||
|                      None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(pip_unknown_cmd): | ||||
|     assert get_new_command(Command('pip instatl', stderr=pip_unknown_cmd), | ||||
|                            None) == 'pip install' | ||||
| @@ -1,9 +1,12 @@ | ||||
| from thefuck.main import Command | ||||
| from thefuck.rules.python_command import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('temp.py', '', 'Permission denied'), None) | ||||
|     assert not match(Command('', '', ''), None) | ||||
|     assert match(Command('temp.py', stderr='Permission denied'), None) | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('./test_sudo.py', '', ''), None) == 'python ./test_sudo.py' | ||||
|     assert get_new_command(Command('./test_sudo.py'), None)\ | ||||
|            == 'python ./test_sudo.py' | ||||
|   | ||||
| @@ -1,12 +1,20 @@ | ||||
| from thefuck.main import Command | ||||
| import pytest | ||||
| from thefuck.rules.rm_dir import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('rm foo', '', 'rm: foo: is a directory'), None) | ||||
|     assert not match(Command('rm foo', '', ''), None) | ||||
|     assert not match(Command('rm foo', '', 'foo bar baz'), None) | ||||
|     assert not match(Command('', '', ''), None) | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command('rm foo', stderr='rm: foo: is a directory'), | ||||
|     Command('rm foo', stderr='rm: foo: Is a directory')]) | ||||
| def test_match(command): | ||||
|     assert match(command, None) | ||||
|     assert match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command('rm foo'), Command('rm foo'), Command()]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|   | ||||
							
								
								
									
										21
									
								
								tests/rules/test_rm_root.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								tests/rules/test_rm_root.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import pytest | ||||
| from thefuck.rules.rm_root import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command(script='rm -rf /', | ||||
|                          stderr='add --no-preserve-root'), None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(script='ls', stderr='add --no-preserve-root'), | ||||
|     Command(script='rm --no-preserve-root /', stderr='add --no-preserve-root'), | ||||
|     Command(script='rm -rf /', stderr='')]) | ||||
| def test_not_match(command): | ||||
|     assert not match(command, None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command(script='rm -rf /'), None) \ | ||||
|            == 'rm -rf / --no-preserve-root' | ||||
							
								
								
									
										12
									
								
								tests/rules/test_sl_ls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/rules/test_sl_ls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
|  | ||||
| from thefuck.rules.sl_ls import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('sl'), None) | ||||
|     assert not match(Command('ls'), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('sl'), None) == 'ls' | ||||
| @@ -1,8 +1,9 @@ | ||||
| import os | ||||
| import pytest | ||||
| from mock import Mock | ||||
| from thefuck.main import Command | ||||
| from thefuck.rules.ssh_known_hosts import match, get_new_command, remove_offending_keys | ||||
| from thefuck.rules.ssh_known_hosts import match, get_new_command,\ | ||||
|     side_effect | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| @@ -43,27 +44,23 @@ Host key verification failed.""".format(path, '98.765.432.321') | ||||
|  | ||||
| def test_match(ssh_error): | ||||
|     errormsg, _, _, _ = ssh_error | ||||
|     assert match(Command('ssh', '', errormsg), None) | ||||
|     assert match(Command('ssh', '', errormsg), None) | ||||
|     assert match(Command('scp something something', '', errormsg), None) | ||||
|     assert match(Command('scp something something', '', errormsg), None) | ||||
|     assert not match(Command('', '', errormsg), None) | ||||
|     assert not match(Command('notssh', '', errormsg), None) | ||||
|     assert not match(Command('ssh', '', ''), None) | ||||
|     assert match(Command('ssh', stderr=errormsg), None) | ||||
|     assert match(Command('ssh', stderr=errormsg), None) | ||||
|     assert match(Command('scp something something', stderr=errormsg), None) | ||||
|     assert match(Command('scp something something', stderr=errormsg), None) | ||||
|     assert not match(Command(stderr=errormsg), None) | ||||
|     assert not match(Command('notssh', stderr=errormsg), None) | ||||
|     assert not match(Command('ssh'), None) | ||||
|  | ||||
|  | ||||
| def test_remove_offending_keys(ssh_error): | ||||
| def test_side_effect(ssh_error): | ||||
|     errormsg, path, reset, known_hosts = ssh_error | ||||
|     command = Command('ssh user@host', '', errormsg) | ||||
|     remove_offending_keys(command, None) | ||||
|     command = Command('ssh user@host', stderr=errormsg) | ||||
|     side_effect(command, None) | ||||
|     expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n'] | ||||
|     assert known_hosts(path) == expected | ||||
|  | ||||
|  | ||||
| def test_get_new_command(ssh_error, monkeypatch): | ||||
|     errormsg, _, _, _ = ssh_error | ||||
|  | ||||
|     method = Mock() | ||||
|     monkeypatch.setattr('thefuck.rules.ssh_known_hosts.remove_offending_keys', method) | ||||
|     assert get_new_command(Command('ssh user@host', '', errormsg), None) == 'ssh user@host' | ||||
|     assert method.call_count | ||||
|     assert get_new_command(Command('ssh user@host', stderr=errormsg), None) == 'ssh user@host' | ||||
|   | ||||
| @@ -1,13 +1,21 @@ | ||||
| from thefuck.main import Command | ||||
| import pytest | ||||
| from thefuck.rules.sudo import match, get_new_command | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert match(Command('', '', 'Permission denied'), None) | ||||
|     assert match(Command('', '', 'permission denied'), None) | ||||
|     assert match(Command('', '', "npm ERR! Error: EACCES, unlink"), None) | ||||
|     assert not match(Command('', '', ''), None) | ||||
| @pytest.mark.parametrize('stderr, stdout', [ | ||||
|     ('Permission denied', ''), | ||||
|     ('permission denied', ''), | ||||
|     ("npm ERR! Error: EACCES, unlink", ''), | ||||
|     ('requested operation requires superuser privilege', ''), | ||||
|     ('', "error: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/ipaddr.py'")]) | ||||
| def test_match(stderr, stdout): | ||||
|     assert match(Command(stderr=stderr, stdout=stdout), None) | ||||
|  | ||||
|  | ||||
| def test_not_match(): | ||||
|     assert not match(Command(), None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert get_new_command(Command('ls', '', ''), None) == 'sudo ls' | ||||
|     assert get_new_command(Command('ls'), None) == 'sudo ls' | ||||
|   | ||||
| @@ -1,25 +1,27 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
|  | ||||
| from mock import Mock | ||||
| import pytest | ||||
| from thefuck.rules import switch_lang | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| def test_match(): | ||||
|     assert switch_lang.match(Mock(stderr='command not found: фзе-пуе', | ||||
|                                   script=u'фзе-пуе'), None) | ||||
|     assert switch_lang.match(Mock(stderr='command not found: λσ', | ||||
|                                   script=u'λσ'), None) | ||||
|  | ||||
|     assert not switch_lang.match(Mock(stderr='command not found: pat-get', | ||||
|                                       script=u'pat-get'), None) | ||||
|     assert not switch_lang.match(Mock(stderr='command not found: ls', | ||||
|                                       script=u'ls'), None) | ||||
|     assert not switch_lang.match(Mock(stderr='some info', | ||||
|                                       script=u'фзе-пуе'), None) | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(stderr='command not found: фзе-пуе', script=u'фзе-пуе'), | ||||
|     Command(stderr='command not found: λσ', script=u'λσ')]) | ||||
| def test_match(command): | ||||
|     assert switch_lang.match(command, None) | ||||
|  | ||||
|  | ||||
| def test_get_new_command(): | ||||
|     assert switch_lang.get_new_command( | ||||
|         Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim' | ||||
|     assert switch_lang.get_new_command( | ||||
|         Mock(script=u'λσ -λα'), None) == 'ls -la' | ||||
| @pytest.mark.parametrize('command', [ | ||||
|     Command(stderr='command not found: pat-get', script=u'pat-get'), | ||||
|     Command(stderr='command not found: ls', script=u'ls'), | ||||
|     Command(stderr='some info', script=u'фзе-пуе')]) | ||||
| def test_not_match(command): | ||||
|     assert not switch_lang.match(command, None) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('command, new_command', [ | ||||
|     (Command(u'фзе-пуе штыефдд мшь'), 'apt-get install vim'), | ||||
|     (Command(u'λσ -λα'), 'ls -la')]) | ||||
| def test_get_new_command(command, new_command): | ||||
|     assert switch_lang.get_new_command(command, None) == new_command | ||||
|   | ||||
							
								
								
									
										102
									
								
								tests/test_conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								tests/test_conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| import pytest | ||||
| import six | ||||
| from mock import Mock | ||||
| from thefuck import conf | ||||
| from tests.utils import Rule | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('enabled, rules, result', [ | ||||
|     (True, conf.DEFAULT_RULES, True), | ||||
|     (False, conf.DEFAULT_RULES, False), | ||||
|     (False, conf.DEFAULT_RULES + ['test'], True)]) | ||||
| def test_default(enabled, rules, result): | ||||
|     assert (Rule('test', enabled_by_default=enabled) in rules) == result | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def load_source(mocker): | ||||
|     return mocker.patch('thefuck.conf.load_source') | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def environ(monkeypatch): | ||||
|     data = {} | ||||
|     monkeypatch.setattr('thefuck.conf.os.environ', data) | ||||
|     return data | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixture('environ') | ||||
| def test_settings_defaults(load_source): | ||||
|     load_source.return_value = object() | ||||
|     for key, val in conf.DEFAULT_SETTINGS.items(): | ||||
|         assert getattr(conf.get_settings(Mock()), key) == val | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixture('environ') | ||||
| class TestSettingsFromFile(object): | ||||
|     def test_from_file(self, load_source): | ||||
|         load_source.return_value = Mock(rules=['test'], | ||||
|                                         wait_command=10, | ||||
|                                         require_confirmation=True, | ||||
|                                         no_colors=True, | ||||
|                                         priority={'vim': 100}) | ||||
|         settings = conf.get_settings(Mock()) | ||||
|         assert settings.rules == ['test'] | ||||
|         assert settings.wait_command == 10 | ||||
|         assert settings.require_confirmation is True | ||||
|         assert settings.no_colors is True | ||||
|         assert settings.priority == {'vim': 100} | ||||
|  | ||||
|     def test_from_file_with_DEFAULT(self, load_source): | ||||
|         load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'], | ||||
|                                         wait_command=10, | ||||
|                                         require_confirmation=True, | ||||
|                                         no_colors=True) | ||||
|         settings = conf.get_settings(Mock()) | ||||
|         assert settings.rules == conf.DEFAULT_RULES + ['test'] | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixture('load_source') | ||||
| class TestSettingsFromEnv(object): | ||||
|     def test_from_env(self, environ): | ||||
|         environ.update({'THEFUCK_RULES': 'bash:lisp', | ||||
|                         'THEFUCK_WAIT_COMMAND': '55', | ||||
|                         'THEFUCK_REQUIRE_CONFIRMATION': 'true', | ||||
|                         'THEFUCK_NO_COLORS': 'false', | ||||
|                         'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15'}) | ||||
|         settings = conf.get_settings(Mock()) | ||||
|         assert settings.rules == ['bash', 'lisp'] | ||||
|         assert settings.wait_command == 55 | ||||
|         assert settings.require_confirmation is True | ||||
|         assert settings.no_colors is False | ||||
|         assert settings.priority == {'bash': 10, 'vim': 15} | ||||
|  | ||||
|     def test_from_env_with_DEFAULT(self, environ): | ||||
|         environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) | ||||
|         settings = conf.get_settings(Mock()) | ||||
|         assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp'] | ||||
|  | ||||
|  | ||||
| class TestInitializeSettingsFile(object): | ||||
|     def test_ignore_if_exists(self): | ||||
|         settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock()) | ||||
|         user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) | ||||
|         conf.initialize_settings_file(user_dir_mock) | ||||
|         assert settings_path_mock.is_file.call_count == 1 | ||||
|         assert not settings_path_mock.open.called | ||||
|  | ||||
|     def test_create_if_doesnt_exists(self): | ||||
|         settings_file = six.StringIO() | ||||
|         settings_path_mock = Mock( | ||||
|             is_file=Mock(return_value=False), | ||||
|             open=Mock(return_value=Mock( | ||||
|                 __exit__=lambda *args: None, __enter__=lambda *args: settings_file))) | ||||
|         user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) | ||||
|         conf.initialize_settings_file(user_dir_mock) | ||||
|         settings_file_contents = settings_file.getvalue() | ||||
|         assert settings_path_mock.is_file.call_count == 1 | ||||
|         assert settings_path_mock.open.call_count == 1 | ||||
|         assert conf.SETTINGS_HEADER in settings_file_contents | ||||
|         for setting in conf.DEFAULT_SETTINGS.items(): | ||||
|             assert '# {} = {}\n'.format(*setting) in settings_file_contents | ||||
|         settings_file.close() | ||||
| @@ -1,103 +1,173 @@ | ||||
| import pytest | ||||
| from subprocess import PIPE | ||||
| from pathlib import PosixPath, Path | ||||
| from mock import patch, Mock | ||||
| from thefuck import main | ||||
| from mock import Mock | ||||
| from thefuck import main, conf, types | ||||
| from tests.utils import Rule, Command | ||||
|  | ||||
|  | ||||
| def test_get_settings(): | ||||
|     with patch('thefuck.main.load_source', return_value=Mock(rules=['bash'])): | ||||
|         assert main.get_settings(Path('/')).rules == ['bash'] | ||||
|     with patch('thefuck.main.load_source', return_value=Mock(spec=[])): | ||||
|         assert main.get_settings(Path('/')).rules is None | ||||
|  | ||||
|  | ||||
| def test_is_rule_enabled(): | ||||
|     assert main.is_rule_enabled(Mock(rules=None), Path('bash.py')) | ||||
|     assert main.is_rule_enabled(Mock(rules=['bash']), Path('bash.py')) | ||||
|     assert not main.is_rule_enabled(Mock(rules=['bash']), Path('lisp.py')) | ||||
|  | ||||
|  | ||||
| def test_load_rule(): | ||||
| def test_load_rule(mocker): | ||||
|     match = object() | ||||
|     get_new_command = object() | ||||
|     with patch('thefuck.main.load_source', | ||||
|                return_value=Mock( | ||||
|                    match=match, | ||||
|                    get_new_command=get_new_command)) as load_source: | ||||
|         assert main.load_rule(Path('/rules/bash.py')) == main.Rule('bash', match, get_new_command) | ||||
|     load_source = mocker.patch( | ||||
|         'thefuck.main.load_source', | ||||
|         return_value=Mock(match=match, | ||||
|                           get_new_command=get_new_command, | ||||
|                           enabled_by_default=True, | ||||
|                           priority=900)) | ||||
|     assert main.load_rule(Path('/rules/bash.py')) \ | ||||
|            == Rule('bash', match, get_new_command, priority=900) | ||||
|     load_source.assert_called_once_with('bash', '/rules/bash.py') | ||||
|  | ||||
|  | ||||
| def test_get_rules(): | ||||
|     with patch('thefuck.main.Path.glob') as glob, \ | ||||
|             patch('thefuck.main.load_source', | ||||
|                   lambda x, _: Mock(match=x, get_new_command=x)): | ||||
| class TestGetRules(object): | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def glob(self, mocker): | ||||
|         return mocker.patch('thefuck.main.Path.glob', return_value=[]) | ||||
|  | ||||
|     def _compare_names(self, rules, names): | ||||
|         return [r.name for r in rules] == names | ||||
|  | ||||
|     @pytest.mark.parametrize('conf_rules, rules', [ | ||||
|         (conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']), | ||||
|         (types.RulesNamesList(['bash']), ['bash', 'bash'])]) | ||||
|     def test_get(self, monkeypatch, glob, conf_rules, rules): | ||||
|         glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] | ||||
|         assert main.get_rules( | ||||
|             Path('~'), | ||||
|             Mock(rules=None)) == [main.Rule('bash', 'bash', 'bash'), | ||||
|                                   main.Rule('lisp', 'lisp', 'lisp'), | ||||
|                                   main.Rule('bash', 'bash', 'bash'), | ||||
|                                   main.Rule('lisp', 'lisp', 'lisp')] | ||||
|         assert main.get_rules( | ||||
|             Path('~'), | ||||
|             Mock(rules=['bash'])) == [main.Rule('bash', 'bash', 'bash'), | ||||
|                                       main.Rule('bash', 'bash', 'bash')] | ||||
|         monkeypatch.setattr('thefuck.main.load_source', | ||||
|                             lambda x, _: Rule(x)) | ||||
|         assert self._compare_names( | ||||
|             main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})), | ||||
|             rules) | ||||
|  | ||||
|     @pytest.mark.parametrize('priority, unordered, ordered', [ | ||||
|         ({}, | ||||
|          [Rule('bash', priority=100), Rule('python', priority=5)], | ||||
|          ['python', 'bash']), | ||||
|         ({}, | ||||
|          [Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)], | ||||
|          ['c', 'lisp']), | ||||
|         ({'python': 9999}, | ||||
|          [Rule('bash', priority=100), Rule('python', priority=5)], | ||||
|          ['bash', 'python'])]) | ||||
|     def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered): | ||||
|         monkeypatch.setattr('thefuck.main._get_loaded_rules', | ||||
|                             lambda *_: unordered) | ||||
|         assert self._compare_names( | ||||
|             main.get_rules(Path('~'), Mock(priority=priority)), | ||||
|             ordered) | ||||
|  | ||||
|  | ||||
| def test_get_command(): | ||||
|     with patch('thefuck.main.Popen') as Popen, \ | ||||
|             patch('thefuck.main.os.environ', | ||||
|                   new_callable=lambda: {}), \ | ||||
|             patch('thefuck.main.wait_output', | ||||
|                   return_value=True): | ||||
| class TestGetCommand(object): | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, monkeypatch): | ||||
|         Popen = Mock() | ||||
|         Popen.return_value.stdout.read.return_value = b'stdout' | ||||
|         Popen.return_value.stderr.read.return_value = b'stderr' | ||||
|         assert main.get_command(Mock(), ['thefuck', 'apt-get', | ||||
|                                          'search', 'vim']) \ | ||||
|                == main.Command('apt-get search vim', 'stdout', 'stderr') | ||||
|         monkeypatch.setattr('thefuck.main.Popen', Popen) | ||||
|         return Popen | ||||
|  | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def prepare(self, monkeypatch): | ||||
|         monkeypatch.setattr('thefuck.main.os.environ', {}) | ||||
|         monkeypatch.setattr('thefuck.main.wait_output', lambda *_: True) | ||||
|  | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def generic_shell(self, monkeypatch): | ||||
|         monkeypatch.setattr('thefuck.shells.from_shell', lambda x: x) | ||||
|         monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x) | ||||
|  | ||||
|     def test_get_command_calls(self, Popen): | ||||
|         assert main.get_command(Mock(), | ||||
|             ['thefuck', 'apt-get', 'search', 'vim']) \ | ||||
|                == Command('apt-get search vim', 'stdout', 'stderr') | ||||
|         Popen.assert_called_once_with('apt-get search vim', | ||||
|                                       shell=True, | ||||
|                                       stdout=PIPE, | ||||
|                                       stderr=PIPE, | ||||
|                                       env={'LANG': 'C'}) | ||||
|         assert main.get_command(Mock(), ['']) is None | ||||
|  | ||||
|     @pytest.mark.parametrize('args, result', [ | ||||
|         (['thefuck', 'ls', '-la'], 'ls -la'), | ||||
|         (['thefuck', 'ls'], 'ls')]) | ||||
|     def test_get_command_script(self, args, result): | ||||
|         if result: | ||||
|             assert main.get_command(Mock(), args).script == result | ||||
|         else: | ||||
|             assert main.get_command(Mock(), args) is None | ||||
|  | ||||
|  | ||||
| def test_get_matched_rule(capsys): | ||||
|     rules = [main.Rule('', lambda x, _: x.script == 'cd ..', None), | ||||
|              main.Rule('', lambda *_: False, None), | ||||
|              main.Rule('rule', Mock(side_effect=OSError('Denied')), None)] | ||||
|     assert main.get_matched_rule(main.Command('ls', '', ''), | ||||
|                                  rules, Mock(no_colors=True)) is None | ||||
|     assert main.get_matched_rule(main.Command('cd ..', '', ''), | ||||
|                                  rules, Mock(no_colors=True)) == rules[0] | ||||
|     assert capsys.readouterr()[1].split('\n')[0]\ | ||||
|            == '[WARN] Rule rule:' | ||||
| class TestGetMatchedRule(object): | ||||
|     def test_no_match(self): | ||||
|         assert main.get_matched_rule( | ||||
|             Command('ls'), [Rule('', lambda *_: False)], | ||||
|             Mock(no_colors=True)) is None | ||||
|  | ||||
|     def test_match(self): | ||||
|         rule = Rule('', lambda x, _: x.script == 'cd ..') | ||||
|         assert main.get_matched_rule( | ||||
|             Command('cd ..'), [rule], Mock(no_colors=True)) == rule | ||||
|  | ||||
|     def test_when_rule_failed(self, capsys): | ||||
|         main.get_matched_rule( | ||||
|             Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))], | ||||
|             Mock(no_colors=True)) | ||||
|         assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:' | ||||
|  | ||||
|  | ||||
| def test_run_rule(capsys): | ||||
|     with patch('thefuck.main.confirm', return_value=True): | ||||
|         main.run_rule(main.Rule('', None, lambda *_: 'new-command'), | ||||
|                       None, None) | ||||
| class TestRunRule(object): | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def confirm(self, mocker): | ||||
|         return mocker.patch('thefuck.main.confirm', return_value=True) | ||||
|  | ||||
|     def test_run_rule(self, capsys): | ||||
|         main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), | ||||
|                       Command(), None) | ||||
|         assert capsys.readouterr() == ('new-command\n', '') | ||||
|     with patch('thefuck.main.confirm', return_value=False): | ||||
|         main.run_rule(main.Rule('', None, lambda *_: 'new-command'), | ||||
|                       None, None) | ||||
|  | ||||
|     def test_run_rule_with_side_effect(self, capsys): | ||||
|         side_effect = Mock() | ||||
|         settings = Mock() | ||||
|         command = Command() | ||||
|         main.run_rule(Rule(get_new_command=lambda *_: 'new-command', | ||||
|                            side_effect=side_effect), | ||||
|                       command, settings) | ||||
|         assert capsys.readouterr() == ('new-command\n', '') | ||||
|         side_effect.assert_called_once_with(command, settings) | ||||
|  | ||||
|     def test_when_not_comfirmed(self, capsys, confirm): | ||||
|         confirm.return_value = False | ||||
|         main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), | ||||
|                       Command(), None) | ||||
|         assert capsys.readouterr() == ('', '') | ||||
|  | ||||
|  | ||||
| def test_confirm(capsys): | ||||
|     # When confirmation not required: | ||||
|     assert main.confirm('command', Mock(require_confirmation=False)) | ||||
| class TestConfirm(object): | ||||
|     @pytest.fixture | ||||
|     def stdin(self, mocker): | ||||
|         return mocker.patch('sys.stdin.read', return_value='\n') | ||||
|  | ||||
|     def test_when_not_required(self, capsys): | ||||
|         assert main.confirm('command', None, Mock(require_confirmation=False)) | ||||
|         assert capsys.readouterr() == ('', 'command\n') | ||||
|     # When confirmation required and confirmed: | ||||
|     with patch('thefuck.main.sys.stdin.read', return_value='\n'): | ||||
|         assert main.confirm('command', Mock(require_confirmation=True, | ||||
|  | ||||
|     def test_with_side_effect_and_without_confirmation(self, capsys): | ||||
|         assert main.confirm('command', Mock(), Mock(require_confirmation=False)) | ||||
|         assert capsys.readouterr() == ('', 'command*\n') | ||||
|  | ||||
|     # `stdin` fixture should be applied after `capsys` | ||||
|     def test_when_confirmation_required_and_confirmed(self, capsys, stdin): | ||||
|         assert main.confirm('command', None, Mock(require_confirmation=True, | ||||
|                                                   no_colors=True)) | ||||
|         assert capsys.readouterr() == ('', 'command [enter/ctrl+c]') | ||||
|     # When confirmation required and ctrl+c: | ||||
|     with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt): | ||||
|         assert not main.confirm('command', Mock(require_confirmation=True, | ||||
|  | ||||
|     # `stdin` fixture should be applied after `capsys` | ||||
|     def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin): | ||||
|         assert main.confirm('command', Mock(), Mock(require_confirmation=True, | ||||
|                                                     no_colors=True)) | ||||
|         assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]') | ||||
|  | ||||
|     def test_when_confirmation_required_and_aborted(self, capsys, stdin): | ||||
|         stdin.side_effect = KeyboardInterrupt | ||||
|         assert not main.confirm('command', None, Mock(require_confirmation=True, | ||||
|                                                       no_colors=True)) | ||||
|         assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n') | ||||
|   | ||||
							
								
								
									
										78
									
								
								tests/test_shells.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								tests/test_shells.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import pytest | ||||
| from thefuck import shells | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def builtins_open(mocker): | ||||
|     return mocker.patch('six.moves.builtins.open') | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def isfile(mocker): | ||||
|     return mocker.patch('os.path.isfile', return_value=True) | ||||
|  | ||||
|  | ||||
| class TestGeneric(object): | ||||
|     def test_from_shell(self): | ||||
|         assert shells.Generic().from_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_to_shell(self): | ||||
|         assert shells.Generic().to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open): | ||||
|         assert shells.Generic().put_to_history('ls') is None | ||||
|         assert builtins_open.call_count == 0 | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixtures('isfile') | ||||
| class TestBash(object): | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, mocker): | ||||
|         mock = mocker.patch('thefuck.shells.Popen') | ||||
|         mock.return_value.stdout.read.return_value = ( | ||||
|             b'alias l=\'ls -CF\'\n' | ||||
|             b'alias la=\'ls -A\'\n' | ||||
|             b'alias ll=\'ls -alF\'') | ||||
|         return mock | ||||
|  | ||||
|     @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_to_shell(self): | ||||
|         assert shells.Bash().to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open): | ||||
|         shells.Bash().put_to_history('ls') | ||||
|         builtins_open.return_value.__enter__.return_value. \ | ||||
|             write.assert_called_once_with('ls\n') | ||||
|  | ||||
|  | ||||
| @pytest.mark.usefixtures('isfile') | ||||
| class TestZsh(object): | ||||
|     @pytest.fixture(autouse=True) | ||||
|     def Popen(self, mocker): | ||||
|         mock = mocker.patch('thefuck.shells.Popen') | ||||
|         mock.return_value.stdout.read.return_value = ( | ||||
|             b'l=\'ls -CF\'\n' | ||||
|             b'la=\'ls -A\'\n' | ||||
|             b'll=\'ls -alF\'') | ||||
|         return mock | ||||
|  | ||||
|     @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_to_shell(self): | ||||
|         assert shells.Zsh().to_shell('pwd') == 'pwd' | ||||
|  | ||||
|     def test_put_to_history(self, builtins_open, mocker): | ||||
|         mocker.patch('thefuck.shells.time', | ||||
|                      return_value=1430707243.3517463) | ||||
|         shells.Zsh().put_to_history('ls') | ||||
|         builtins_open.return_value.__enter__.return_value. \ | ||||
|             write.assert_called_once_with(': 1430707243:0;ls\n') | ||||
							
								
								
									
										16
									
								
								tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tests/test_types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| from thefuck.types import RulesNamesList, Settings | ||||
| from tests.utils import Rule | ||||
|  | ||||
|  | ||||
| def test_rules_names_list(): | ||||
|     assert RulesNamesList(['bash', 'lisp']) == ['bash', 'lisp'] | ||||
|     assert RulesNamesList(['bash', 'lisp']) == RulesNamesList(['bash', 'lisp']) | ||||
|     assert Rule('lisp') in RulesNamesList(['lisp']) | ||||
|     assert Rule('bash') not in RulesNamesList(['lisp']) | ||||
|  | ||||
|  | ||||
| def test_update_settings(): | ||||
|     settings = Settings({'key': 'val'}) | ||||
|     new_settings = settings.update(key='new-val') | ||||
|     assert new_settings.key == 'new-val' | ||||
|     assert settings.key == 'val' | ||||
							
								
								
									
										26
									
								
								tests/test_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								tests/test_utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import pytest | ||||
| from mock import Mock | ||||
| from thefuck.utils import sudo_support, wrap_settings | ||||
| from thefuck.types import Settings | ||||
| from tests.utils import Command | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('override, old, new', [ | ||||
|     ({'key': 'val'}, {}, {'key': 'val'}), | ||||
|     ({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})]) | ||||
| def test_wrap_settings(override, old, new): | ||||
|     fn = lambda _, settings: settings | ||||
|     assert wrap_settings(override)(fn)(None, Settings(old)) == new | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize('return_value, command, called, result', [ | ||||
|     ('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'), | ||||
|     ('ls -lah', 'ls', 'ls', 'ls -lah'), | ||||
|     (True, 'sudo ls', 'ls', True), | ||||
|     (True, 'ls', 'ls', True), | ||||
|     (False, 'sudo ls', 'ls', False), | ||||
|     (False, 'ls', 'ls', False)]) | ||||
| 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) | ||||
							
								
								
									
										16
									
								
								tests/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tests/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| from thefuck import types | ||||
| from thefuck.conf import DEFAULT_PRIORITY | ||||
|  | ||||
|  | ||||
| def Command(script='', stdout='', stderr=''): | ||||
|     return types.Command(script, stdout, stderr) | ||||
|  | ||||
|  | ||||
| def Rule(name='', match=lambda *_: True, | ||||
|          get_new_command=lambda *_: '', | ||||
|          enabled_by_default=True, | ||||
|          side_effect=None, | ||||
|          priority=DEFAULT_PRIORITY): | ||||
|     return types.Rule(name, match, get_new_command, | ||||
|                       enabled_by_default, side_effect, | ||||
|                       priority) | ||||
							
								
								
									
										132
									
								
								thefuck/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								thefuck/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| from copy import copy | ||||
| from imp import load_source | ||||
| import os | ||||
| import sys | ||||
| from six import text_type | ||||
| from . import logs, types | ||||
|  | ||||
|  | ||||
| class _DefaultRulesNames(types.RulesNamesList): | ||||
|     def __add__(self, items): | ||||
|         return _DefaultRulesNames(list(self) + items) | ||||
|  | ||||
|     def __contains__(self, item): | ||||
|         return item.enabled_by_default or \ | ||||
|                super(_DefaultRulesNames, self).__contains__(item) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         if isinstance(other, _DefaultRulesNames): | ||||
|             return super(_DefaultRulesNames, self).__eq__(other) | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|  | ||||
| DEFAULT_RULES = _DefaultRulesNames([]) | ||||
| DEFAULT_PRIORITY = 1000 | ||||
|  | ||||
|  | ||||
| DEFAULT_SETTINGS = {'rules': DEFAULT_RULES, | ||||
|                     'wait_command': 3, | ||||
|                     'require_confirmation': False, | ||||
|                     'no_colors': False, | ||||
|                     'priority': {}} | ||||
|  | ||||
| ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', | ||||
|                'THEFUCK_WAIT_COMMAND': 'wait_command', | ||||
|                'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation', | ||||
|                'THEFUCK_NO_COLORS': 'no_colors', | ||||
|                'THEFUCK_PRIORITY': 'priority'} | ||||
|  | ||||
|  | ||||
| SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file | ||||
| # | ||||
| # The rules are defined as in the example bellow: | ||||
| # | ||||
| # rules = ['cd_parent', 'git_push', 'python_command', 'sudo'] | ||||
| # | ||||
| # The default values are as follows. Uncomment and change to fit your needs. | ||||
| # See https://github.com/nvbn/thefuck#settings for more information. | ||||
| # | ||||
|  | ||||
| """ | ||||
|  | ||||
|  | ||||
| def _settings_from_file(user_dir): | ||||
|     """Loads settings from file.""" | ||||
|     settings = load_source('settings', | ||||
|                            text_type(user_dir.joinpath('settings.py'))) | ||||
|     return {key: getattr(settings, key) | ||||
|             for key in DEFAULT_SETTINGS.keys() | ||||
|             if hasattr(settings, key)} | ||||
|  | ||||
|  | ||||
| def _rules_from_env(val): | ||||
|     """Transforms rules list from env-string to python.""" | ||||
|     val = val.split(':') | ||||
|     if 'DEFAULT_RULES' in val: | ||||
|         val = DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES'] | ||||
|     return val | ||||
|  | ||||
|  | ||||
| def _priority_from_env(val): | ||||
|     """Gets priority pairs from env.""" | ||||
|     for part in val.split(':'): | ||||
|         try: | ||||
|             rule, priority = part.split('=') | ||||
|             yield rule, int(priority) | ||||
|         except ValueError: | ||||
|             continue | ||||
|  | ||||
|  | ||||
| def _val_from_env(env, attr): | ||||
|     """Transforms env-strings to python.""" | ||||
|     val = os.environ[env] | ||||
|     if attr == 'rules': | ||||
|         return _rules_from_env(val) | ||||
|     elif attr == 'priority': | ||||
|         return dict(_priority_from_env(val)) | ||||
|     elif attr == 'wait_command': | ||||
|         return int(val) | ||||
|     elif attr in ('require_confirmation', 'no_colors'): | ||||
|         return val.lower() == 'true' | ||||
|     else: | ||||
|         return val | ||||
|  | ||||
|  | ||||
| def _settings_from_env(): | ||||
|     """Loads settings from env.""" | ||||
|     return {attr: _val_from_env(env, attr) | ||||
|             for env, attr in ENV_TO_ATTR.items() | ||||
|             if env in os.environ} | ||||
|  | ||||
|  | ||||
| def get_settings(user_dir): | ||||
|     """Returns settings filled with values from `settings.py` and env.""" | ||||
|     conf = copy(DEFAULT_SETTINGS) | ||||
|     try: | ||||
|         conf.update(_settings_from_file(user_dir)) | ||||
|     except Exception: | ||||
|         logs.exception("Can't load settings from file", | ||||
|                        sys.exc_info(), | ||||
|                        types.Settings(conf)) | ||||
|  | ||||
|     try: | ||||
|         conf.update(_settings_from_env()) | ||||
|     except Exception: | ||||
|         logs.exception("Can't load settings from env", | ||||
|                        sys.exc_info(), | ||||
|                        types.Settings(conf)) | ||||
|  | ||||
|     if not isinstance(conf['rules'], types.RulesNamesList): | ||||
|         conf['rules'] = types.RulesNamesList(conf['rules']) | ||||
|  | ||||
|     return types.Settings(conf) | ||||
|  | ||||
|  | ||||
| def initialize_settings_file(user_dir): | ||||
|     settings_path = user_dir.joinpath('settings.py') | ||||
|     if not settings_path.is_file(): | ||||
|         with settings_path.open(mode='w') as settings_file: | ||||
|             settings_file.write(SETTINGS_HEADER) | ||||
|             for setting in DEFAULT_SETTINGS.items(): | ||||
|                 settings_file.write(u'# {} = {}\n'.format(*setting)) | ||||
| @@ -11,28 +11,35 @@ def color(color_, settings): | ||||
|         return color_ | ||||
|  | ||||
|  | ||||
| def rule_failed(rule, exc_info, settings): | ||||
| def exception(title, exc_info, settings): | ||||
|     sys.stderr.write( | ||||
|         u'{warn}[WARN] Rule {name}:{reset}\n{trace}' | ||||
|         u'{warn}[WARN] {title}:{reset}\n{trace}' | ||||
|         u'{warn}----------------------------{reset}\n\n'.format( | ||||
|             warn=color(colorama.Back.RED + colorama.Fore.WHITE | ||||
|                        + colorama.Style.BRIGHT, settings), | ||||
|             reset=color(colorama.Style.RESET_ALL, settings), | ||||
|             name=rule.name, | ||||
|             title=title, | ||||
|             trace=''.join(format_exception(*exc_info)))) | ||||
|  | ||||
|  | ||||
| def show_command(new_command, settings): | ||||
|     sys.stderr.write('{bold}{command}{reset}\n'.format( | ||||
| def rule_failed(rule, exc_info, settings): | ||||
|     exception('Rule {}'.format(rule.name), exc_info, settings) | ||||
|  | ||||
|  | ||||
| def show_command(new_command, side_effect, settings): | ||||
|     sys.stderr.write('{bold}{command}{side_effect}{reset}\n'.format( | ||||
|         command=new_command, | ||||
|         side_effect='*' if side_effect else '', | ||||
|         bold=color(colorama.Style.BRIGHT, settings), | ||||
|         reset=color(colorama.Style.RESET_ALL, settings))) | ||||
|  | ||||
|  | ||||
| def confirm_command(new_command, settings): | ||||
| def confirm_command(new_command, side_effect, settings): | ||||
|     sys.stderr.write( | ||||
|         '{bold}{command}{reset} [{green}enter{reset}/{red}ctrl+c{reset}]'.format( | ||||
|         '{bold}{command}{side_effect}{reset} ' | ||||
|         '[{green}enter{reset}/{red}ctrl+c{reset}]'.format( | ||||
|             command=new_command, | ||||
|             side_effect='*' if side_effect else '', | ||||
|             bold=color(colorama.Style.BRIGHT, settings), | ||||
|             green=color(colorama.Fore.GREEN, settings), | ||||
|             red=color(colorama.Fore.RED, settings), | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| from collections import namedtuple | ||||
| from imp import load_source | ||||
| from pathlib import Path | ||||
| from os.path import expanduser | ||||
| @@ -7,11 +6,8 @@ import os | ||||
| import sys | ||||
| from psutil import Process, TimeoutExpired | ||||
| import colorama | ||||
| from thefuck import logs | ||||
|  | ||||
|  | ||||
| Command = namedtuple('Command', ('script', 'stdout', 'stderr')) | ||||
| Rule = namedtuple('Rule', ('name', 'match', 'get_new_command')) | ||||
| import six | ||||
| from . import logs, conf, types, shells | ||||
|  | ||||
|  | ||||
| def setup_user_dir(): | ||||
| @@ -20,34 +16,27 @@ def setup_user_dir(): | ||||
|     rules_dir = user_dir.joinpath('rules') | ||||
|     if not rules_dir.is_dir(): | ||||
|         rules_dir.mkdir(parents=True) | ||||
|     user_dir.joinpath('settings.py').touch() | ||||
|     conf.initialize_settings_file(user_dir) | ||||
|     return user_dir | ||||
|  | ||||
|  | ||||
| def get_settings(user_dir): | ||||
|     """Returns prepared settings module.""" | ||||
|     settings = load_source('settings', | ||||
|                            str(user_dir.joinpath('settings.py'))) | ||||
|     settings.__dict__.setdefault('rules', None) | ||||
|     settings.__dict__.setdefault('wait_command', 3) | ||||
|     settings.__dict__.setdefault('require_confirmation', False) | ||||
|     settings.__dict__.setdefault('no_colors', False) | ||||
|     return settings | ||||
|  | ||||
|  | ||||
| def is_rule_enabled(settings, rule): | ||||
|     """Returns `True` when rule mentioned in `rules` or `rules` | ||||
|     isn't defined. | ||||
|  | ||||
|     """ | ||||
|     return settings.rules is None or rule.name[:-3] in settings.rules | ||||
|  | ||||
|  | ||||
| def load_rule(rule): | ||||
|     """Imports rule module and returns it.""" | ||||
|     rule_module = load_source(rule.name[:-3], str(rule)) | ||||
|     return Rule(rule.name[:-3], rule_module.match, | ||||
|                 rule_module.get_new_command) | ||||
|     return types.Rule(rule.name[:-3], rule_module.match, | ||||
|                       rule_module.get_new_command, | ||||
|                       getattr(rule_module, 'enabled_by_default', True), | ||||
|                       getattr(rule_module, 'side_effect', None), | ||||
|                       getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY)) | ||||
|  | ||||
|  | ||||
| def _get_loaded_rules(rules, settings): | ||||
|     """Yields all available rules.""" | ||||
|     for rule in rules: | ||||
|         if rule.name != '__init__.py': | ||||
|             loaded_rule = load_rule(rule) | ||||
|             if loaded_rule in settings.rules: | ||||
|                 yield loaded_rule | ||||
|  | ||||
|  | ||||
| def get_rules(user_dir, settings): | ||||
| @@ -56,8 +45,9 @@ def get_rules(user_dir, settings): | ||||
|         .joinpath('rules') \ | ||||
|         .glob('*.py') | ||||
|     user = user_dir.joinpath('rules').glob('*.py') | ||||
|     return [load_rule(rule) for rule in sorted(list(bundled)) + list(user) | ||||
|             if rule.name != '__init__.py' and is_rule_enabled(settings, rule)] | ||||
|     rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings) | ||||
|     return sorted(rules, key=lambda rule: settings.priority.get( | ||||
|         rule.name, rule.priority)) | ||||
|  | ||||
|  | ||||
| def wait_output(settings, popen): | ||||
| @@ -80,7 +70,7 @@ def wait_output(settings, popen): | ||||
|  | ||||
| def get_command(settings, args): | ||||
|     """Creates command from `args` and executes it.""" | ||||
|     if sys.version_info[0] < 3: | ||||
|     if six.PY2: | ||||
|         script = ' '.join(arg.decode('utf-8') for arg in args[1:]) | ||||
|     else: | ||||
|         script = ' '.join(args[1:]) | ||||
| @@ -88,10 +78,11 @@ def get_command(settings, args): | ||||
|     if not script: | ||||
|         return | ||||
|  | ||||
|     script = shells.from_shell(script) | ||||
|     result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, | ||||
|                    env=dict(os.environ, LANG='C')) | ||||
|     if wait_output(settings, result): | ||||
|         return Command(script, result.stdout.read().decode('utf-8'), | ||||
|         return types.Command(script, result.stdout.read().decode('utf-8'), | ||||
|                              result.stderr.read().decode('utf-8')) | ||||
|  | ||||
|  | ||||
| @@ -105,13 +96,13 @@ def get_matched_rule(command, rules, settings): | ||||
|             logs.rule_failed(rule, sys.exc_info(), settings) | ||||
|  | ||||
|  | ||||
| def confirm(new_command, settings): | ||||
| def confirm(new_command, side_effect, settings): | ||||
|     """Returns `True` when running of new command confirmed.""" | ||||
|     if not settings.require_confirmation: | ||||
|         logs.show_command(new_command, settings) | ||||
|         logs.show_command(new_command, side_effect, settings) | ||||
|         return True | ||||
|  | ||||
|     logs.confirm_command(new_command, settings) | ||||
|     logs.confirm_command(new_command, side_effect, settings) | ||||
|     try: | ||||
|         sys.stdin.read(1) | ||||
|         return True | ||||
| @@ -122,27 +113,21 @@ def confirm(new_command, settings): | ||||
|  | ||||
| def run_rule(rule, command, settings): | ||||
|     """Runs command from rule for passed command.""" | ||||
|     new_command = rule.get_new_command(command, settings) | ||||
|     if confirm(new_command, settings): | ||||
|     new_command = shells.to_shell(rule.get_new_command(command, settings)) | ||||
|     if confirm(new_command, rule.side_effect, settings): | ||||
|         if rule.side_effect: | ||||
|             rule.side_effect(command, settings) | ||||
|         shells.put_to_history(new_command) | ||||
|         print(new_command) | ||||
|  | ||||
|  | ||||
| def is_second_run(command): | ||||
|     """Is it the second run of `fuck`?""" | ||||
|     return command.script.startswith('fuck') | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     colorama.init() | ||||
|     user_dir = setup_user_dir() | ||||
|     settings = get_settings(user_dir) | ||||
|     settings = conf.get_settings(user_dir) | ||||
|  | ||||
|     command = get_command(settings, sys.argv) | ||||
|     if command: | ||||
|         if is_second_run(command): | ||||
|             logs.failed("Can't fuck twice", settings) | ||||
|             return | ||||
|  | ||||
|         rules = get_rules(user_dir, settings) | ||||
|         matched_rule = get_matched_rule(command, rules, settings) | ||||
|         if matched_rule: | ||||
|   | ||||
							
								
								
									
										23
									
								
								thefuck/rules/apt_get.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								thefuck/rules/apt_get.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| try: | ||||
|     import CommandNotFound | ||||
| except ImportError: | ||||
|     enabled_by_default = False | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     if 'not found' in command.stderr: | ||||
|         try: | ||||
|             c = CommandNotFound.CommandNotFound() | ||||
|             pkgs = c.getPackages(command.script.split(" ")[0]) | ||||
|             name, _ = pkgs[0] | ||||
|             return True | ||||
|         except IndexError: | ||||
|             # IndexError is thrown when no matching package is found | ||||
|             return False | ||||
|  | ||||
|  | ||||
| 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) | ||||
							
								
								
									
										43
									
								
								thefuck/rules/brew_install.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								thefuck/rules/brew_install.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import difflib | ||||
| 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_formula_path = brew_path_prefix + '/Library/Formula' | ||||
|  | ||||
|     for file_name in os.listdir(brew_formula_path): | ||||
|         if file_name.endswith('.rb'): | ||||
|             brew_formulas.append(file_name.replace('.rb', '')) | ||||
| except: | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def _get_similar_formulars(formula_name): | ||||
|     return difflib.get_close_matches(formula_name, brew_formulas, 1, 0.85) | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     is_proper_command = ('brew install' in command.script and | ||||
|                          'No available formula' in command.stderr) | ||||
|  | ||||
|     has_possible_formulas = False | ||||
|     if is_proper_command: | ||||
|         formula = re.findall(r'Error: No available formula for ([a-z]+)', | ||||
|                              command.stderr)[0] | ||||
|         has_possible_formulas = len(_get_similar_formulars(formula)) > 0 | ||||
|  | ||||
|     return has_possible_formulas | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     not_exist_formula = re.findall(r'Error: No available formula for ([a-z]+)', | ||||
|                                    command.stderr)[0] | ||||
|     exist_formula = _get_similar_formulars(not_exist_formula)[0] | ||||
|  | ||||
|     return command.script.replace(not_exist_formula, exist_formula, 1) | ||||
							
								
								
									
										102
									
								
								thefuck/rules/brew_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								thefuck/rules/brew_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| import difflib | ||||
| import os | ||||
| import re | ||||
| import subprocess | ||||
|  | ||||
|  | ||||
| BREW_CMD_PATH = '/Library/Homebrew/cmd' | ||||
| TAP_PATH = '/Library/Taps' | ||||
| TAP_CMD_PATH = '/%s/%s/cmd' | ||||
|  | ||||
|  | ||||
| def _get_brew_path_prefix(): | ||||
|     """To get brew path""" | ||||
|     try: | ||||
|         return subprocess.check_output(['brew', '--prefix']).strip() | ||||
|     except: | ||||
|         return None | ||||
|  | ||||
|  | ||||
| def _get_brew_commands(brew_path_prefix): | ||||
|     """To get brew default commands on local environment""" | ||||
|     brew_cmd_path = brew_path_prefix + BREW_CMD_PATH | ||||
|  | ||||
|     commands = [name.replace('.rb', '') for name in os.listdir(brew_cmd_path) | ||||
|                 if name.endswith('.rb')] | ||||
|  | ||||
|     return commands | ||||
|  | ||||
|  | ||||
| def _get_brew_tap_specific_commands(brew_path_prefix): | ||||
|     """To get tap's specific commands | ||||
|     https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115""" | ||||
|     commands = [] | ||||
|     brew_taps_path = brew_path_prefix + TAP_PATH | ||||
|  | ||||
|     for user in _get_directory_names_only(brew_taps_path): | ||||
|         taps = _get_directory_names_only(brew_taps_path + '/%s' % user) | ||||
|  | ||||
|         # Brew Taps's naming rule | ||||
|         # https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/brew-tap.md#naming-conventions-and-limitations | ||||
|         taps = (tap for tap in taps if tap.startswith('homebrew-')) | ||||
|         for tap in taps: | ||||
|             tap_cmd_path = brew_taps_path + TAP_CMD_PATH % (user, tap) | ||||
|  | ||||
|             if os.path.isdir(tap_cmd_path): | ||||
|                 commands += (name.replace('brew-', '').replace('.rb', '') | ||||
|                              for name in os.listdir(tap_cmd_path) | ||||
|                              if _is_brew_tap_cmd_naming(name)) | ||||
|  | ||||
|     return commands | ||||
|  | ||||
|  | ||||
| def _is_brew_tap_cmd_naming(name): | ||||
|     if name.startswith('brew-') and name.endswith('.rb'): | ||||
|         return True | ||||
|  | ||||
|     return False | ||||
|  | ||||
|  | ||||
| def _get_directory_names_only(path): | ||||
|     return [d for d in os.listdir(path) | ||||
|             if os.path.isdir(os.path.join(path, d))] | ||||
|  | ||||
|  | ||||
| brew_path_prefix = _get_brew_path_prefix() | ||||
|  | ||||
| # Failback commands for testing (Based on Homebrew 0.9.5) | ||||
| brew_commands = ['info', 'home', 'options', 'install', 'uninstall', | ||||
|                  'search', 'list', 'update', 'upgrade', 'pin', 'unpin', | ||||
|                  'doctor', 'create', 'edit'] | ||||
|  | ||||
| if brew_path_prefix: | ||||
|     try: | ||||
|         brew_commands = _get_brew_commands(brew_path_prefix) \ | ||||
|                         + _get_brew_tap_specific_commands(brew_path_prefix) | ||||
|     except OSError: | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def _get_similar_commands(command): | ||||
|     return difflib.get_close_matches(command, brew_commands) | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     is_proper_command = ('brew' in command.script and | ||||
|                          'Unknown command' in command.stderr) | ||||
|  | ||||
|     has_possible_commands = False | ||||
|     if is_proper_command: | ||||
|         broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', | ||||
|                                 command.stderr)[0] | ||||
|         has_possible_commands = len(_get_similar_commands(broken_cmd)) > 0 | ||||
|  | ||||
|     return has_possible_commands | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', | ||||
|                             command.stderr)[0] | ||||
|     new_cmd = _get_similar_commands(broken_cmd)[0] | ||||
|  | ||||
|     return command.script.replace(broken_cmd, new_cmd, 1) | ||||
							
								
								
									
										14
									
								
								thefuck/rules/cd_mkdir.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								thefuck/rules/cd_mkdir.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return (command.script.startswith('cd ') | ||||
|         and ('no such file or directory' in command.stderr.lower() | ||||
|             or 'cd: can\'t cd to' in command.stderr.lower())) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub(r'^cd (.*)', 'mkdir -p \\1 && cd \\1', command.script) | ||||
							
								
								
									
										15
									
								
								thefuck/rules/composer_not_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/composer_not_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import re | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return ('composer' in command.script | ||||
|             and ('did you mean this?' in command.stderr.lower() | ||||
|                  or 'did you mean one of these?' in command.stderr.lower())) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     broken_cmd = re.findall(r"Command \"([^']*)\" is not defined", command.stderr)[0] | ||||
|     new_cmd = re.findall(r'Did you mean this\?[^\n]*\n\s*([^\n]*)', command.stderr) | ||||
|     if not new_cmd: | ||||
|         new_cmd = re.findall(r'Did you mean one of these\?[^\n]*\n\s*([^\n]*)', command.stderr) | ||||
|     return command.script.replace(broken_cmd, new_cmd[0].strip(), 1) | ||||
| @@ -1,10 +1,13 @@ | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return command.script.startswith('cp ') \ | ||||
|         and 'cp: omitting directory' in command.stderr.lower() | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub(r'^cp', 'cp -a', command.script) | ||||
|   | ||||
							
								
								
									
										9
									
								
								thefuck/rules/cpp11.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								thefuck/rules/cpp11.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| def match(command, settings): | ||||
|     return (('g++' in command.script or 'clang++' in command.script) and | ||||
|             ('This file requires compiler and library support for the ' | ||||
|              'ISO C++ 2011 standard.' in command.stderr or | ||||
|              '-Wc++11-extensions' in command.stderr)) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return command.script + ' -std=c++11' | ||||
							
								
								
									
										12
									
								
								thefuck/rules/dry.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								thefuck/rules/dry.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| def match(command, settings): | ||||
|     split_command = command.script.split() | ||||
|  | ||||
|     return len(split_command) >= 2 and split_command[0] == split_command[1] | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return command.script[command.script.find(' ')+1:] | ||||
|  | ||||
| # it should be rare enough to actually have to type twice the same word, so | ||||
| # this rule can have a higher priority to come before things like "cd cd foo" | ||||
| priority = 900 | ||||
							
								
								
									
										15
									
								
								thefuck/rules/fix_alt_space.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/fix_alt_space.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # -*- encoding: utf-8 -*- | ||||
|  | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return ('command not found' in command.stderr.lower() | ||||
|             and u' ' in command.script) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub(u' ', ' ', command.script) | ||||
							
								
								
									
										15
									
								
								thefuck/rules/git_add.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/git_add.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import re | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return ('git' in command.script | ||||
|             and 'did not match any file(s) known to git.' in command.stderr | ||||
|             and "Did you forget to 'git add'?" in command.stderr) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     missing_file = re.findall( | ||||
|             r"error: pathspec '([^']*)' " | ||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] | ||||
|  | ||||
|     return 'git add -- {} && {}'.format(missing_file, command.script) | ||||
							
								
								
									
										15
									
								
								thefuck/rules/git_checkout.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/git_checkout.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import re | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return ('git' in command.script | ||||
|             and 'did not match any file(s) known to git.' in command.stderr | ||||
|             and "Did you forget to 'git add'?" not in command.stderr) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     missing_file = re.findall( | ||||
|             r"error: pathspec '([^']*)' " | ||||
|             "did not match any file\(s\) known to git.", command.stderr)[0] | ||||
|  | ||||
|     return 'git branch {} && {}'.format(missing_file, command.script) | ||||
| @@ -1,11 +1,14 @@ | ||||
| import os | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return os.path.exists(command.script.split()[0]) \ | ||||
|         and 'command not found' in command.stderr | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return u'./{}'.format(command.script) | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return (command.script.startswith('lein') | ||||
|             and "is not a task. See 'lein help'" in command.stderr | ||||
|             and 'Did you mean this?' in command.stderr) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     broken_cmd = re.findall(r"'([^']*)' is not a task", | ||||
|                             command.stderr)[0] | ||||
|   | ||||
							
								
								
									
										11
									
								
								thefuck/rules/ls_lah.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								thefuck/rules/ls_lah.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| enabled_by_default = False | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return 'ls' in command.script and not ('ls -' in command.script) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     command = command.script.split(' ') | ||||
|     command[0] = 'ls -lah' | ||||
|     return ' '.join(command) | ||||
							
								
								
									
										9
									
								
								thefuck/rules/man_no_space.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								thefuck/rules/man_no_space.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| def match(command, settings): | ||||
|     return (command.script.startswith(u'man') | ||||
|             and u'command not found' in command.stderr.lower()) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return u'man {}'.format(command.script[3:]) | ||||
|  | ||||
| priority = 2000 | ||||
| @@ -1,9 +1,13 @@ | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return ('mkdir' in command.script | ||||
|             and 'No such file or directory' in command.stderr) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub('^mkdir (.*)', 'mkdir -p \\1', command.script) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| from difflib import get_close_matches | ||||
| import os | ||||
| from pathlib import Path | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| def _safe(fn, fallback): | ||||
| @@ -17,14 +18,19 @@ def _get_all_bins(): | ||||
|             if not _safe(exe.is_dir, True)] | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return 'not found' in command.stderr and \ | ||||
|            bool(get_close_matches(command.script.split(' ')[0], | ||||
|                                   _get_all_bins())) | ||||
|  | ||||
|  | ||||
| @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] | ||||
|     return ' '.join([new_command] + command.script.split(' ')[1:]) | ||||
|  | ||||
|  | ||||
| priority = 3000 | ||||
|   | ||||
							
								
								
									
										43
									
								
								thefuck/rules/pacman.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								thefuck/rules/pacman.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| 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 | ||||
|  | ||||
|  | ||||
| def __get_pkgfile(command): | ||||
|     try: | ||||
|         return subprocess.check_output( | ||||
|             ['pkgfile', '-b', '-v', command.script.split(" ")[0]], | ||||
|             universal_newlines=True, stderr=subprocess.DEVNULL | ||||
|         ).split() | ||||
|     except subprocess.CalledProcessError: | ||||
|         return None | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return 'not found' in command.stderr and __get_pkgfile(command) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     package = __get_pkgfile(command)[0] | ||||
|  | ||||
|     return '{} -S {} && {}'.format(pacman, package, command.script) | ||||
|  | ||||
|  | ||||
| if not __command_available('pkgfile'): | ||||
|     enabled_by_default = False | ||||
| elif __command_available('yaourt'): | ||||
|     pacman = 'yaourt' | ||||
| elif __command_available('pacman'): | ||||
|     pacman = 'sudo pacman' | ||||
| else: | ||||
|     enabled_by_default = False | ||||
							
								
								
									
										15
									
								
								thefuck/rules/pip_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								thefuck/rules/pip_unknown_command.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import re | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return ('pip' in command.script and | ||||
|             'unknown command' in command.stderr and | ||||
|             'maybe you meant' in command.stderr) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     broken_cmd = re.findall(r'ERROR: unknown command \"([a-z]+)\"', | ||||
|                             command.stderr)[0] | ||||
|     new_cmd = re.findall(r'maybe you meant \"([a-z]+)\"', command.stderr)[0] | ||||
|  | ||||
|     return command.script.replace(broken_cmd, new_cmd, 1) | ||||
| @@ -1,7 +1,10 @@ | ||||
| from thefuck.utils import sudo_support | ||||
| # add 'python' suffix to the command if | ||||
| #  1) The script does not have execute permission or | ||||
| #  2) is interpreted as shell script | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     toks = command.script.split() | ||||
|     return (len(toks) > 0 | ||||
| @@ -10,5 +13,6 @@ def match(command, settings): | ||||
|                  'command not found' in command.stderr)) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return 'python ' + command.script | ||||
|   | ||||
| @@ -1,9 +1,13 @@ | ||||
| import re | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return ('rm' in command.script | ||||
|             and 'is a directory' in command.stderr) | ||||
|             and 'is a directory' in command.stderr.lower()) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return re.sub('^rm (.*)', 'rm -rf \\1', command.script) | ||||
|   | ||||
							
								
								
									
										16
									
								
								thefuck/rules/rm_root.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								thefuck/rules/rm_root.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| from thefuck.utils import sudo_support | ||||
|  | ||||
|  | ||||
| enabled_by_default = False | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def match(command, settings): | ||||
|     return ({'rm', '/'}.issubset(command.script.split()) | ||||
|             and '--no-preserve-root' not in command.script | ||||
|             and '--no-preserve-root' in command.stderr) | ||||
|  | ||||
|  | ||||
| @sudo_support | ||||
| def get_new_command(command, settings): | ||||
|     return u'{} --no-preserve-root'.format(command.script) | ||||
							
								
								
									
										14
									
								
								thefuck/rules/sl_ls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								thefuck/rules/sl_ls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| """ | ||||
| This happens way too often | ||||
|  | ||||
| When typing really fast cause I'm a 1337 H4X0R, | ||||
| I often fuck up 'ls' and type 'sl'. No more! | ||||
| """ | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     return command.script == 'sl' | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     return 'ls' | ||||
| @@ -22,7 +22,11 @@ def match(command, settings): | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def remove_offending_keys(command, settings): | ||||
| def get_new_command(command, settings): | ||||
|     return command.script | ||||
|  | ||||
|  | ||||
| def side_effect(command, settings): | ||||
|     offending = offending_pattern.findall(command.stderr) | ||||
|     for filepath, lineno in offending: | ||||
|         with open(filepath, 'r') as fh: | ||||
| @@ -30,8 +34,3 @@ def remove_offending_keys(command, settings): | ||||
|             del lines[int(lineno) - 1] | ||||
|         with open(filepath, 'w') as fh: | ||||
|             fh.writelines(lines) | ||||
|  | ||||
|  | ||||
| def get_new_command(command, settings): | ||||
|     remove_offending_keys(command, settings) | ||||
|     return command.script | ||||
|   | ||||
| @@ -6,12 +6,18 @@ patterns = ['permission denied', | ||||
|             'Operation not permitted', | ||||
|             'root privilege', | ||||
|             'This command has to be run under the root user.', | ||||
|             'You need to be root to perform this command.'] | ||||
|             'This operation requires root.', | ||||
|             'You need to be root to perform this command.', | ||||
|             'requested operation requires superuser privilege', | ||||
|             'must be run as root', | ||||
|             'must be superuser', | ||||
|             'Need to be root'] | ||||
|  | ||||
|  | ||||
| def match(command, settings): | ||||
|     for pattern in patterns: | ||||
|         if pattern.lower() in command.stderr.lower(): | ||||
|         if pattern.lower() in command.stderr.lower()\ | ||||
|                 or pattern.lower() in command.stdout.lower(): | ||||
|             return True | ||||
|     return False | ||||
|  | ||||
|   | ||||
							
								
								
									
										118
									
								
								thefuck/shells.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								thefuck/shells.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| """Module with shell specific actions, each shell class should | ||||
| implement `from_shell`, `to_shell`, `app_alias` and `put_to_history` | ||||
| methods. | ||||
|  | ||||
| """ | ||||
| from collections import defaultdict | ||||
| from subprocess import Popen, PIPE | ||||
| from time import time | ||||
| import os | ||||
| from psutil import Process | ||||
| from .utils import DEVNULL | ||||
|  | ||||
|  | ||||
| class Generic(object): | ||||
|     def _get_aliases(self): | ||||
|         return {} | ||||
|  | ||||
|     def _expand_aliases(self, command_script): | ||||
|         aliases = self._get_aliases() | ||||
|         binary = command_script.split(' ')[0] | ||||
|         if binary in aliases: | ||||
|             return command_script.replace(binary, aliases[binary], 1) | ||||
|         else: | ||||
|             return command_script | ||||
|  | ||||
|     def from_shell(self, command_script): | ||||
|         """Prepares command before running in app.""" | ||||
|         return self._expand_aliases(command_script) | ||||
|  | ||||
|     def to_shell(self, command_script): | ||||
|         """Prepares command for running in shell.""" | ||||
|         return command_script | ||||
|  | ||||
|     def app_alias(self): | ||||
|         return "\nalias fuck='eval $(thefuck $(fc -ln -1))'\n" | ||||
|  | ||||
|     def _get_history_file_name(self): | ||||
|         return '' | ||||
|  | ||||
|     def _get_history_line(self, command_script): | ||||
|         return '' | ||||
|  | ||||
|     def put_to_history(self, command_script): | ||||
|         """Puts command script to shell history.""" | ||||
|         history_file_name = self._get_history_file_name() | ||||
|         if os.path.isfile(history_file_name): | ||||
|             with open(history_file_name, 'a') as history: | ||||
|                 history.write(self._get_history_line(command_script)) | ||||
|  | ||||
|  | ||||
| class Bash(Generic): | ||||
|     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) | ||||
|         return dict( | ||||
|             self._parse_alias(alias) | ||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||
|             if alias and '=' in alias) | ||||
|  | ||||
|     def _get_history_file_name(self): | ||||
|         return os.environ.get("HISTFILE", | ||||
|                               os.path.expanduser('~/.bash_history')) | ||||
|  | ||||
|     def _get_history_line(self, command_script): | ||||
|         return u'{}\n'.format(command_script) | ||||
|  | ||||
|  | ||||
| class Zsh(Generic): | ||||
|     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) | ||||
|         return dict( | ||||
|             self._parse_alias(alias) | ||||
|             for alias in proc.stdout.read().decode('utf-8').split('\n') | ||||
|             if alias and '=' in alias) | ||||
|  | ||||
|     def _get_history_file_name(self): | ||||
|         return os.environ.get("HISTFILE", | ||||
|                               os.path.expanduser('~/.zsh_history')) | ||||
|  | ||||
|     def _get_history_line(self, command_script): | ||||
|         return u': {}:0;{}\n'.format(int(time()), command_script) | ||||
|  | ||||
|  | ||||
| shells = defaultdict(lambda: Generic(), { | ||||
|     'bash': Bash(), | ||||
|     'zsh': Zsh()}) | ||||
|  | ||||
|  | ||||
| def _get_shell(): | ||||
|     shell = Process(os.getpid()).parent().cmdline()[0] | ||||
|     return shells[shell] | ||||
|  | ||||
|  | ||||
| def from_shell(command): | ||||
|     return _get_shell().from_shell(command) | ||||
|  | ||||
|  | ||||
| def to_shell(command): | ||||
|     return _get_shell().to_shell(command) | ||||
|  | ||||
|  | ||||
| def app_alias(): | ||||
|     return _get_shell().app_alias() | ||||
|  | ||||
|  | ||||
| def put_to_history(command): | ||||
|     return _get_shell().put_to_history(command) | ||||
							
								
								
									
										27
									
								
								thefuck/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								thefuck/types.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| Command = namedtuple('Command', ('script', 'stdout', 'stderr')) | ||||
|  | ||||
| Rule = namedtuple('Rule', ('name', 'match', 'get_new_command', | ||||
|                            'enabled_by_default', 'side_effect', | ||||
|                            'priority')) | ||||
|  | ||||
|  | ||||
| class RulesNamesList(list): | ||||
|     """Wrapper a top of list for storing rules names.""" | ||||
|  | ||||
|     def __contains__(self, item): | ||||
|         return super(RulesNamesList, self).__contains__(item.name) | ||||
|  | ||||
|  | ||||
| class Settings(dict): | ||||
|  | ||||
|     def __getattr__(self, item): | ||||
|         return self.get(item) | ||||
|  | ||||
|     def update(self, **kwargs): | ||||
|         """Returns new settings with new values from `kwargs`.""" | ||||
|         conf = dict(self) | ||||
|         conf.update(kwargs) | ||||
|         return Settings(conf) | ||||
| @@ -1,5 +1,10 @@ | ||||
| from functools import wraps | ||||
| import os | ||||
| import six | ||||
| from .types import Command | ||||
|  | ||||
|  | ||||
| DEVNULL = open(os.devnull, 'w') | ||||
|  | ||||
|  | ||||
| def which(program): | ||||
| @@ -35,9 +40,25 @@ def wrap_settings(params): | ||||
|     def decorator(fn): | ||||
|         @wraps(fn) | ||||
|         def wrapper(command, settings): | ||||
|             for key, val in params.items(): | ||||
|                 if not hasattr(settings, key): | ||||
|                     setattr(settings, key, val) | ||||
|             return fn(command, settings) | ||||
|             return fn(command, settings.update(**params)) | ||||
|         return wrapper | ||||
|     return decorator | ||||
|  | ||||
|  | ||||
| def sudo_support(fn): | ||||
|     """Removes sudo before calling fn and adds it after.""" | ||||
|     @wraps(fn) | ||||
|     def wrapper(command, settings): | ||||
|         if not command.script.startswith('sudo '): | ||||
|             return fn(command, settings) | ||||
|  | ||||
|         result = fn(Command(command.script[5:], | ||||
|                             command.stdout, | ||||
|                             command.stderr), | ||||
|                     settings) | ||||
|  | ||||
|         if result and isinstance(result, six.string_types): | ||||
|             return u'sudo {}'.format(result) | ||||
|         else: | ||||
|             return result | ||||
|     return wrapper | ||||
|   | ||||
		Reference in New Issue
	
	Block a user