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) | # The Fuck [](https://travis-ci.org/nvbn/thefuck) | ||||||
|  |  | ||||||
|  | **Aliases changed in 1.34.** | ||||||
|  |  | ||||||
| Magnificent app which corrects your previous console command, | Magnificent app which corrects your previous console command, | ||||||
| inspired by a [@liamosaur](https://twitter.com/liamosaur/) | inspired by a [@liamosaur](https://twitter.com/liamosaur/) | ||||||
| [tweet](https://twitter.com/liamosaur/status/506975850596536320). | [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: | [settings](#settings) option: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| @@ -88,8 +90,8 @@ Reading package lists... Done | |||||||
|  |  | ||||||
| ## Requirements | ## Requirements | ||||||
|  |  | ||||||
|  | - python (2.7+ or 3.3+) | ||||||
| - pip | - pip | ||||||
| - python |  | ||||||
| - python-dev | - python-dev | ||||||
|  |  | ||||||
| ## Installation | ## Installation | ||||||
| @@ -100,40 +102,30 @@ Install `The Fuck` with `pip`: | |||||||
| sudo pip install thefuck | 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 | ```bash | ||||||
| sudo easy_install thefuck | alias fuck='eval $(thefuck $(fc -ln -1)); history -r' | ||||||
| ``` | # You can use whatever you want as an alias, like for Mondays: | ||||||
|  |  | ||||||
| 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='fuck' | alias FUCK='fuck' | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Or in `config.fish`: | Or in your `.zshrc`: | ||||||
|  |  | ||||||
| ```fish | ```bash | ||||||
| function fuck | alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R' | ||||||
|     eval (thefuck $history[1]) |  | ||||||
| end |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Or in your Powershell `$PROFILE` on Windows: | Alternatively, you can redirect the output of `thefuck-alias`: | ||||||
|  |  | ||||||
| ```powershell | ```bash | ||||||
| function fuck {  | thefuck-alias >> ~/.bashrc | ||||||
|     $fuck = $(thefuck (get-history -count 1).commandline) |  | ||||||
|     if($fuck.startswith("echo")) {  |  | ||||||
|         $fuck.substring(5)  |  | ||||||
|     }  |  | ||||||
|     else { iex "$fuck" }  |  | ||||||
| } |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | [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. | Changes will be available only in a new shell session. | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -145,21 +137,41 @@ sudo pip install thefuck --upgrade | |||||||
|  |  | ||||||
| ## How it works | ## How it works | ||||||
|  |  | ||||||
| The Fuck tries to match rule for the previous command, create new command | The Fuck tries to match a rule for the previous command, creates a new command | ||||||
| using matched rule and run it. Rules enabled by default: | 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_parent` – changes `cd..` to `cd ..`; | ||||||
|  | * `cd_mkdir` – creates directories before cd'ing into them; | ||||||
| * `cp_omitting_directory` – adds `-a` when you `cp` directory; | * `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_no_command` – fixes wrong git commands like `git brnch`; | ||||||
| * `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`; | * `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`; | ||||||
| * `has_exists_script` – prepends `./` when script/binary exists; | * `has_exists_script` – prepends `./` when script/binary exists; | ||||||
| * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; | * `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`; | ||||||
| * `mkdir_p` – adds `-p` when you trying to create directory without parent; | * `mkdir_p` – adds `-p` when you trying to create directory without parent; | ||||||
| * `no_command` – fixes wrong console commands, for example `vom/vim`; | * `no_command` – fixes wrong console commands, for example `vom/vim`; | ||||||
|  | * `man_no_space` – fixes man commands without spaces, for example `mandiff`; | ||||||
|  | * `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; | * `python_command` – prepends `python` when you trying to run not executable/without `./` python script; | ||||||
|  | * `sl_ls` – changes `sl` to `ls`; | ||||||
| * `rm_dir` – adds `-rf` when you trying to remove directory; | * `rm_dir` – adds `-rf` when you trying to remove directory; | ||||||
|  | * `ssh_known_hosts` – removes host from `known_hosts` on warning; | ||||||
| * `sudo` – prepends `sudo` to previous command if it failed because of permissions; | * `sudo` – prepends `sudo` to previous command if it failed because of permissions; | ||||||
| * `switch_layout` – switches command from your local layout to en. | * `switch_layout` – switches command from your local layout to en; | ||||||
|  | * `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 | ## 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: | in `~/.thefuck/rules`. Rule should contain two functions: | ||||||
| `match(command: Command, settings: Settings) -> bool` | `match(command: Command, settings: Settings) -> bool` | ||||||
| and `get_new_command(command: Command, settings: Settings) -> str`. | 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`. | `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`: | Simple example of the rule for running script with `sudo`: | ||||||
|  |  | ||||||
| @@ -182,6 +197,14 @@ def match(command, settings): | |||||||
|  |  | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     return 'sudo {}'.format(command.script) |     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), | [More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules), | ||||||
| @@ -189,12 +212,42 @@ def get_new_command(command, settings): | |||||||
|  |  | ||||||
| ## 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; | * `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`; | ||||||
| * `require_confirmation` – require confirmation before running new command, by default `False`;  | * `require_confirmation` – requires confirmation before running new command, by default `False`; | ||||||
| * `wait_command` – max amount of time in seconds for getting previous command output; | * `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 | ## Developing | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,4 +28,4 @@ call('git commit -am "Bump to {}"'.format(version), shell=True) | |||||||
| call('git tag {}'.format(version), shell=True) | call('git tag {}'.format(version), shell=True) | ||||||
| call('git push', shell=True) | call('git push', shell=True) | ||||||
| call('git push --tags', 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 | pytest | ||||||
| mock | mock | ||||||
|  | pytest-mock | ||||||
|  | wheel | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||||
|  |  | ||||||
|  |  | ||||||
| VERSION = '1.26' | VERSION = '1.39' | ||||||
|  |  | ||||||
|  |  | ||||||
| setup(name='thefuck', | setup(name='thefuck', | ||||||
| @@ -15,6 +15,7 @@ setup(name='thefuck', | |||||||
|                                       'tests', 'release']), |                                       'tests', 'release']), | ||||||
|       include_package_data=True, |       include_package_data=True, | ||||||
|       zip_safe=False, |       zip_safe=False, | ||||||
|       install_requires=['pathlib', 'psutil', 'colorama'], |       install_requires=['pathlib', 'psutil', 'colorama', 'six'], | ||||||
|       entry_points={'console_scripts': [ |       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 thefuck.rules.cd_parent import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | def test_match(): | ||||||
|     assert match(Command('cd..', '', 'cd..: command not found'), None) |     assert match(Command('cd..', stderr='cd..: command not found'), None) | ||||||
|     assert not match(Command('', '', ''), None) |     assert not match(Command(), None) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(): | def test_get_new_command(): | ||||||
|     assert 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 | import pytest | ||||||
| from thefuck.main import Command |  | ||||||
| from thefuck.rules.git_not_command import match, get_new_command | from thefuck.rules.git_not_command import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| @@ -31,14 +31,14 @@ def git_command(): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(git_not_command, git_command, git_not_command_one_of_this): | 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 brnch', stderr=git_not_command), None) | ||||||
|     assert match(Command('git st', '', git_not_command_one_of_this), None) |     assert match(Command('git st', stderr=git_not_command_one_of_this), None) | ||||||
|     assert not match(Command('ls brnch', '', git_not_command), None) |     assert not match(Command('ls brnch', stderr=git_not_command), None) | ||||||
|     assert not match(Command('git branch', '', git_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): | 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' |         == 'git branch' | ||||||
|     assert get_new_command( |     assert get_new_command(Command('git st', stderr=git_not_command_one_of_this), | ||||||
|         Command('git st', '', git_not_command_one_of_this), None) == 'git status' |                            None) == 'git status' | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import pytest | import pytest | ||||||
| from thefuck.main import Command |  | ||||||
| from thefuck.rules.git_push import match, get_new_command | from thefuck.rules.git_push import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| @@ -14,11 +14,11 @@ To push the current branch and set the remote as upstream, use | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(stderr): | def test_match(stderr): | ||||||
|     assert match(Command('git push master', '', stderr), None) |     assert match(Command('git push master', stderr=stderr), None) | ||||||
|     assert not match(Command('git push master', '', ''), None) |     assert not match(Command('git push master'), None) | ||||||
|     assert not match(Command('ls', '', stderr), None) |     assert not match(Command('ls', stderr=stderr), None) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(stderr): | 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" |         == "git push --set-upstream origin master" | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| from mock import Mock, patch | from mock import Mock, patch | ||||||
| from thefuck.rules. has_exists_script import match, get_new_command | from thefuck.rules.has_exists_script import match, get_new_command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | def test_match(): | ||||||
|   | |||||||
							
								
								
									
										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 thefuck.rules.mkdir_p import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | def test_match(): | ||||||
|     assert match(Command('mkdir foo/bar/baz', '', 'mkdir: foo/bar: No such file or directory'), None) |     assert match(Command('mkdir foo/bar/baz', | ||||||
|     assert not match(Command('mkdir foo/bar/baz', '', ''), None) |                          stderr='mkdir: foo/bar: No such file or directory'), | ||||||
|     assert not match(Command('mkdir foo/bar/baz', '', 'foo bar baz'), None) |                  None) | ||||||
|     assert not match(Command('', '', ''), 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(): | 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 thefuck.rules.python_command import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | def test_match(): | ||||||
|     assert match(Command('temp.py', '', 'Permission denied'), None) |     assert match(Command('temp.py', stderr='Permission denied'), None) | ||||||
|     assert not match(Command('', '', ''), None) |     assert not match(Command(), None) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(): | 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 thefuck.rules.rm_dir import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | @pytest.mark.parametrize('command', [ | ||||||
|     assert match(Command('rm foo', '', 'rm: foo: is a directory'), None) |     Command('rm foo', stderr='rm: foo: is a directory'), | ||||||
|     assert not match(Command('rm foo', '', ''), None) |     Command('rm foo', stderr='rm: foo: Is a directory')]) | ||||||
|     assert not match(Command('rm foo', '', 'foo bar baz'), None) | def test_match(command): | ||||||
|     assert not match(Command('', '', ''), None) |     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(): | 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 os | ||||||
| import pytest | import pytest | ||||||
| from mock import Mock | from mock import Mock | ||||||
| from thefuck.main import Command | from thefuck.rules.ssh_known_hosts import match, get_new_command,\ | ||||||
| from thefuck.rules.ssh_known_hosts import match, get_new_command, remove_offending_keys |     side_effect | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| @@ -43,27 +44,23 @@ Host key verification failed.""".format(path, '98.765.432.321') | |||||||
|  |  | ||||||
| def test_match(ssh_error): | def test_match(ssh_error): | ||||||
|     errormsg, _, _, _ = ssh_error |     errormsg, _, _, _ = ssh_error | ||||||
|     assert match(Command('ssh', '', errormsg), None) |     assert match(Command('ssh', stderr=errormsg), None) | ||||||
|     assert match(Command('ssh', '', errormsg), None) |     assert match(Command('ssh', stderr=errormsg), None) | ||||||
|     assert match(Command('scp something something', '', errormsg), None) |     assert match(Command('scp something something', stderr=errormsg), None) | ||||||
|     assert match(Command('scp something something', '', errormsg), None) |     assert match(Command('scp something something', stderr=errormsg), None) | ||||||
|     assert not match(Command('', '', errormsg), None) |     assert not match(Command(stderr=errormsg), None) | ||||||
|     assert not match(Command('notssh', '', errormsg), None) |     assert not match(Command('notssh', stderr=errormsg), None) | ||||||
|     assert not match(Command('ssh', '', ''), 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 |     errormsg, path, reset, known_hosts = ssh_error | ||||||
|     command = Command('ssh user@host', '', errormsg) |     command = Command('ssh user@host', stderr=errormsg) | ||||||
|     remove_offending_keys(command, None) |     side_effect(command, None) | ||||||
|     expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n'] |     expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n'] | ||||||
|     assert known_hosts(path) == expected |     assert known_hosts(path) == expected | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(ssh_error, monkeypatch): | def test_get_new_command(ssh_error, monkeypatch): | ||||||
|     errormsg, _, _, _ = ssh_error |     errormsg, _, _, _ = ssh_error | ||||||
|  |     assert get_new_command(Command('ssh user@host', stderr=errormsg), None) == 'ssh user@host' | ||||||
|     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 |  | ||||||
|   | |||||||
| @@ -1,13 +1,21 @@ | |||||||
| from thefuck.main import Command | import pytest | ||||||
| from thefuck.rules.sudo import match, get_new_command | from thefuck.rules.sudo import match, get_new_command | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | @pytest.mark.parametrize('stderr, stdout', [ | ||||||
|     assert match(Command('', '', 'Permission denied'), None) |     ('Permission denied', ''), | ||||||
|     assert match(Command('', '', 'permission denied'), None) |     ('permission denied', ''), | ||||||
|     assert match(Command('', '', "npm ERR! Error: EACCES, unlink"), None) |     ("npm ERR! Error: EACCES, unlink", ''), | ||||||
|     assert not match(Command('', '', ''), None) |     ('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(): | 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 -*- | # -*- encoding: utf-8 -*- | ||||||
|  |  | ||||||
| from mock import Mock | import pytest | ||||||
| from thefuck.rules import switch_lang | from thefuck.rules import switch_lang | ||||||
|  | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_match(): | @pytest.mark.parametrize('command', [ | ||||||
|     assert switch_lang.match(Mock(stderr='command not found: фзе-пуе', |     Command(stderr='command not found: фзе-пуе', script=u'фзе-пуе'), | ||||||
|                                   script=u'фзе-пуе'), None) |     Command(stderr='command not found: λσ', script=u'λσ')]) | ||||||
|     assert switch_lang.match(Mock(stderr='command not found: λσ', | def test_match(command): | ||||||
|                                   script=u'λσ'), None) |     assert switch_lang.match(command, 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) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_new_command(): | @pytest.mark.parametrize('command', [ | ||||||
|     assert switch_lang.get_new_command( |     Command(stderr='command not found: pat-get', script=u'pat-get'), | ||||||
|         Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim' |     Command(stderr='command not found: ls', script=u'ls'), | ||||||
|     assert switch_lang.get_new_command( |     Command(stderr='some info', script=u'фзе-пуе')]) | ||||||
|         Mock(script=u'λσ -λα'), None) == 'ls -la' | 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 subprocess import PIPE | ||||||
| from pathlib import PosixPath, Path | from pathlib import PosixPath, Path | ||||||
| from mock import patch, Mock | from mock import Mock | ||||||
| from thefuck import main | from thefuck import main, conf, types | ||||||
|  | from tests.utils import Rule, Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_settings(): | def test_load_rule(mocker): | ||||||
|     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(): |  | ||||||
|     match = object() |     match = object() | ||||||
|     get_new_command = object() |     get_new_command = object() | ||||||
|     with patch('thefuck.main.load_source', |     load_source = mocker.patch( | ||||||
|                return_value=Mock( |         'thefuck.main.load_source', | ||||||
|                    match=match, |         return_value=Mock(match=match, | ||||||
|                    get_new_command=get_new_command)) as load_source: |                           get_new_command=get_new_command, | ||||||
|         assert main.load_rule(Path('/rules/bash.py')) == main.Rule('bash', match, get_new_command) |                           enabled_by_default=True, | ||||||
|         load_source.assert_called_once_with('bash', '/rules/bash.py') |                           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(): | class TestGetRules(object): | ||||||
|     with patch('thefuck.main.Path.glob') as glob, \ |     @pytest.fixture(autouse=True) | ||||||
|             patch('thefuck.main.load_source', |     def glob(self, mocker): | ||||||
|                   lambda x, _: Mock(match=x, get_new_command=x)): |         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')] |         glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] | ||||||
|         assert main.get_rules( |         monkeypatch.setattr('thefuck.main.load_source', | ||||||
|             Path('~'), |                             lambda x, _: Rule(x)) | ||||||
|             Mock(rules=None)) == [main.Rule('bash', 'bash', 'bash'), |         assert self._compare_names( | ||||||
|                                   main.Rule('lisp', 'lisp', 'lisp'), |             main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})), | ||||||
|                                   main.Rule('bash', 'bash', 'bash'), |             rules) | ||||||
|                                   main.Rule('lisp', 'lisp', 'lisp')] |  | ||||||
|         assert main.get_rules( |     @pytest.mark.parametrize('priority, unordered, ordered', [ | ||||||
|             Path('~'), |         ({}, | ||||||
|             Mock(rules=['bash'])) == [main.Rule('bash', 'bash', 'bash'), |          [Rule('bash', priority=100), Rule('python', priority=5)], | ||||||
|                                       main.Rule('bash', 'bash', 'bash')] |          ['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(): | class TestGetCommand(object): | ||||||
|     with patch('thefuck.main.Popen') as Popen, \ |     @pytest.fixture(autouse=True) | ||||||
|             patch('thefuck.main.os.environ', |     def Popen(self, monkeypatch): | ||||||
|                   new_callable=lambda: {}), \ |         Popen = Mock() | ||||||
|             patch('thefuck.main.wait_output', |  | ||||||
|                   return_value=True): |  | ||||||
|         Popen.return_value.stdout.read.return_value = b'stdout' |         Popen.return_value.stdout.read.return_value = b'stdout' | ||||||
|         Popen.return_value.stderr.read.return_value = b'stderr' |         Popen.return_value.stderr.read.return_value = b'stderr' | ||||||
|         assert main.get_command(Mock(), ['thefuck', 'apt-get', |         monkeypatch.setattr('thefuck.main.Popen', Popen) | ||||||
|                                          'search', 'vim']) \ |         return Popen | ||||||
|                == main.Command('apt-get search vim', 'stdout', 'stderr') |  | ||||||
|  |     @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', |         Popen.assert_called_once_with('apt-get search vim', | ||||||
|                                       shell=True, |                                       shell=True, | ||||||
|                                       stdout=PIPE, |                                       stdout=PIPE, | ||||||
|                                       stderr=PIPE, |                                       stderr=PIPE, | ||||||
|                                       env={'LANG': 'C'}) |                                       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): | class TestGetMatchedRule(object): | ||||||
|     rules = [main.Rule('', lambda x, _: x.script == 'cd ..', None), |     def test_no_match(self): | ||||||
|              main.Rule('', lambda *_: False, None), |         assert main.get_matched_rule( | ||||||
|              main.Rule('rule', Mock(side_effect=OSError('Denied')), None)] |             Command('ls'), [Rule('', lambda *_: False)], | ||||||
|     assert main.get_matched_rule(main.Command('ls', '', ''), |             Mock(no_colors=True)) is None | ||||||
|                                  rules, Mock(no_colors=True)) is None |  | ||||||
|     assert main.get_matched_rule(main.Command('cd ..', '', ''), |     def test_match(self): | ||||||
|                                  rules, Mock(no_colors=True)) == rules[0] |         rule = Rule('', lambda x, _: x.script == 'cd ..') | ||||||
|     assert capsys.readouterr()[1].split('\n')[0]\ |         assert main.get_matched_rule( | ||||||
|            == '[WARN] Rule 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): | class TestRunRule(object): | ||||||
|     with patch('thefuck.main.confirm', return_value=True): |     @pytest.fixture(autouse=True) | ||||||
|         main.run_rule(main.Rule('', None, lambda *_: 'new-command'), |     def confirm(self, mocker): | ||||||
|                       None, None) |         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', '') |         assert capsys.readouterr() == ('new-command\n', '') | ||||||
|     with patch('thefuck.main.confirm', return_value=False): |  | ||||||
|         main.run_rule(main.Rule('', None, lambda *_: 'new-command'), |     def test_run_rule_with_side_effect(self, capsys): | ||||||
|                       None, None) |         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() == ('', '') |         assert capsys.readouterr() == ('', '') | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_confirm(capsys): | class TestConfirm(object): | ||||||
|     # When confirmation not required: |     @pytest.fixture | ||||||
|     assert main.confirm('command', Mock(require_confirmation=False)) |     def stdin(self, mocker): | ||||||
|     assert capsys.readouterr() == ('', 'command\n') |         return mocker.patch('sys.stdin.read', return_value='\n') | ||||||
|     # When confirmation required and confirmed: |  | ||||||
|     with patch('thefuck.main.sys.stdin.read', return_value='\n'): |     def test_when_not_required(self, capsys): | ||||||
|         assert main.confirm('command', Mock(require_confirmation=True, |         assert main.confirm('command', None, Mock(require_confirmation=False)) | ||||||
|                                             no_colors=True)) |         assert capsys.readouterr() == ('', 'command\n') | ||||||
|  |  | ||||||
|  |     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]') |         assert capsys.readouterr() == ('', 'command [enter/ctrl+c]') | ||||||
|     # When confirmation required and ctrl+c: |  | ||||||
|     with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt): |     # `stdin` fixture should be applied after `capsys` | ||||||
|         assert not main.confirm('command', Mock(require_confirmation=True, |     def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin): | ||||||
|                                                 no_colors=True)) |         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') |         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_ |         return color_ | ||||||
|  |  | ||||||
|  |  | ||||||
| def rule_failed(rule, exc_info, settings): | def exception(title, exc_info, settings): | ||||||
|     sys.stderr.write( |     sys.stderr.write( | ||||||
|         u'{warn}[WARN] Rule {name}:{reset}\n{trace}' |         u'{warn}[WARN] {title}:{reset}\n{trace}' | ||||||
|         u'{warn}----------------------------{reset}\n\n'.format( |         u'{warn}----------------------------{reset}\n\n'.format( | ||||||
|             warn=color(colorama.Back.RED + colorama.Fore.WHITE |             warn=color(colorama.Back.RED + colorama.Fore.WHITE | ||||||
|                        + colorama.Style.BRIGHT, settings), |                        + colorama.Style.BRIGHT, settings), | ||||||
|             reset=color(colorama.Style.RESET_ALL, settings), |             reset=color(colorama.Style.RESET_ALL, settings), | ||||||
|             name=rule.name, |             title=title, | ||||||
|             trace=''.join(format_exception(*exc_info)))) |             trace=''.join(format_exception(*exc_info)))) | ||||||
|  |  | ||||||
|  |  | ||||||
| def show_command(new_command, settings): | def rule_failed(rule, exc_info, settings): | ||||||
|     sys.stderr.write('{bold}{command}{reset}\n'.format( |     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, |         command=new_command, | ||||||
|  |         side_effect='*' if side_effect else '', | ||||||
|         bold=color(colorama.Style.BRIGHT, settings), |         bold=color(colorama.Style.BRIGHT, settings), | ||||||
|         reset=color(colorama.Style.RESET_ALL, 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( |     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, |             command=new_command, | ||||||
|  |             side_effect='*' if side_effect else '', | ||||||
|             bold=color(colorama.Style.BRIGHT, settings), |             bold=color(colorama.Style.BRIGHT, settings), | ||||||
|             green=color(colorama.Fore.GREEN, settings), |             green=color(colorama.Fore.GREEN, settings), | ||||||
|             red=color(colorama.Fore.RED, settings), |             red=color(colorama.Fore.RED, settings), | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| from collections import namedtuple |  | ||||||
| from imp import load_source | from imp import load_source | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from os.path import expanduser | from os.path import expanduser | ||||||
| @@ -7,11 +6,8 @@ import os | |||||||
| import sys | import sys | ||||||
| from psutil import Process, TimeoutExpired | from psutil import Process, TimeoutExpired | ||||||
| import colorama | import colorama | ||||||
| from thefuck import logs | import six | ||||||
|  | from . import logs, conf, types, shells | ||||||
|  |  | ||||||
| Command = namedtuple('Command', ('script', 'stdout', 'stderr')) |  | ||||||
| Rule = namedtuple('Rule', ('name', 'match', 'get_new_command')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_user_dir(): | def setup_user_dir(): | ||||||
| @@ -20,44 +16,38 @@ def setup_user_dir(): | |||||||
|     rules_dir = user_dir.joinpath('rules') |     rules_dir = user_dir.joinpath('rules') | ||||||
|     if not rules_dir.is_dir(): |     if not rules_dir.is_dir(): | ||||||
|         rules_dir.mkdir(parents=True) |         rules_dir.mkdir(parents=True) | ||||||
|     user_dir.joinpath('settings.py').touch() |     conf.initialize_settings_file(user_dir) | ||||||
|     return 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): | def load_rule(rule): | ||||||
|     """Imports rule module and returns it.""" |     """Imports rule module and returns it.""" | ||||||
|     rule_module = load_source(rule.name[:-3], str(rule)) |     rule_module = load_source(rule.name[:-3], str(rule)) | ||||||
|     return Rule(rule.name[:-3], rule_module.match, |     return types.Rule(rule.name[:-3], rule_module.match, | ||||||
|                 rule_module.get_new_command) |                       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): | def get_rules(user_dir, settings): | ||||||
|     """Returns all enabled rules.""" |     """Returns all enabled rules.""" | ||||||
|     bundled = Path(__file__).parent\ |     bundled = Path(__file__).parent \ | ||||||
|                             .joinpath('rules')\ |         .joinpath('rules') \ | ||||||
|                             .glob('*.py') |         .glob('*.py') | ||||||
|     user = user_dir.joinpath('rules').glob('*.py') |     user = user_dir.joinpath('rules').glob('*.py') | ||||||
|     return [load_rule(rule) for rule in sorted(list(bundled)) + list(user) |     rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings) | ||||||
|             if rule.name != '__init__.py' and is_rule_enabled(settings, rule)] |     return sorted(rules, key=lambda rule: settings.priority.get( | ||||||
|  |         rule.name, rule.priority)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def wait_output(settings, popen): | def wait_output(settings, popen): | ||||||
| @@ -80,7 +70,7 @@ def wait_output(settings, popen): | |||||||
|  |  | ||||||
| def get_command(settings, args): | def get_command(settings, args): | ||||||
|     """Creates command from `args` and executes it.""" |     """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:]) |         script = ' '.join(arg.decode('utf-8') for arg in args[1:]) | ||||||
|     else: |     else: | ||||||
|         script = ' '.join(args[1:]) |         script = ' '.join(args[1:]) | ||||||
| @@ -88,11 +78,12 @@ def get_command(settings, args): | |||||||
|     if not script: |     if not script: | ||||||
|         return |         return | ||||||
|  |  | ||||||
|  |     script = shells.from_shell(script) | ||||||
|     result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, |     result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, | ||||||
|                    env=dict(os.environ, LANG='C')) |                    env=dict(os.environ, LANG='C')) | ||||||
|     if wait_output(settings, result): |     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')) |                              result.stderr.read().decode('utf-8')) | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_matched_rule(command, rules, settings): | def get_matched_rule(command, rules, settings): | ||||||
| @@ -105,13 +96,13 @@ def get_matched_rule(command, rules, settings): | |||||||
|             logs.rule_failed(rule, sys.exc_info(), 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.""" |     """Returns `True` when running of new command confirmed.""" | ||||||
|     if not settings.require_confirmation: |     if not settings.require_confirmation: | ||||||
|         logs.show_command(new_command, settings) |         logs.show_command(new_command, side_effect, settings) | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     logs.confirm_command(new_command, settings) |     logs.confirm_command(new_command, side_effect, settings) | ||||||
|     try: |     try: | ||||||
|         sys.stdin.read(1) |         sys.stdin.read(1) | ||||||
|         return True |         return True | ||||||
| @@ -122,27 +113,21 @@ def confirm(new_command, settings): | |||||||
|  |  | ||||||
| def run_rule(rule, command, settings): | def run_rule(rule, command, settings): | ||||||
|     """Runs command from rule for passed command.""" |     """Runs command from rule for passed command.""" | ||||||
|     new_command = rule.get_new_command(command, settings) |     new_command = shells.to_shell(rule.get_new_command(command, settings)) | ||||||
|     if confirm(new_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) |         print(new_command) | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_second_run(command): |  | ||||||
|     """Is it the second run of `fuck`?""" |  | ||||||
|     return command.script.startswith('fuck') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): | def main(): | ||||||
|     colorama.init() |     colorama.init() | ||||||
|     user_dir = setup_user_dir() |     user_dir = setup_user_dir() | ||||||
|     settings = get_settings(user_dir) |     settings = conf.get_settings(user_dir) | ||||||
|  |  | ||||||
|     command = get_command(settings, sys.argv) |     command = get_command(settings, sys.argv) | ||||||
|     if command: |     if command: | ||||||
|         if is_second_run(command): |  | ||||||
|             logs.failed("Can't fuck twice", settings) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         rules = get_rules(user_dir, settings) |         rules = get_rules(user_dir, settings) | ||||||
|         matched_rule = get_matched_rule(command, rules, settings) |         matched_rule = get_matched_rule(command, rules, settings) | ||||||
|         if matched_rule: |         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 | import re | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return command.script.startswith('cp ') \ |     return command.script.startswith('cp ') \ | ||||||
|         and 'cp: omitting directory' in command.stderr.lower() |         and 'cp: omitting directory' in command.stderr.lower() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     return re.sub(r'^cp', 'cp -a', command.script) |     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 | import os | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return os.path.exists(command.script.split()[0]) \ |     return os.path.exists(command.script.split()[0]) \ | ||||||
|         and 'command not found' in command.stderr |         and 'command not found' in command.stderr | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     return u'./{}'.format(command.script) |     return u'./{}'.format(command.script) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,15 @@ | |||||||
| import re | import re | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return (command.script.startswith('lein') |     return (command.script.startswith('lein') | ||||||
|             and "is not a task. See 'lein help'" in command.stderr |             and "is not a task. See 'lein help'" in command.stderr | ||||||
|             and 'Did you mean this?' in command.stderr) |             and 'Did you mean this?' in command.stderr) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     broken_cmd = re.findall(r"'([^']*)' is not a task", |     broken_cmd = re.findall(r"'([^']*)' is not a task", | ||||||
|                             command.stderr)[0] |                             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 | import re | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return ('mkdir' in command.script |     return ('mkdir' in command.script | ||||||
|             and 'No such file or directory' in command.stderr) |             and 'No such file or directory' in command.stderr) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     return re.sub('^mkdir (.*)', 'mkdir -p \\1', command.script) |     return re.sub('^mkdir (.*)', 'mkdir -p \\1', command.script) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| from difflib import get_close_matches | from difflib import get_close_matches | ||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
| def _safe(fn, fallback): | def _safe(fn, fallback): | ||||||
| @@ -17,14 +18,19 @@ def _get_all_bins(): | |||||||
|             if not _safe(exe.is_dir, True)] |             if not _safe(exe.is_dir, True)] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return 'not found' in command.stderr and \ |     return 'not found' in command.stderr and \ | ||||||
|            bool(get_close_matches(command.script.split(' ')[0], |            bool(get_close_matches(command.script.split(' ')[0], | ||||||
|                                   _get_all_bins())) |                                   _get_all_bins())) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     old_command = command.script.split(' ')[0] |     old_command = command.script.split(' ')[0] | ||||||
|     new_command = get_close_matches(old_command, |     new_command = get_close_matches(old_command, | ||||||
|                                     _get_all_bins())[0] |                                     _get_all_bins())[0] | ||||||
|     return ' '.join([new_command] + command.script.split(' ')[1:]) |     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 | # add 'python' suffix to the command if | ||||||
| #  1) The script does not have execute permission or | #  1) The script does not have execute permission or | ||||||
| #  2) is interpreted as shell script | #  2) is interpreted as shell script | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     toks = command.script.split() |     toks = command.script.split() | ||||||
|     return (len(toks) > 0 |     return (len(toks) > 0 | ||||||
| @@ -10,5 +13,6 @@ def match(command, settings): | |||||||
|                  'command not found' in command.stderr)) |                  'command not found' in command.stderr)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     return 'python ' + command.script |     return 'python ' + command.script | ||||||
|   | |||||||
| @@ -1,9 +1,13 @@ | |||||||
| import re | import re | ||||||
|  | from thefuck.utils import sudo_support | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @sudo_support | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     return ('rm' in command.script |     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): | def get_new_command(command, settings): | ||||||
|     return re.sub('^rm (.*)', 'rm -rf \\1', command.script) |     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 |     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) |     offending = offending_pattern.findall(command.stderr) | ||||||
|     for filepath, lineno in offending: |     for filepath, lineno in offending: | ||||||
|         with open(filepath, 'r') as fh: |         with open(filepath, 'r') as fh: | ||||||
| @@ -30,8 +34,3 @@ def remove_offending_keys(command, settings): | |||||||
|             del lines[int(lineno) - 1] |             del lines[int(lineno) - 1] | ||||||
|         with open(filepath, 'w') as fh: |         with open(filepath, 'w') as fh: | ||||||
|             fh.writelines(lines) |             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', |             'Operation not permitted', | ||||||
|             'root privilege', |             'root privilege', | ||||||
|             'This command has to be run under the root user.', |             '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): | def match(command, settings): | ||||||
|     for pattern in patterns: |     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 True | ||||||
|     return False |     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 | from functools import wraps | ||||||
| import os | import os | ||||||
|  | import six | ||||||
|  | from .types import Command | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DEVNULL = open(os.devnull, 'w') | ||||||
|  |  | ||||||
|  |  | ||||||
| def which(program): | def which(program): | ||||||
| @@ -35,9 +40,25 @@ def wrap_settings(params): | |||||||
|     def decorator(fn): |     def decorator(fn): | ||||||
|         @wraps(fn) |         @wraps(fn) | ||||||
|         def wrapper(command, settings): |         def wrapper(command, settings): | ||||||
|             for key, val in params.items(): |             return fn(command, settings.update(**params)) | ||||||
|                 if not hasattr(settings, key): |  | ||||||
|                     setattr(settings, key, val) |  | ||||||
|             return fn(command, settings) |  | ||||||
|         return wrapper |         return wrapper | ||||||
|     return decorator |     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