mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	Merge pull request #324 from nvbn/298-variants
Add ability to select fixed command from variants
This commit is contained in:
		| @@ -13,6 +13,7 @@ addons: | |||||||
|       - fish |       - fish | ||||||
|       - tcsh |       - tcsh | ||||||
|       - pandoc |       - pandoc | ||||||
|  |       - git | ||||||
| env: | env: | ||||||
|   - FUNCTIONAL=true BARE=true |   - FUNCTIONAL=true BARE=true | ||||||
| install: | install: | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -14,7 +14,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied) | |||||||
| E: Unable to lock the administration directory (/var/lib/dpkg/), are you root? | E: Unable to lock the administration directory (/var/lib/dpkg/), are you root? | ||||||
|  |  | ||||||
| ➜ fuck | ➜ fuck | ||||||
| sudo apt-get install vim [enter/ctrl+c] | sudo apt-get install vim [enter/↑/↓/ctrl+c] | ||||||
| [sudo] password for nvbn: | [sudo] password for nvbn: | ||||||
| Reading package lists... Done | Reading package lists... Done | ||||||
| ... | ... | ||||||
| @@ -29,7 +29,7 @@ To push the current branch and set the remote as upstream, use | |||||||
|  |  | ||||||
|  |  | ||||||
| ➜ fuck | ➜ fuck | ||||||
| git push --set-upstream origin master [enter/ctrl+c] | git push --set-upstream origin master [enter/↑/↓/ctrl+c] | ||||||
| Counting objects: 9, done. | Counting objects: 9, done. | ||||||
| ... | ... | ||||||
| ``` | ``` | ||||||
| @@ -42,7 +42,7 @@ No command 'puthon' found, did you mean: | |||||||
| zsh: command not found: puthon | zsh: command not found: puthon | ||||||
|  |  | ||||||
| ➜ fuck | ➜ fuck | ||||||
| python [enter/ctrl+c] | python [enter/↑/↓/ctrl+c] | ||||||
| Python 3.4.2 (default, Oct  8 2014, 13:08:17) | Python 3.4.2 (default, Oct  8 2014, 13:08:17) | ||||||
| ... | ... | ||||||
| ``` | ``` | ||||||
| @@ -55,7 +55,7 @@ Did you mean this? | |||||||
| 	branch | 	branch | ||||||
|  |  | ||||||
| ➜ fuck | ➜ fuck | ||||||
| git branch [enter/ctrl+c] | git branch [enter/↑/↓/ctrl+c] | ||||||
| * master | * master | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -67,7 +67,7 @@ Did you mean this? | |||||||
|          repl |          repl | ||||||
|  |  | ||||||
| ➜ fuck | ➜ fuck | ||||||
| lein repl [enter/ctrl+c] | lein repl [enter/↑/↓/ctrl+c] | ||||||
| nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848 | nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848 | ||||||
| REPL-y 0.3.1 | REPL-y 0.3.1 | ||||||
| ... | ... | ||||||
| @@ -213,7 +213,7 @@ in `~/.thefuck/rules`. The rule should contain two functions: | |||||||
|  |  | ||||||
| ```python | ```python | ||||||
| match(command: Command, settings: Settings) -> bool | match(command: Command, settings: Settings) -> bool | ||||||
| get_new_command(command: Command, settings: Settings) -> str | get_new_command(command: Command, settings: Settings) -> str | list[str] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Also the rule can contain an optional function `side_effect(command: Command, settings: Settings) -> None` | Also the rule can contain an optional function `side_effect(command: Command, settings: Settings) -> None` | ||||||
|   | |||||||
| @@ -1,10 +1,17 @@ | |||||||
|  | from time import sleep | ||||||
| from pexpect import TIMEOUT | from pexpect import TIMEOUT | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _set_confirmation(proc, require): | ||||||
|  |     proc.sendline(u'mkdir -p ~/.thefuck') | ||||||
|  |     proc.sendline( | ||||||
|  |         u'echo "require_confirmation = {}" > ~/.thefuck/settings.py'.format( | ||||||
|  |             require)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def with_confirmation(proc): | def with_confirmation(proc): | ||||||
|     """Ensures that command can be fixed when confirmation enabled.""" |     """Ensures that command can be fixed when confirmation enabled.""" | ||||||
|     proc.sendline(u'mkdir -p ~/.thefuck') |     _set_confirmation(proc, True) | ||||||
|     proc.sendline(u'echo "require_confirmation = True" > ~/.thefuck/settings.py') |  | ||||||
|  |  | ||||||
|     proc.sendline(u'ehco test') |     proc.sendline(u'ehco test') | ||||||
|  |  | ||||||
| @@ -17,10 +24,10 @@ def with_confirmation(proc): | |||||||
|     assert proc.expect([TIMEOUT, u'test']) |     assert proc.expect([TIMEOUT, u'test']) | ||||||
|  |  | ||||||
|  |  | ||||||
| def history_changed(proc): | def history_changed(proc, to): | ||||||
|     """Ensures that history changed.""" |     """Ensures that history changed.""" | ||||||
|     proc.send('\033[A') |     proc.send('\033[A') | ||||||
|     assert proc.expect([TIMEOUT, u'echo test']) |     assert proc.expect([TIMEOUT, to]) | ||||||
|  |  | ||||||
|  |  | ||||||
| def history_not_changed(proc): | def history_not_changed(proc): | ||||||
| @@ -29,10 +36,29 @@ def history_not_changed(proc): | |||||||
|     assert proc.expect([TIMEOUT, u'fuck']) |     assert proc.expect([TIMEOUT, u'fuck']) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def select_command_with_arrows(proc): | ||||||
|  |     """Ensures that command can be selected with arrow keys.""" | ||||||
|  |     _set_confirmation(proc, True) | ||||||
|  |  | ||||||
|  |     proc.sendline(u'git h') | ||||||
|  |     assert proc.expect([TIMEOUT, u"git: 'h' is not a git command."]) | ||||||
|  |  | ||||||
|  |     proc.sendline(u'fuck') | ||||||
|  |     assert proc.expect([TIMEOUT, u'git show']) | ||||||
|  |     proc.send('\033[B') | ||||||
|  |     assert proc.expect([TIMEOUT, u'git push']) | ||||||
|  |     proc.send('\033[B') | ||||||
|  |     assert proc.expect([TIMEOUT, u'git help']) | ||||||
|  |     proc.send('\033[A') | ||||||
|  |     assert proc.expect([TIMEOUT, u'git push']) | ||||||
|  |     proc.send('\n') | ||||||
|  |  | ||||||
|  |     assert proc.expect([TIMEOUT, u'Not a git repository']) | ||||||
|  |  | ||||||
|  |  | ||||||
| def refuse_with_confirmation(proc): | def refuse_with_confirmation(proc): | ||||||
|     """Ensures that fix can be refused when confirmation enabled.""" |     """Ensures that fix can be refused when confirmation enabled.""" | ||||||
|     proc.sendline(u'mkdir -p ~/.thefuck') |     _set_confirmation(proc, True) | ||||||
|     proc.sendline(u'echo "require_confirmation = True" > ~/.thefuck/settings.py') |  | ||||||
|  |  | ||||||
|     proc.sendline(u'ehco test') |     proc.sendline(u'ehco test') | ||||||
|  |  | ||||||
| @@ -47,8 +73,7 @@ def refuse_with_confirmation(proc): | |||||||
|  |  | ||||||
| def without_confirmation(proc): | def without_confirmation(proc): | ||||||
|     """Ensures that command can be fixed when confirmation disabled.""" |     """Ensures that command can be fixed when confirmation disabled.""" | ||||||
|     proc.sendline(u'mkdir -p ~/.thefuck') |     _set_confirmation(proc, False) | ||||||
|     proc.sendline(u'echo "require_confirmation = False" > ~/.thefuck/settings.py') |  | ||||||
|  |  | ||||||
|     proc.sendline(u'ehco test') |     proc.sendline(u'ehco test') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,51 +1,53 @@ | |||||||
| import pytest | import pytest | ||||||
| from tests.functional.plots import with_confirmation, without_confirmation, \ | from tests.functional.plots import with_confirmation, without_confirmation, \ | ||||||
|     refuse_with_confirmation, history_changed, history_not_changed |     refuse_with_confirmation, history_changed, history_not_changed, \ | ||||||
|  |     select_command_with_arrows | ||||||
| from tests.functional.utils import spawn, functional, images | from tests.functional.utils import spawn, functional, images | ||||||
|  |  | ||||||
| containers = images(('ubuntu-python3-bash', u''' | containers = images(('ubuntu-python3-bash', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python3 python3-pip python3-dev | RUN apt-get install -yy python3 python3-pip python3-dev git | ||||||
| RUN pip3 install -U setuptools | RUN pip3 install -U setuptools | ||||||
| RUN ln -s /usr/bin/pip3 /usr/bin/pip | RUN ln -s /usr/bin/pip3 /usr/bin/pip | ||||||
| '''), | '''), | ||||||
|                     ('ubuntu-python2-bash', u''' |                     ('ubuntu-python2-bash', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python python-pip python-dev | RUN apt-get install -yy python python-pip python-dev git | ||||||
| RUN pip2 install -U pip setuptools | RUN pip2 install -U pip setuptools | ||||||
| ''')) | ''')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @pytest.fixture(params=containers) | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def proc(request): | ||||||
| def test_with_confirmation(tag, dockerfile): |     tag, dockerfile = request.param | ||||||
|     with spawn(tag, dockerfile, u'bash') as proc: |     proc = spawn(request, tag, dockerfile, u'bash') | ||||||
|     proc.sendline(u"export PS1='$ '") |     proc.sendline(u"export PS1='$ '") | ||||||
|     proc.sendline(u'eval $(thefuck-alias)') |     proc.sendline(u'eval $(thefuck-alias)') | ||||||
|         proc.sendline(u'touch $HISTFILE') |     proc.sendline(u'echo > $HISTFILE') | ||||||
|  |     return proc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @functional | ||||||
|  | def test_with_confirmation(proc): | ||||||
|     with_confirmation(proc) |     with_confirmation(proc) | ||||||
|         history_changed(proc) |     history_changed(proc, u'echo test') | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_select_command_with_arrows(proc): | ||||||
| def test_refuse_with_confirmation(tag, dockerfile): |     select_command_with_arrows(proc) | ||||||
|     with spawn(tag, dockerfile, u'bash') as proc: |     history_changed(proc, u'git push') | ||||||
|         proc.sendline(u"export PS1='$ '") |  | ||||||
|         proc.sendline(u'eval $(thefuck-alias)') |  | ||||||
|         proc.sendline(u'touch $HISTFILE') | @functional | ||||||
|  | def test_refuse_with_confirmation(proc): | ||||||
|     refuse_with_confirmation(proc) |     refuse_with_confirmation(proc) | ||||||
|     history_not_changed(proc) |     history_not_changed(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_without_confirmation(proc): | ||||||
| def test_without_confirmation(tag, dockerfile): |  | ||||||
|     with spawn(tag, dockerfile, u'bash') as proc: |  | ||||||
|         proc.sendline(u"export PS1='$ '") |  | ||||||
|         proc.sendline(u'eval $(thefuck-alias)') |  | ||||||
|         proc.sendline(u'touch $HISTFILE') |  | ||||||
|     without_confirmation(proc) |     without_confirmation(proc) | ||||||
|         history_changed(proc) |     history_changed(proc, u'echo test') | ||||||
|   | |||||||
| @@ -1,53 +1,59 @@ | |||||||
| import pytest | import pytest | ||||||
| from tests.functional.plots import with_confirmation, without_confirmation, \ | from tests.functional.plots import with_confirmation, without_confirmation, \ | ||||||
|     refuse_with_confirmation |     refuse_with_confirmation, select_command_with_arrows | ||||||
| from tests.functional.utils import spawn, functional, images, bare | from tests.functional.utils import spawn, functional, images, bare | ||||||
|  |  | ||||||
| containers = images(('ubuntu-python3-fish', u''' | containers = images(('ubuntu-python3-fish', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python3 python3-pip python3-dev fish | RUN apt-get install -yy python3 python3-pip python3-dev fish git | ||||||
| RUN pip3 install -U setuptools | RUN pip3 install -U setuptools | ||||||
| RUN ln -s /usr/bin/pip3 /usr/bin/pip | RUN ln -s /usr/bin/pip3 /usr/bin/pip | ||||||
|  | RUN apt-get install -yy fish | ||||||
| '''), | '''), | ||||||
|                     ('ubuntu-python2-fish', u''' |                     ('ubuntu-python2-fish', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python python-pip python-dev fish | RUN apt-get install -yy python python-pip python-dev git | ||||||
| RUN pip2 install -U pip setuptools | RUN pip2 install -U pip setuptools | ||||||
|  | RUN apt-get install -yy fish | ||||||
| ''')) | ''')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(params=containers) | ||||||
|  | def proc(request): | ||||||
|  |     tag, dockerfile = request.param | ||||||
|  |     proc = spawn(request, tag, dockerfile, u'fish') | ||||||
|  |     proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish') | ||||||
|  |     proc.sendline(u'fish') | ||||||
|  |     return proc | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.skipif( | @pytest.mark.skipif( | ||||||
|     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') |     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_with_confirmation(proc): | ||||||
| def test_with_confirmation(tag, dockerfile): |  | ||||||
|     with spawn(tag, dockerfile, u'fish') as proc: |  | ||||||
|         proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish') |  | ||||||
|         proc.sendline(u'fish') |  | ||||||
|     with_confirmation(proc) |     with_confirmation(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.skipif( | @pytest.mark.skipif( | ||||||
|     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') |     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_select_command_with_arrows(proc): | ||||||
| def test_refuse_with_confirmation(tag, dockerfile): |     select_command_with_arrows(proc) | ||||||
|     with spawn(tag, dockerfile, u'fish') as proc: |  | ||||||
|         proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish') |  | ||||||
|         proc.sendline(u'fish') | @functional | ||||||
|  | @pytest.mark.skipif( | ||||||
|  |     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') | ||||||
|  | def test_refuse_with_confirmation(proc): | ||||||
|     refuse_with_confirmation(proc) |     refuse_with_confirmation(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.skipif( | @pytest.mark.skipif( | ||||||
|     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') |     bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71') | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_without_confirmation(proc): | ||||||
| def test_without_confirmation(tag, dockerfile): |  | ||||||
|     with spawn(tag, dockerfile, u'fish') as proc: |  | ||||||
|         proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish') |  | ||||||
|         proc.sendline(u'fish') |  | ||||||
|     without_confirmation(proc) |     without_confirmation(proc) | ||||||
|  |  | ||||||
| # TODO: ensure that history changes. | # TODO: ensure that history changes. | ||||||
|   | |||||||
| @@ -1,47 +1,51 @@ | |||||||
| import pytest | import pytest | ||||||
| from tests.functional.utils import spawn, functional, images | from tests.functional.utils import spawn, functional, images | ||||||
| from tests.functional.plots import with_confirmation, without_confirmation, \ | from tests.functional.plots import with_confirmation, without_confirmation, \ | ||||||
|     refuse_with_confirmation |     refuse_with_confirmation, select_command_with_arrows | ||||||
|  |  | ||||||
| containers = images(('ubuntu-python3-tcsh', u''' | containers = images(('ubuntu-python3-tcsh', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python3 python3-pip python3-dev tcsh | RUN apt-get install -yy python3 python3-pip python3-dev git | ||||||
| RUN pip3 install -U setuptools | RUN pip3 install -U setuptools | ||||||
| RUN ln -s /usr/bin/pip3 /usr/bin/pip | RUN ln -s /usr/bin/pip3 /usr/bin/pip | ||||||
|  | RUN apt-get install -yy tcsh | ||||||
| '''), | '''), | ||||||
|                     ('ubuntu-python2-tcsh', u''' |                     ('ubuntu-python2-tcsh', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python python-pip python-dev tcsh | RUN apt-get install -yy python python-pip python-dev git | ||||||
| RUN pip2 install -U pip setuptools | RUN pip2 install -U pip setuptools | ||||||
|  | RUN apt-get install -yy tcsh | ||||||
| ''')) | ''')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @pytest.fixture(params=containers) | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def proc(request): | ||||||
| def test_with_confirmation(tag, dockerfile): |     tag, dockerfile = request.param | ||||||
|     with spawn(tag, dockerfile, u'tcsh') as proc: |     proc = spawn(request, tag, dockerfile, u'tcsh') | ||||||
|     proc.sendline(u'tcsh') |     proc.sendline(u'tcsh') | ||||||
|     proc.sendline(u'eval `thefuck-alias`') |     proc.sendline(u'eval `thefuck-alias`') | ||||||
|  |     return proc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @functional | ||||||
|  | def test_with_confirmation(proc): | ||||||
|     with_confirmation(proc) |     with_confirmation(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_select_command_with_arrows(proc): | ||||||
| def test_refuse_with_confirmation(tag, dockerfile): |     select_command_with_arrows(proc) | ||||||
|     with spawn(tag, dockerfile, u'tcsh') as proc: |  | ||||||
|         proc.sendline(u'tcsh') |  | ||||||
|         proc.sendline(u'eval `thefuck-alias`') | @functional | ||||||
|  | def test_refuse_with_confirmation(proc): | ||||||
|     refuse_with_confirmation(proc) |     refuse_with_confirmation(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_without_confirmation(proc): | ||||||
| def test_without_confirmation(tag, dockerfile): |  | ||||||
|     with spawn(tag, dockerfile, u'tcsh') as proc: |  | ||||||
|         proc.sendline(u'tcsh') |  | ||||||
|         proc.sendline(u'eval `thefuck-alias`') |  | ||||||
|     without_confirmation(proc) |     without_confirmation(proc) | ||||||
|  |  | ||||||
| # TODO: ensure that history changes. | # TODO: ensure that history changes. | ||||||
|   | |||||||
| @@ -1,51 +1,57 @@ | |||||||
| import pytest | import pytest | ||||||
| from tests.functional.utils import spawn, functional, images | from tests.functional.utils import spawn, functional, images | ||||||
| from tests.functional.plots import with_confirmation, without_confirmation, \ | from tests.functional.plots import with_confirmation, without_confirmation, \ | ||||||
|     refuse_with_confirmation, history_changed, history_not_changed |     refuse_with_confirmation, history_changed, history_not_changed, select_command_with_arrows | ||||||
|  |  | ||||||
| containers = images(('ubuntu-python3-zsh', u''' | containers = images(('ubuntu-python3-zsh', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python3 python3-pip python3-dev zsh | RUN apt-get install -yy python3 python3-pip python3-dev git | ||||||
| RUN pip3 install -U setuptools | RUN pip3 install -U setuptools | ||||||
| RUN ln -s /usr/bin/pip3 /usr/bin/pip | RUN ln -s /usr/bin/pip3 /usr/bin/pip | ||||||
|  | RUN apt-get install -yy zsh | ||||||
| '''), | '''), | ||||||
|                     ('ubuntu-python2-zsh', u''' |                     ('ubuntu-python2-zsh', u''' | ||||||
| FROM ubuntu:latest | FROM ubuntu:latest | ||||||
| RUN apt-get update | RUN apt-get update | ||||||
| RUN apt-get install -yy python python-pip python-dev zsh | RUN apt-get install -yy python python-pip python-dev git | ||||||
| RUN pip2 install -U pip setuptools | RUN pip2 install -U pip setuptools | ||||||
|  | RUN apt-get install -yy zsh | ||||||
| ''')) | ''')) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @pytest.fixture(params=containers) | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def proc(request): | ||||||
| def test_with_confirmation(tag, dockerfile): |     tag, dockerfile = request.param | ||||||
|     with spawn(tag, dockerfile, u'zsh') as proc: |     proc = spawn(request, tag, dockerfile, u'zsh') | ||||||
|     proc.sendline(u'eval $(thefuck-alias)') |     proc.sendline(u'eval $(thefuck-alias)') | ||||||
|     proc.sendline(u'export HISTFILE=~/.zsh_history') |     proc.sendline(u'export HISTFILE=~/.zsh_history') | ||||||
|         proc.sendline(u'touch $HISTFILE') |     proc.sendline(u'echo > $HISTFILE') | ||||||
|  |     proc.sendline(u'export SAVEHIST=100') | ||||||
|  |     proc.sendline(u'export HISTSIZE=100') | ||||||
|  |     proc.sendline(u'setopt INC_APPEND_HISTORY') | ||||||
|  |     return proc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @functional | ||||||
|  | def test_with_confirmation(proc): | ||||||
|     with_confirmation(proc) |     with_confirmation(proc) | ||||||
|         history_changed(proc) |     history_changed(proc, u'echo test') | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_select_command_with_arrows(proc): | ||||||
| def test_refuse_with_confirmation(tag, dockerfile): |     select_command_with_arrows(proc) | ||||||
|     with spawn(tag, dockerfile, u'zsh') as proc: |     history_changed(proc, u'git push') | ||||||
|         proc.sendline(u'eval $(thefuck-alias)') |  | ||||||
|         proc.sendline(u'export HISTFILE=~/.zsh_history') |  | ||||||
|         proc.sendline(u'touch $HISTFILE') | @functional | ||||||
|  | def test_refuse_with_confirmation(proc): | ||||||
|     refuse_with_confirmation(proc) |     refuse_with_confirmation(proc) | ||||||
|     history_not_changed(proc) |     history_not_changed(proc) | ||||||
|  |  | ||||||
|  |  | ||||||
| @functional | @functional | ||||||
| @pytest.mark.parametrize('tag, dockerfile', containers) | def test_without_confirmation(proc): | ||||||
| def test_without_confirmation(tag, dockerfile): |  | ||||||
|     with spawn(tag, dockerfile, u'zsh') as proc: |  | ||||||
|         proc.sendline(u'eval $(thefuck-alias)') |  | ||||||
|         proc.sendline(u'export HISTFILE=~/.zsh_history') |  | ||||||
|         proc.sendline(u'touch $HISTFILE') |  | ||||||
|     without_confirmation(proc) |     without_confirmation(proc) | ||||||
|         history_changed(proc) |     history_changed(proc, u'echo test') | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| import os | import os | ||||||
| from contextlib import contextmanager |  | ||||||
| import subprocess | import subprocess | ||||||
| import shutil | import shutil | ||||||
| from tempfile import mkdtemp | from tempfile import mkdtemp | ||||||
| @@ -15,16 +14,17 @@ enabled = os.environ.get('FUNCTIONAL') | |||||||
|  |  | ||||||
| def build_container(tag, dockerfile): | def build_container(tag, dockerfile): | ||||||
|     tmpdir = mkdtemp() |     tmpdir = mkdtemp() | ||||||
|  |     try: | ||||||
|         with Path(tmpdir).joinpath('Dockerfile').open('w') as file: |         with Path(tmpdir).joinpath('Dockerfile').open('w') as file: | ||||||
|             file.write(dockerfile) |             file.write(dockerfile) | ||||||
|         if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir], |         if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir], | ||||||
|                            cwd=root) != 0: |                            cwd=root) != 0: | ||||||
|             raise Exception("Can't build a container") |             raise Exception("Can't build a container") | ||||||
|  |     finally: | ||||||
|         shutil.rmtree(tmpdir) |         shutil.rmtree(tmpdir) | ||||||
|  |  | ||||||
|  |  | ||||||
| @contextmanager | def spawn(request, tag, dockerfile, cmd): | ||||||
| def spawn(tag, dockerfile, cmd): |  | ||||||
|     if bare: |     if bare: | ||||||
|         proc = pexpect.spawnu(cmd) |         proc = pexpect.spawnu(cmd) | ||||||
|     else: |     else: | ||||||
| @@ -33,13 +33,12 @@ def spawn(tag, dockerfile, cmd): | |||||||
|         proc = pexpect.spawnu('docker run --volume {}:/src --tty=true ' |         proc = pexpect.spawnu('docker run --volume {}:/src --tty=true ' | ||||||
|                               '--interactive=true {} {}'.format(root, tag, cmd)) |                               '--interactive=true {} {}'.format(root, tag, cmd)) | ||||||
|         proc.sendline('pip install /src') |         proc.sendline('pip install /src') | ||||||
|  |     proc.sendline('cd /') | ||||||
|  |  | ||||||
|     proc.logfile = sys.stdout |     proc.logfile = sys.stdout | ||||||
|  |  | ||||||
|     try: |     request.addfinalizer(proc.terminate) | ||||||
|         yield proc |     return proc | ||||||
|     finally: |  | ||||||
|         proc.terminate(force=bare) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def images(*items): | def images(*items): | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ def test_match(brew_unknown_cmd): | |||||||
|  |  | ||||||
| def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2): | def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2): | ||||||
|     assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd), |     assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd), | ||||||
|                            None) == 'brew list' |                            None) == ['brew list', 'brew install', 'brew uninstall'] | ||||||
|  |  | ||||||
|     assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2), |     assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2), | ||||||
|                            None) == 'brew install' |                            None) == ['brew install', 'brew uninstall', 'brew list'] | ||||||
|   | |||||||
| @@ -122,8 +122,8 @@ def test_not_match(script, stderr): | |||||||
|  |  | ||||||
| @pytest.mark.usefixtures('docker_help') | @pytest.mark.usefixtures('docker_help') | ||||||
| @pytest.mark.parametrize('wrong, fixed', [ | @pytest.mark.parametrize('wrong, fixed', [ | ||||||
|     ('pes', 'ps'), |     ('pes', ['ps', 'push', 'pause']), | ||||||
|     ('tags', 'tag')]) |     ('tags', ['tag', 'stats', 'images'])]) | ||||||
| def test_get_new_command(wrong, fixed): | def test_get_new_command(wrong, fixed): | ||||||
|     command = Command('docker {}'.format(wrong), stderr=stderr(wrong)) |     command = Command('docker {}'.format(wrong), stderr=stderr(wrong)) | ||||||
|     assert get_new_command(command, None) == 'docker {}'.format(fixed) |     assert get_new_command(command, None) == ['docker {}'.format(x) for x in fixed] | ||||||
|   | |||||||
| @@ -50,8 +50,8 @@ def test_match(git_not_command, git_command, git_not_command_one_of_this): | |||||||
| 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, | ||||||
|                          git_not_command_closest): |                          git_not_command_closest): | ||||||
|     assert get_new_command(Command('git brnch', stderr=git_not_command), None) \ |     assert get_new_command(Command('git brnch', stderr=git_not_command), None) \ | ||||||
|            == 'git branch' |            == ['git branch'] | ||||||
|     assert get_new_command(Command('git st', stderr=git_not_command_one_of_this), |     assert get_new_command(Command('git st', stderr=git_not_command_one_of_this), | ||||||
|                            None) == 'git status' |                            None) == ['git stats', 'git stash', 'git stage'] | ||||||
|     assert get_new_command(Command('git tags', stderr=git_not_command_closest), |     assert get_new_command(Command('git tags', stderr=git_not_command_closest), | ||||||
|                            None) == 'git tag' |                            None) == ['git tag', 'git stage'] | ||||||
|   | |||||||
| @@ -25,4 +25,4 @@ def test_get_new_command(mocker): | |||||||
|     mocker.patch('thefuck.rules.gulp_not_task.get_gulp_tasks', return_value=[ |     mocker.patch('thefuck.rules.gulp_not_task.get_gulp_tasks', return_value=[ | ||||||
|         'serve', 'build', 'default']) |         'serve', 'build', 'default']) | ||||||
|     command = Command('gulp srve', stdout('srve')) |     command = Command('gulp srve', stdout('srve')) | ||||||
|     assert get_new_command(command, None) == 'gulp serve' |     assert get_new_command(command, None) == ['gulp serve', 'gulp default'] | ||||||
|   | |||||||
| @@ -27,8 +27,8 @@ def test_not_match(script, stderr): | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize('cmd, result', [ | @pytest.mark.parametrize('cmd, result', [ | ||||||
|     ('log', 'heroku logs'), |     ('log', ['heroku logs', 'heroku pg']), | ||||||
|     ('pge', 'heroku pg')]) |     ('pge', ['heroku pg', 'heroku logs'])]) | ||||||
| def test_get_new_command(cmd, result): | def test_get_new_command(cmd, result): | ||||||
|     command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)) |     command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)) | ||||||
|     assert get_new_command(command, None) == result |     assert get_new_command(command, None) == result | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ def is_not_task(): | |||||||
|  |  | ||||||
| Did you mean this? | Did you mean this? | ||||||
|          repl |          repl | ||||||
|  |          jar | ||||||
| ''' | ''' | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -19,4 +20,4 @@ def test_match(is_not_task): | |||||||
|  |  | ||||||
| def test_get_new_command(is_not_task): | def test_get_new_command(is_not_task): | ||||||
|     assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task), |     assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task), | ||||||
|                            None) == 'lein repl --help' |                            None) == ['lein repl --help', 'lein jar --help'] | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ def test_get_new_command(): | |||||||
|     assert get_new_command( |     assert get_new_command( | ||||||
|         Command(stderr='vom: not found', |         Command(stderr='vom: not found', | ||||||
|                 script='vom file.py'), |                 script='vom file.py'), | ||||||
|         None) == 'vim file.py' |         None) == ['vim file.py'] | ||||||
|     assert get_new_command( |     assert get_new_command( | ||||||
|         Command(stderr='fucck: not found', |         Command(stderr='fucck: not found', | ||||||
|                 script='fucck'), |                 script='fucck'), | ||||||
|         Command) == 'fsck' |         Command) == ['fsck'] | ||||||
|   | |||||||
| @@ -61,30 +61,30 @@ def test_not_match(command): | |||||||
|     assert not match(command, None) |     assert not match(command, None) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize('command, new_command', [ | @pytest.mark.parametrize('command, new_commands', [ | ||||||
|     (Command('tsuru log', stderr=( |     (Command('tsuru log', stderr=( | ||||||
|         'tsuru: "log" is not a tsuru command. See "tsuru help".\n' |         'tsuru: "log" is not a tsuru command. See "tsuru help".\n' | ||||||
|         '\nDid you mean?\n' |         '\nDid you mean?\n' | ||||||
|         '\tapp-log\n' |         '\tapp-log\n' | ||||||
|         '\tlogin\n' |         '\tlogin\n' | ||||||
|         '\tlogout\n' |         '\tlogout\n' | ||||||
|     )), 'tsuru login'), |     )), ['tsuru login', 'tsuru logout', 'tsuru app-log']), | ||||||
|     (Command('tsuru app-l', stderr=( |     (Command('tsuru app-l', stderr=( | ||||||
|         'tsuru: "app-l" is not a tsuru command. See "tsuru help".\n' |         'tsuru: "app-l" is not a tsuru command. See "tsuru help".\n' | ||||||
|         '\nDid you mean?\n' |         '\nDid you mean?\n' | ||||||
|         '\tapp-list\n' |         '\tapp-list\n' | ||||||
|         '\tapp-log\n' |         '\tapp-log\n' | ||||||
|     )), 'tsuru app-log'), |     )), ['tsuru app-log', 'tsuru app-list']), | ||||||
|     (Command('tsuru user-list', stderr=( |     (Command('tsuru user-list', stderr=( | ||||||
|         'tsuru: "user-list" is not a tsuru command. See "tsuru help".\n' |         'tsuru: "user-list" is not a tsuru command. See "tsuru help".\n' | ||||||
|         '\nDid you mean?\n' |         '\nDid you mean?\n' | ||||||
|         '\tteam-user-list\n' |         '\tteam-user-list\n' | ||||||
|     )), 'tsuru team-user-list'), |     )), ['tsuru team-user-list']), | ||||||
|     (Command('tsuru targetlist', stderr=( |     (Command('tsuru targetlist', stderr=( | ||||||
|         'tsuru: "targetlist" is not a tsuru command. See "tsuru help".\n' |         'tsuru: "targetlist" is not a tsuru command. See "tsuru help".\n' | ||||||
|         '\nDid you mean?\n' |         '\nDid you mean?\n' | ||||||
|         '\ttarget-list\n' |         '\ttarget-list\n' | ||||||
|     )), 'tsuru target-list'), |     )), ['tsuru target-list']), | ||||||
| ]) | ]) | ||||||
| def test_get_new_command(command, new_command): | def test_get_new_command(command, new_commands): | ||||||
|     assert get_new_command(command, None) == new_command |     assert get_new_command(command, None) == new_commands | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								tests/test_corrector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								tests/test_corrector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | import pytest | ||||||
|  | from pathlib import PosixPath, Path | ||||||
|  | from mock import Mock | ||||||
|  | from thefuck import corrector, conf, types | ||||||
|  | from tests.utils import Rule, Command | ||||||
|  | from thefuck.corrector import make_corrected_commands, get_corrected_commands | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_load_rule(mocker): | ||||||
|  |     match = object() | ||||||
|  |     get_new_command = object() | ||||||
|  |     load_source = mocker.patch( | ||||||
|  |         'thefuck.corrector.load_source', | ||||||
|  |         return_value=Mock(match=match, | ||||||
|  |                           get_new_command=get_new_command, | ||||||
|  |                           enabled_by_default=True, | ||||||
|  |                           priority=900, | ||||||
|  |                           requires_output=True)) | ||||||
|  |     assert corrector.load_rule(Path('/rules/bash.py'), settings=Mock(priority={})) \ | ||||||
|  |            == Rule('bash', match, get_new_command, priority=900) | ||||||
|  |     load_source.assert_called_once_with('bash', '/rules/bash.py') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestGetRules(object): | ||||||
|  |     @pytest.fixture(autouse=True) | ||||||
|  |     def glob(self, mocker): | ||||||
|  |         return mocker.patch('thefuck.corrector.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')] | ||||||
|  |         monkeypatch.setattr('thefuck.corrector.load_source', | ||||||
|  |                             lambda x, _: Rule(x)) | ||||||
|  |         assert self._compare_names( | ||||||
|  |             corrector.get_rules(Path('~'), Mock(rules=conf_rules, priority={})), | ||||||
|  |             rules) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestGetMatchedRules(object): | ||||||
|  |     def test_no_match(self): | ||||||
|  |         assert list(corrector.get_matched_rules( | ||||||
|  |             Command('ls'), [Rule('', lambda *_: False)], | ||||||
|  |             Mock(no_colors=True))) == [] | ||||||
|  |  | ||||||
|  |     def test_match(self): | ||||||
|  |         rule = Rule('', lambda x, _: x.script == 'cd ..') | ||||||
|  |         assert list(corrector.get_matched_rules( | ||||||
|  |             Command('cd ..'), [rule], Mock(no_colors=True))) == [rule] | ||||||
|  |  | ||||||
|  |     def test_when_rule_failed(self, capsys): | ||||||
|  |         all(corrector.get_matched_rules( | ||||||
|  |             Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')), | ||||||
|  |                                  requires_output=False)], | ||||||
|  |             Mock(no_colors=True, debug=False))) | ||||||
|  |         assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestGetCorrectedCommands(object): | ||||||
|  |     def test_with_rule_returns_list(self): | ||||||
|  |         rule = Rule(get_new_command=lambda x, _: [x.script + '!', x.script + '@'], | ||||||
|  |                     priority=100) | ||||||
|  |         assert list(make_corrected_commands(Command(script='test'), [rule], None)) \ | ||||||
|  |                == [types.CorrectedCommand(script='test!', priority=100, side_effect=None), | ||||||
|  |                    types.CorrectedCommand(script='test@', priority=200, side_effect=None)] | ||||||
|  |  | ||||||
|  |     def test_with_rule_returns_command(self): | ||||||
|  |         rule = Rule(get_new_command=lambda x, _: x.script + '!', | ||||||
|  |                     priority=100) | ||||||
|  |         assert list(make_corrected_commands(Command(script='test'), [rule], None)) \ | ||||||
|  |                == [types.CorrectedCommand(script='test!', priority=100, side_effect=None)] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_corrected_commands(mocker): | ||||||
|  |     command = Command('test', 'test', 'test') | ||||||
|  |     rules = [Rule(match=lambda *_: False), | ||||||
|  |              Rule(match=lambda *_: True, | ||||||
|  |                   get_new_command=lambda x, _: x.script + '!', priority=100), | ||||||
|  |              Rule(match=lambda *_: True, | ||||||
|  |                   get_new_command=lambda x, _: [x.script + '@', x.script + ';'], | ||||||
|  |                   priority=60)] | ||||||
|  |     mocker.patch('thefuck.corrector.get_rules', return_value=rules) | ||||||
|  |     assert [cmd.script for cmd in get_corrected_commands(command, None, Mock(debug=False))]\ | ||||||
|  |         == ['test@', 'test!', 'test;'] | ||||||
| @@ -1,61 +1,8 @@ | |||||||
| import pytest | import pytest | ||||||
| from subprocess import PIPE | from subprocess import PIPE | ||||||
| from pathlib import PosixPath, Path |  | ||||||
| from mock import Mock | from mock import Mock | ||||||
| from thefuck import main, conf, types | from thefuck import main | ||||||
| from tests.utils import Rule, Command | from tests.utils import Command | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_load_rule(mocker): |  | ||||||
|     match = object() |  | ||||||
|     get_new_command = object() |  | ||||||
|     load_source = mocker.patch( |  | ||||||
|         'thefuck.main.load_source', |  | ||||||
|         return_value=Mock(match=match, |  | ||||||
|                           get_new_command=get_new_command, |  | ||||||
|                           enabled_by_default=True, |  | ||||||
|                           priority=900, |  | ||||||
|                           requires_output=True)) |  | ||||||
|     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') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestGetRules(object): |  | ||||||
|     @pytest.fixture(autouse=True) |  | ||||||
|     def glob(self, mocker): |  | ||||||
|         return mocker.patch('thefuck.main.Path.glob', return_value=[]) |  | ||||||
|  |  | ||||||
|     def _compare_names(self, rules, names): |  | ||||||
|         return [r.name for r in rules] == names |  | ||||||
|  |  | ||||||
|     @pytest.mark.parametrize('conf_rules, rules', [ |  | ||||||
|         (conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']), |  | ||||||
|         (types.RulesNamesList(['bash']), ['bash', 'bash'])]) |  | ||||||
|     def test_get(self, monkeypatch, glob, conf_rules, rules): |  | ||||||
|         glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] |  | ||||||
|         monkeypatch.setattr('thefuck.main.load_source', |  | ||||||
|                             lambda x, _: Rule(x)) |  | ||||||
|         assert self._compare_names( |  | ||||||
|             main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})), |  | ||||||
|             rules) |  | ||||||
|  |  | ||||||
|     @pytest.mark.parametrize('priority, unordered, ordered', [ |  | ||||||
|         ({}, |  | ||||||
|          [Rule('bash', priority=100), Rule('python', priority=5)], |  | ||||||
|          ['python', 'bash']), |  | ||||||
|         ({}, |  | ||||||
|          [Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)], |  | ||||||
|          ['c', 'lisp']), |  | ||||||
|         ({'python': 9999}, |  | ||||||
|          [Rule('bash', priority=100), Rule('python', priority=5)], |  | ||||||
|          ['bash', 'python'])]) |  | ||||||
|     def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered): |  | ||||||
|         monkeypatch.setattr('thefuck.main._get_loaded_rules', |  | ||||||
|                             lambda *_: unordered) |  | ||||||
|         assert self._compare_names( |  | ||||||
|             main.get_rules(Path('~'), Mock(priority=priority)), |  | ||||||
|             ordered) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestGetCommand(object): | class TestGetCommand(object): | ||||||
| @@ -95,80 +42,3 @@ class TestGetCommand(object): | |||||||
|             assert main.get_command(Mock(env={}), args).script == result |             assert main.get_command(Mock(env={}), args).script == result | ||||||
|         else: |         else: | ||||||
|             assert main.get_command(Mock(env={}), args) is None |             assert main.get_command(Mock(env={}), args) is None | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestGetMatchedRule(object): |  | ||||||
|     def test_no_match(self): |  | ||||||
|         assert main.get_matched_rule( |  | ||||||
|             Command('ls'), [Rule('', lambda *_: False)], |  | ||||||
|             Mock(no_colors=True)) is None |  | ||||||
|  |  | ||||||
|     def test_match(self): |  | ||||||
|         rule = Rule('', lambda x, _: x.script == 'cd ..') |  | ||||||
|         assert main.get_matched_rule( |  | ||||||
|             Command('cd ..'), [rule], Mock(no_colors=True)) == rule |  | ||||||
|  |  | ||||||
|     def test_when_rule_failed(self, capsys): |  | ||||||
|         main.get_matched_rule( |  | ||||||
|             Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))], |  | ||||||
|             Mock(no_colors=True, debug=False)) |  | ||||||
|         assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestRunRule(object): |  | ||||||
|     @pytest.fixture(autouse=True) |  | ||||||
|     def confirm(self, mocker): |  | ||||||
|         return mocker.patch('thefuck.main.confirm', return_value=True) |  | ||||||
|  |  | ||||||
|     def test_run_rule(self, capsys): |  | ||||||
|         main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), |  | ||||||
|                       Command(), None) |  | ||||||
|         assert capsys.readouterr() == ('new-command\n', '') |  | ||||||
|  |  | ||||||
|     def test_run_rule_with_side_effect(self, capsys): |  | ||||||
|         side_effect = Mock() |  | ||||||
|         settings = Mock(debug=False) |  | ||||||
|         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() == ('', '') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TestConfirm(object): |  | ||||||
|     @pytest.fixture |  | ||||||
|     def stdin(self, mocker): |  | ||||||
|         return mocker.patch('sys.stdin.read', return_value='\n') |  | ||||||
|  |  | ||||||
|     def test_when_not_required(self, capsys): |  | ||||||
|         assert main.confirm('command', None, Mock(require_confirmation=False)) |  | ||||||
|         assert capsys.readouterr() == ('', 'command\n') |  | ||||||
|  |  | ||||||
|     def test_with_side_effect_and_without_confirmation(self, capsys): |  | ||||||
|         assert main.confirm('command', Mock(), Mock(require_confirmation=False)) |  | ||||||
|         assert capsys.readouterr() == ('', 'command (+side effect)\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]') |  | ||||||
|  |  | ||||||
|     # `stdin` fixture should be applied after `capsys` |  | ||||||
|     def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin): |  | ||||||
|         assert main.confirm('command', Mock(), Mock(require_confirmation=True, |  | ||||||
|                                                     no_colors=True)) |  | ||||||
|         assert capsys.readouterr() == ('', 'command (+side effect) [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') |  | ||||||
|   | |||||||
							
								
								
									
										117
									
								
								tests/test_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								tests/test_ui.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | # -*- encoding: utf-8 -*- | ||||||
|  |  | ||||||
|  | from mock import Mock | ||||||
|  | import pytest | ||||||
|  | from itertools import islice | ||||||
|  | from thefuck import ui | ||||||
|  | from thefuck.types import CorrectedCommand | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def patch_getch(monkeypatch): | ||||||
|  |     def patch(vals): | ||||||
|  |         def getch(): | ||||||
|  |             for val in vals: | ||||||
|  |                 if val == KeyboardInterrupt: | ||||||
|  |                     raise val | ||||||
|  |                 else: | ||||||
|  |                     yield val | ||||||
|  |  | ||||||
|  |         getch_gen = getch() | ||||||
|  |         monkeypatch.setattr('thefuck.ui.getch', lambda: next(getch_gen)) | ||||||
|  |  | ||||||
|  |     return patch | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_read_actions(patch_getch): | ||||||
|  |     patch_getch([  # Enter: | ||||||
|  |                    '\n', | ||||||
|  |                    # Enter: | ||||||
|  |                    '\r', | ||||||
|  |                    # Ignored: | ||||||
|  |                    'x', 'y', | ||||||
|  |                    # Up: | ||||||
|  |                    '\x1b', '[', 'A', | ||||||
|  |                    # Down: | ||||||
|  |                    '\x1b', '[', 'B', | ||||||
|  |                    # Ctrl+C: | ||||||
|  |                    KeyboardInterrupt], ) | ||||||
|  |     assert list(islice(ui.read_actions(), 5)) \ | ||||||
|  |            == [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_command_selector(): | ||||||
|  |     selector = ui.CommandSelector([1, 2, 3]) | ||||||
|  |     assert selector.value == 1 | ||||||
|  |     changes = [] | ||||||
|  |     selector.on_change(changes.append) | ||||||
|  |     selector.next() | ||||||
|  |     assert selector.value == 2 | ||||||
|  |     selector.next() | ||||||
|  |     assert selector.value == 3 | ||||||
|  |     selector.next() | ||||||
|  |     assert selector.value == 1 | ||||||
|  |     selector.previous() | ||||||
|  |     assert selector.value == 3 | ||||||
|  |     assert changes == [1, 2, 3, 1, 3] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestSelectCommand(object): | ||||||
|  |     @pytest.fixture | ||||||
|  |     def commands_with_side_effect(self): | ||||||
|  |         return [CorrectedCommand('ls', lambda *_: None, 100), | ||||||
|  |                 CorrectedCommand('cd', lambda *_: None, 100)] | ||||||
|  |  | ||||||
|  |     @pytest.fixture | ||||||
|  |     def commands(self): | ||||||
|  |         return [CorrectedCommand('ls', None, 100), | ||||||
|  |                 CorrectedCommand('cd', None, 100)] | ||||||
|  |  | ||||||
|  |     def test_without_commands(self, capsys): | ||||||
|  |         assert ui.select_command([], Mock(debug=False, no_color=True)) is None | ||||||
|  |         assert capsys.readouterr() == ('', 'No fuck given\n') | ||||||
|  |  | ||||||
|  |     def test_without_confirmation(self, capsys, commands): | ||||||
|  |         assert ui.select_command(commands, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=False)) == commands[0] | ||||||
|  |         assert capsys.readouterr() == ('', 'ls\n') | ||||||
|  |  | ||||||
|  |     def test_without_confirmation_with_side_effects(self, capsys, | ||||||
|  |                                                     commands_with_side_effect): | ||||||
|  |         assert ui.select_command(commands_with_side_effect, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=False)) \ | ||||||
|  |                == commands_with_side_effect[0] | ||||||
|  |         assert capsys.readouterr() == ('', 'ls (+side effect)\n') | ||||||
|  |  | ||||||
|  |     def test_with_confirmation(self, capsys, patch_getch, commands): | ||||||
|  |         patch_getch(['\n']) | ||||||
|  |         assert ui.select_command(commands, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=True)) == commands[0] | ||||||
|  |         assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n') | ||||||
|  |  | ||||||
|  |     def test_with_confirmation_abort(self, capsys, patch_getch, commands): | ||||||
|  |         patch_getch([KeyboardInterrupt]) | ||||||
|  |         assert ui.select_command(commands, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=True)) is None | ||||||
|  |         assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n') | ||||||
|  |  | ||||||
|  |     def test_with_confirmation_with_side_effct(self, capsys, patch_getch, | ||||||
|  |                                                commands_with_side_effect): | ||||||
|  |         patch_getch(['\n']) | ||||||
|  |         assert ui.select_command(commands_with_side_effect, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=True))\ | ||||||
|  |                == commands_with_side_effect[0] | ||||||
|  |         assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n') | ||||||
|  |  | ||||||
|  |     def test_with_confirmation_select_second(self, capsys, patch_getch, commands): | ||||||
|  |         patch_getch(['\x1b', '[', 'B', '\n']) | ||||||
|  |         assert ui.select_command(commands, | ||||||
|  |                                  Mock(debug=False, no_color=True, | ||||||
|  |                                       require_confirmation=True)) == commands[1] | ||||||
|  |         assert capsys.readouterr() == ( | ||||||
|  |             '', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n') | ||||||
| @@ -18,6 +18,7 @@ def test_wrap_settings(override, old, new): | |||||||
| @pytest.mark.parametrize('return_value, command, called, result', [ | @pytest.mark.parametrize('return_value, command, called, result', [ | ||||||
|     ('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'), |     ('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'), | ||||||
|     ('ls -lah', 'ls', 'ls', 'ls -lah'), |     ('ls -lah', 'ls', 'ls', 'ls -lah'), | ||||||
|  |     (['ls -lah'], 'sudo ls', 'ls', ['sudo ls -lah']), | ||||||
|     (True, 'sudo ls', 'ls', True), |     (True, 'sudo ls', 'ls', True), | ||||||
|     (True, 'ls', 'ls', True), |     (True, 'ls', 'ls', True), | ||||||
|     (False, 'sudo ls', 'ls', False), |     (False, 'sudo ls', 'ls', False), | ||||||
|   | |||||||
							
								
								
									
										80
									
								
								thefuck/corrector.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								thefuck/corrector.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | import sys | ||||||
|  | from imp import load_source | ||||||
|  | from pathlib import Path | ||||||
|  | from . import conf, types, logs | ||||||
|  | from .utils import eager | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def load_rule(rule, settings): | ||||||
|  |     """Imports rule module and returns it.""" | ||||||
|  |     name = rule.name[:-3] | ||||||
|  |     rule_module = load_source(name, str(rule)) | ||||||
|  |     priority = getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY) | ||||||
|  |     return types.Rule(name, rule_module.match, | ||||||
|  |                       rule_module.get_new_command, | ||||||
|  |                       getattr(rule_module, 'enabled_by_default', True), | ||||||
|  |                       getattr(rule_module, 'side_effect', None), | ||||||
|  |                       settings.priority.get(name, priority), | ||||||
|  |                       getattr(rule_module, 'requires_output', True)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_loaded_rules(rules, settings): | ||||||
|  |     """Yields all available rules.""" | ||||||
|  |     for rule in rules: | ||||||
|  |         if rule.name != '__init__.py': | ||||||
|  |             loaded_rule = load_rule(rule, settings) | ||||||
|  |             if loaded_rule in settings.rules: | ||||||
|  |                 yield loaded_rule | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @eager | ||||||
|  | def get_rules(user_dir, settings): | ||||||
|  |     """Returns all enabled rules.""" | ||||||
|  |     bundled = Path(__file__).parent \ | ||||||
|  |         .joinpath('rules') \ | ||||||
|  |         .glob('*.py') | ||||||
|  |     user = user_dir.joinpath('rules').glob('*.py') | ||||||
|  |     return get_loaded_rules(sorted(bundled) + sorted(user), settings) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @eager | ||||||
|  | def get_matched_rules(command, rules, settings): | ||||||
|  |     """Returns first matched rule for command.""" | ||||||
|  |     script_only = command.stdout is None and command.stderr is None | ||||||
|  |  | ||||||
|  |     for rule in rules: | ||||||
|  |         if script_only and rule.requires_output: | ||||||
|  |             continue | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             with logs.debug_time(u'Trying rule: {};'.format(rule.name), | ||||||
|  |                                  settings): | ||||||
|  |                 if rule.match(command, settings): | ||||||
|  |                     yield rule | ||||||
|  |         except Exception: | ||||||
|  |             logs.rule_failed(rule, sys.exc_info(), settings) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def make_corrected_commands(command, rules, settings): | ||||||
|  |     for rule in rules: | ||||||
|  |         new_commands = rule.get_new_command(command, settings) | ||||||
|  |         if not isinstance(new_commands, list): | ||||||
|  |             new_commands = [new_commands] | ||||||
|  |         for n, new_command in enumerate(new_commands): | ||||||
|  |             yield types.CorrectedCommand(script=new_command, | ||||||
|  |                                          side_effect=rule.side_effect, | ||||||
|  |                                          priority=(n + 1) * rule.priority) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_corrected_commands(command, user_dir, settings): | ||||||
|  |     rules = get_rules(user_dir, settings) | ||||||
|  |     logs.debug( | ||||||
|  |         u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)), | ||||||
|  |         settings) | ||||||
|  |     matched = get_matched_rules(command, rules, settings) | ||||||
|  |     logs.debug( | ||||||
|  |         u'Matched rules: {}'.format(', '.join(rule.name for rule in matched)), | ||||||
|  |         settings) | ||||||
|  |     corrected_commands = make_corrected_commands(command, matched, settings) | ||||||
|  |     return sorted(corrected_commands, | ||||||
|  |                   key=lambda corrected_command: corrected_command.priority) | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | # -*- encoding: utf-8 -*- | ||||||
|  |  | ||||||
| from contextlib import contextmanager | from contextlib import contextmanager | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import sys | import sys | ||||||
| @@ -28,27 +30,6 @@ def rule_failed(rule, exc_info, settings): | |||||||
|     exception('Rule {}'.format(rule.name), exc_info, settings) |     exception('Rule {}'.format(rule.name), exc_info, settings) | ||||||
|  |  | ||||||
|  |  | ||||||
| def show_command(new_command, side_effect, settings): |  | ||||||
|     sys.stderr.write('{bold}{command}{reset}{side_effect}\n'.format( |  | ||||||
|         command=new_command, |  | ||||||
|         side_effect=' (+side effect)' if side_effect else '', |  | ||||||
|         bold=color(colorama.Style.BRIGHT, settings), |  | ||||||
|         reset=color(colorama.Style.RESET_ALL, settings))) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def confirm_command(new_command, side_effect, settings): |  | ||||||
|     sys.stderr.write( |  | ||||||
|         '{bold}{command}{reset}{side_effect} ' |  | ||||||
|         '[{green}enter{reset}/{red}ctrl+c{reset}]'.format( |  | ||||||
|             command=new_command, |  | ||||||
|             side_effect=' (+side effect)' if side_effect else '', |  | ||||||
|             bold=color(colorama.Style.BRIGHT, settings), |  | ||||||
|             green=color(colorama.Fore.GREEN, settings), |  | ||||||
|             red=color(colorama.Fore.RED, settings), |  | ||||||
|             reset=color(colorama.Style.RESET_ALL, settings))) |  | ||||||
|     sys.stderr.flush() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def failed(msg, settings): | def failed(msg, settings): | ||||||
|     sys.stderr.write('{red}{msg}{reset}\n'.format( |     sys.stderr.write('{red}{msg}{reset}\n'.format( | ||||||
|         msg=msg, |         msg=msg, | ||||||
| @@ -56,6 +37,27 @@ def failed(msg, settings): | |||||||
|         reset=color(colorama.Style.RESET_ALL, settings))) |         reset=color(colorama.Style.RESET_ALL, settings))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def show_corrected_command(corrected_command, settings): | ||||||
|  |     sys.stderr.write('{bold}{script}{reset}{side_effect}\n'.format( | ||||||
|  |         script=corrected_command.script, | ||||||
|  |         side_effect=' (+side effect)' if corrected_command.side_effect else '', | ||||||
|  |         bold=color(colorama.Style.BRIGHT, settings), | ||||||
|  |         reset=color(colorama.Style.RESET_ALL, settings))) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def confirm_text(corrected_command, settings): | ||||||
|  |     sys.stderr.write( | ||||||
|  |         '\033[1K\r{bold}{script}{reset}{side_effect} ' | ||||||
|  |         '[{green}enter{reset}/{blue}↑{reset}/{blue}↓{reset}/{red}ctrl+c{reset}]'.format( | ||||||
|  |             script=corrected_command.script, | ||||||
|  |             side_effect=' (+side effect)' if corrected_command.side_effect else '', | ||||||
|  |             bold=color(colorama.Style.BRIGHT, settings), | ||||||
|  |             green=color(colorama.Fore.GREEN, settings), | ||||||
|  |             red=color(colorama.Fore.RED, settings), | ||||||
|  |             reset=color(colorama.Style.RESET_ALL, settings), | ||||||
|  |             blue=color(colorama.Fore.BLUE, settings))) | ||||||
|  |  | ||||||
|  |  | ||||||
| def debug(msg, settings): | def debug(msg, settings): | ||||||
|     if settings.debug: |     if settings.debug: | ||||||
|         sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format( |         sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format( | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| from imp import load_source |  | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from os.path import expanduser | from os.path import expanduser | ||||||
| from pprint import pformat | from pprint import pformat | ||||||
| @@ -9,6 +8,8 @@ from psutil import Process, TimeoutExpired | |||||||
| import colorama | import colorama | ||||||
| import six | import six | ||||||
| from . import logs, conf, types, shells | from . import logs, conf, types, shells | ||||||
|  | from .corrector import get_corrected_commands | ||||||
|  | from .ui import select_command | ||||||
|  |  | ||||||
|  |  | ||||||
| def setup_user_dir(): | def setup_user_dir(): | ||||||
| @@ -21,37 +22,6 @@ def setup_user_dir(): | |||||||
|     return user_dir |     return user_dir | ||||||
|  |  | ||||||
|  |  | ||||||
| def load_rule(rule): |  | ||||||
|     """Imports rule module and returns it.""" |  | ||||||
|     rule_module = load_source(rule.name[:-3], str(rule)) |  | ||||||
|     return types.Rule(rule.name[:-3], rule_module.match, |  | ||||||
|                       rule_module.get_new_command, |  | ||||||
|                       getattr(rule_module, 'enabled_by_default', True), |  | ||||||
|                       getattr(rule_module, 'side_effect', None), |  | ||||||
|                       getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY), |  | ||||||
|                       getattr(rule_module, 'requires_output', True)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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): |  | ||||||
|     """Returns all enabled rules.""" |  | ||||||
|     bundled = Path(__file__).parent \ |  | ||||||
|         .joinpath('rules') \ |  | ||||||
|         .glob('*.py') |  | ||||||
|     user = user_dir.joinpath('rules').glob('*.py') |  | ||||||
|     rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings) |  | ||||||
|     return sorted(rules, key=lambda rule: settings.priority.get( |  | ||||||
|         rule.name, rule.priority)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def wait_output(settings, popen): | def wait_output(settings, popen): | ||||||
|     """Returns `True` if we can get output of the command in the |     """Returns `True` if we can get output of the command in the | ||||||
|     `wait_command` time. |     `wait_command` time. | ||||||
| @@ -100,46 +70,12 @@ def get_command(settings, args): | |||||||
|             return types.Command(script, None, None) |             return types.Command(script, None, None) | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_matched_rule(command, rules, settings): | def run_command(command, settings): | ||||||
|     """Returns first matched rule for command.""" |  | ||||||
|     script_only = command.stdout is None and command.stderr is None |  | ||||||
|  |  | ||||||
|     for rule in rules: |  | ||||||
|         if script_only and rule.requires_output: |  | ||||||
|             continue |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             with logs.debug_time(u'Trying rule: {};'.format(rule.name), |  | ||||||
|                                  settings): |  | ||||||
|                 if rule.match(command, settings): |  | ||||||
|                     return rule |  | ||||||
|         except Exception: |  | ||||||
|             logs.rule_failed(rule, sys.exc_info(), settings) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def confirm(new_command, side_effect, settings): |  | ||||||
|     """Returns `True` when running of new command confirmed.""" |  | ||||||
|     if not settings.require_confirmation: |  | ||||||
|         logs.show_command(new_command, side_effect, settings) |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     logs.confirm_command(new_command, side_effect, settings) |  | ||||||
|     try: |  | ||||||
|         sys.stdin.read(1) |  | ||||||
|         return True |  | ||||||
|     except KeyboardInterrupt: |  | ||||||
|         logs.failed('Aborted', settings) |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def run_rule(rule, command, settings): |  | ||||||
|     """Runs command from rule for passed command.""" |     """Runs command from rule for passed command.""" | ||||||
|     new_command = shells.to_shell(rule.get_new_command(command, settings)) |     if command.side_effect: | ||||||
|     if confirm(new_command, rule.side_effect, settings): |         command.side_effect(command, settings) | ||||||
|         if rule.side_effect: |     shells.put_to_history(command.script) | ||||||
|             rule.side_effect(command, settings) |     print(command.script) | ||||||
|         shells.put_to_history(new_command) |  | ||||||
|         print(new_command) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Entry points: | # Entry points: | ||||||
| @@ -152,18 +88,10 @@ def main(): | |||||||
|         logs.debug(u'Run with settings: {}'.format(pformat(settings)), settings) |         logs.debug(u'Run with settings: {}'.format(pformat(settings)), settings) | ||||||
|  |  | ||||||
|         command = get_command(settings, sys.argv) |         command = get_command(settings, sys.argv) | ||||||
|         rules = get_rules(user_dir, settings) |         corrected_commands = get_corrected_commands(command, user_dir, settings) | ||||||
|         logs.debug( |         selected_command = select_command(corrected_commands, settings) | ||||||
|             u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)), |         if selected_command: | ||||||
|             settings) |             run_command(selected_command, settings) | ||||||
|  |  | ||||||
|         matched_rule = get_matched_rule(command, rules, settings) |  | ||||||
|         if matched_rule: |  | ||||||
|             logs.debug(u'Matched rule: {}'.format(matched_rule.name), settings) |  | ||||||
|             run_rule(matched_rule, command, settings) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         logs.failed('No fuck given', settings) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def print_alias(): | def print_alias(): | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import subprocess | import subprocess | ||||||
| from thefuck.utils import get_closest, replace_argument | from thefuck.utils import get_closest, replace_command | ||||||
|  |  | ||||||
| BREW_CMD_PATH = '/Library/Homebrew/cmd' | BREW_CMD_PATH = '/Library/Homebrew/cmd' | ||||||
| TAP_PATH = '/Library/Taps' | TAP_PATH = '/Library/Taps' | ||||||
| @@ -77,10 +77,6 @@ if brew_path_prefix: | |||||||
|         pass |         pass | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_similar_command(command): |  | ||||||
|     return get_closest(command, brew_commands) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
|     is_proper_command = ('brew' in command.script and |     is_proper_command = ('brew' in command.script and | ||||||
|                          'Unknown command' in command.stderr) |                          'Unknown command' in command.stderr) | ||||||
| @@ -89,7 +85,7 @@ def match(command, settings): | |||||||
|     if is_proper_command: |     if is_proper_command: | ||||||
|         broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', |         broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', | ||||||
|                                 command.stderr)[0] |                                 command.stderr)[0] | ||||||
|         has_possible_commands = bool(_get_similar_command(broken_cmd)) |         has_possible_commands = bool(get_closest(broken_cmd, brew_commands)) | ||||||
|  |  | ||||||
|     return has_possible_commands |     return has_possible_commands | ||||||
|  |  | ||||||
| @@ -97,6 +93,4 @@ def match(command, settings): | |||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', |     broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)', | ||||||
|                             command.stderr)[0] |                             command.stderr)[0] | ||||||
|     new_cmd = _get_similar_command(broken_cmd) |     return replace_command(command, broken_cmd, brew_commands) | ||||||
|  |  | ||||||
|     return replace_argument(command.script, broken_cmd, new_cmd) |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| from itertools import dropwhile, takewhile, islice | from itertools import dropwhile, takewhile, islice | ||||||
| import re | import re | ||||||
| import subprocess | import subprocess | ||||||
| from thefuck.utils import get_closest, sudo_support, replace_argument | from thefuck.utils import get_closest, sudo_support, replace_argument, replace_command | ||||||
|  |  | ||||||
|  |  | ||||||
| @sudo_support | @sudo_support | ||||||
| @@ -23,5 +23,4 @@ def get_docker_commands(): | |||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     wrong_command = re.findall( |     wrong_command = re.findall( | ||||||
|         r"docker: '(\w+)' is not a docker command.", command.stderr)[0] |         r"docker: '(\w+)' is not a docker command.", command.stderr)[0] | ||||||
|     fixed_command = get_closest(wrong_command, get_docker_commands()) |     return replace_command(command, wrong_command, get_docker_commands()) | ||||||
|     return replace_argument(command.script, wrong_command, fixed_command) |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import re | import re | ||||||
| from thefuck.utils import (get_closest, git_support, replace_argument, | from thefuck.utils import (git_support, | ||||||
|                            get_all_matched_commands) |                            get_all_matched_commands, replace_command) | ||||||
|  |  | ||||||
|  |  | ||||||
| @git_support | @git_support | ||||||
| @@ -13,7 +13,5 @@ def match(command, settings): | |||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     broken_cmd = re.findall(r"git: '([^']*)' is not a git command", |     broken_cmd = re.findall(r"git: '([^']*)' is not a git command", | ||||||
|                             command.stderr)[0] |                             command.stderr)[0] | ||||||
|     new_cmd = get_closest(broken_cmd, |     matched = get_all_matched_commands(command.stderr) | ||||||
|                           get_all_matched_commands(command.stderr)) |     return replace_command(command, broken_cmd, matched) | ||||||
|     return replace_argument(command.script, broken_cmd, new_cmd) |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import re | import re | ||||||
| import subprocess | import subprocess | ||||||
| from thefuck.utils import get_closest, replace_argument | from thefuck.utils import replace_command | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, script): | def match(command, script): | ||||||
| @@ -18,5 +18,4 @@ def get_gulp_tasks(): | |||||||
| def get_new_command(command, script): | def get_new_command(command, script): | ||||||
|     wrong_task = re.findall(r"Task '(\w+)' is not in your gulpfile", |     wrong_task = re.findall(r"Task '(\w+)' is not in your gulpfile", | ||||||
|                             command.stdout)[0] |                             command.stdout)[0] | ||||||
|     fixed_task = get_closest(wrong_task, get_gulp_tasks()) |     return replace_command(command, wrong_task, get_gulp_tasks()) | ||||||
|     return replace_argument(command.script, wrong_task, fixed_task) |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import re | import re | ||||||
| from thefuck.utils import get_closest, replace_argument | from thefuck.utils import replace_command | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
| @@ -16,5 +16,4 @@ def _get_suggests(stderr): | |||||||
|  |  | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0] |     wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0] | ||||||
|     correct = get_closest(wrong, _get_suggests(command.stderr)) |     return replace_command(command, wrong, _get_suggests(command.stderr)) | ||||||
|     return replace_argument(command.script, wrong, correct) |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import re | import re | ||||||
| from thefuck.utils import sudo_support, replace_argument | from thefuck.utils import sudo_support,\ | ||||||
|  |     replace_command, get_all_matched_commands | ||||||
|  |  | ||||||
|  |  | ||||||
| @sudo_support | @sudo_support | ||||||
| @@ -13,6 +14,5 @@ def match(command, settings): | |||||||
| 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] | ||||||
|     new_cmd = re.findall(r'Did you mean this\?\n\s*([^\n]*)', |     new_cmds = get_all_matched_commands(command.stderr, 'Did you mean this?') | ||||||
|                          command.stderr)[0] |     return replace_command(command, broken_cmd, new_cmds) | ||||||
|     return replace_argument(command.script, broken_cmd, new_cmd) |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| from difflib import get_close_matches | from difflib import get_close_matches | ||||||
| from thefuck.utils import sudo_support, get_all_executables, get_closest | from thefuck.utils import sudo_support, get_all_executables | ||||||
|  |  | ||||||
|  |  | ||||||
| @sudo_support | @sudo_support | ||||||
| @@ -12,8 +12,9 @@ def match(command, settings): | |||||||
| @sudo_support | @sudo_support | ||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     old_command = command.script.split(' ')[0] |     old_command = command.script.split(' ')[0] | ||||||
|     new_command = get_closest(old_command, get_all_executables()) |     new_cmds = get_close_matches(old_command, get_all_executables(), cutoff=0.1) | ||||||
|     return ' '.join([new_command] + command.script.split(' ')[1:]) |     return [' '.join([new_command] + command.script.split(' ')[1:]) | ||||||
|  |             for new_command in new_cmds] | ||||||
|  |  | ||||||
|  |  | ||||||
| priority = 3000 | priority = 3000 | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import re | import re | ||||||
| from thefuck.utils import (get_closest, replace_argument, | from thefuck.utils import (get_closest, replace_argument, | ||||||
|                            get_all_matched_commands) |                            get_all_matched_commands, replace_command) | ||||||
|  |  | ||||||
|  |  | ||||||
| def match(command, settings): | def match(command, settings): | ||||||
| @@ -12,7 +12,6 @@ def match(command, settings): | |||||||
| def get_new_command(command, settings): | def get_new_command(command, settings): | ||||||
|     broken_cmd = re.findall(r'tsuru: "([^"]*)" is not a tsuru command', |     broken_cmd = re.findall(r'tsuru: "([^"]*)" is not a tsuru command', | ||||||
|                             command.stderr)[0] |                             command.stderr)[0] | ||||||
|     new_cmd = get_closest(broken_cmd, |     return replace_command(command, broken_cmd, | ||||||
|                            get_all_matched_commands(command.stderr)) |                            get_all_matched_commands(command.stderr)) | ||||||
|     return replace_argument(command.script, broken_cmd, new_cmd) |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ from collections import namedtuple | |||||||
|  |  | ||||||
| Command = namedtuple('Command', ('script', 'stdout', 'stderr')) | Command = namedtuple('Command', ('script', 'stdout', 'stderr')) | ||||||
|  |  | ||||||
|  | CorrectedCommand = namedtuple('CorrectedCommand', ('script', 'side_effect', 'priority')) | ||||||
|  |  | ||||||
| Rule = namedtuple('Rule', ('name', 'match', 'get_new_command', | Rule = namedtuple('Rule', ('name', 'match', 'get_new_command', | ||||||
|                            'enabled_by_default', 'side_effect', |                            'enabled_by_default', 'side_effect', | ||||||
|                            'priority', 'requires_output')) |                            'priority', 'requires_output')) | ||||||
|   | |||||||
							
								
								
									
										103
									
								
								thefuck/ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								thefuck/ui.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | # -*- encoding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | from . import logs | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from msvcrt import getch | ||||||
|  | except ImportError: | ||||||
|  |     def getch(): | ||||||
|  |         import tty | ||||||
|  |         import termios | ||||||
|  |  | ||||||
|  |         fd = sys.stdin.fileno() | ||||||
|  |         old = termios.tcgetattr(fd) | ||||||
|  |         try: | ||||||
|  |             tty.setraw(fd) | ||||||
|  |             ch = sys.stdin.read(1) | ||||||
|  |             if ch == '\x03':  # For compatibility with msvcrt.getch | ||||||
|  |                 raise KeyboardInterrupt | ||||||
|  |             return ch | ||||||
|  |         finally: | ||||||
|  |             termios.tcsetattr(fd, termios.TCSADRAIN, old) | ||||||
|  |  | ||||||
|  | SELECT = 0 | ||||||
|  | ABORT = 1 | ||||||
|  | PREVIOUS = 2 | ||||||
|  | NEXT = 3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def read_actions(): | ||||||
|  |     """Yields actions for pressed keys.""" | ||||||
|  |     buffer = [] | ||||||
|  |     while True: | ||||||
|  |         try: | ||||||
|  |             ch = getch() | ||||||
|  |         except KeyboardInterrupt:  # Ctrl+C | ||||||
|  |             yield ABORT | ||||||
|  |  | ||||||
|  |         if ch in ('\n', '\r'):  # Enter | ||||||
|  |             yield SELECT | ||||||
|  |  | ||||||
|  |         buffer.append(ch) | ||||||
|  |         buffer = buffer[-3:] | ||||||
|  |  | ||||||
|  |         if buffer == ['\x1b', '[', 'A']:  # ↑ | ||||||
|  |             yield PREVIOUS | ||||||
|  |  | ||||||
|  |         if buffer == ['\x1b', '[', 'B']:  # ↓ | ||||||
|  |             yield NEXT | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CommandSelector(object): | ||||||
|  |     def __init__(self, commands): | ||||||
|  |         self._commands = commands | ||||||
|  |         self._index = 0 | ||||||
|  |         self._on_change = lambda x: x | ||||||
|  |  | ||||||
|  |     def next(self): | ||||||
|  |         self._index = (self._index + 1) % len(self._commands) | ||||||
|  |         self._on_change(self.value) | ||||||
|  |  | ||||||
|  |     def previous(self): | ||||||
|  |         self._index = (self._index - 1) % len(self._commands) | ||||||
|  |         self._on_change(self.value) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def value(self): | ||||||
|  |         return self._commands[self._index] | ||||||
|  |  | ||||||
|  |     def on_change(self, fn): | ||||||
|  |         self._on_change = fn | ||||||
|  |         fn(self.value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def select_command(corrected_commands, settings): | ||||||
|  |     """Returns: | ||||||
|  |  | ||||||
|  |      - the first command when confirmation disabled; | ||||||
|  |      - None when ctrl+c pressed; | ||||||
|  |      - selected command. | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     if not corrected_commands: | ||||||
|  |         logs.failed('No fuck given', settings) | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     selector = CommandSelector(corrected_commands) | ||||||
|  |     if not settings.require_confirmation: | ||||||
|  |         logs.show_corrected_command(selector.value, settings) | ||||||
|  |         return selector.value | ||||||
|  |  | ||||||
|  |     selector.on_change(lambda val: logs.confirm_text(val, settings)) | ||||||
|  |     for action in read_actions(): | ||||||
|  |         if action == SELECT: | ||||||
|  |             sys.stderr.write('\n') | ||||||
|  |             return selector.value | ||||||
|  |         elif action == ABORT: | ||||||
|  |             logs.failed('\nAborted', settings) | ||||||
|  |             return | ||||||
|  |         elif action == PREVIOUS: | ||||||
|  |             selector.previous() | ||||||
|  |         elif action == NEXT: | ||||||
|  |             selector.next() | ||||||
| @@ -69,6 +69,8 @@ def sudo_support(fn): | |||||||
|  |  | ||||||
|         if result and isinstance(result, six.string_types): |         if result and isinstance(result, six.string_types): | ||||||
|             return u'sudo {}'.format(result) |             return u'sudo {}'.format(result) | ||||||
|  |         elif isinstance(result, list): | ||||||
|  |             return [u'sudo {}'.format(x) for x in result] | ||||||
|         else: |         else: | ||||||
|             return result |             return result | ||||||
|     return wrapper |     return wrapper | ||||||
| @@ -161,6 +163,14 @@ def replace_argument(script, from_, to): | |||||||
|             u' {} '.format(from_), u' {} '.format(to), 1) |             u' {} '.format(from_), u' {} '.format(to), 1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def eager(fn): | ||||||
|  |     @wraps(fn) | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         return list(fn(*args, **kwargs)) | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @eager | ||||||
| def get_all_matched_commands(stderr, separator='Did you mean'): | def get_all_matched_commands(stderr, separator='Did you mean'): | ||||||
|     should_yield = False |     should_yield = False | ||||||
|     for line in stderr.split('\n'): |     for line in stderr.split('\n'): | ||||||
| @@ -168,3 +178,10 @@ def get_all_matched_commands(stderr, separator='Did you mean'): | |||||||
|             should_yield = True |             should_yield = True | ||||||
|         elif should_yield and line: |         elif should_yield and line: | ||||||
|             yield line.strip() |             yield line.strip() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def replace_command(command, broken, matched): | ||||||
|  |     """Helper for *_no_command rules.""" | ||||||
|  |     new_cmds = get_close_matches(broken, matched, cutoff=0.1) | ||||||
|  |     return [replace_argument(command.script, broken, new_cmd.strip()) | ||||||
|  |             for new_cmd in new_cmds] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user