mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-11-04 09:02:08 +00:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fbfb4b5e41 | ||
| 
						 | 
					51c37bc5ab | ||
| 
						 | 
					78769e4fbc | ||
| 
						 | 
					3e4c043ccc | ||
| 
						 | 
					934099fe9e | ||
| 
						 | 
					464f86eccf | ||
| 
						 | 
					891fbe7ed1 | ||
| 
						 | 
					5abab8bd1e | ||
| 
						 | 
					7ebc8a38af | ||
| 
						 | 
					f40b63f44b | ||
| 
						 | 
					4b4e7acc0f | ||
| 
						 | 
					a8587d3871 | ||
| 
						 | 
					370c58e679 | ||
| 
						 | 
					328e65179e | ||
| 
						 | 
					63bb4da8e1 | ||
| 
						 | 
					0b5a7a8e2d | ||
| 
						 | 
					5693bd49f7 | ||
| 
						 | 
					12f8d017b9 | 
@@ -4,6 +4,6 @@ python:
 | 
			
		||||
  - "3.3"
 | 
			
		||||
  - "2.7"
 | 
			
		||||
install:
 | 
			
		||||
  - python setup.py develop
 | 
			
		||||
  - pip install -r requirements.txt
 | 
			
		||||
  - python setup.py develop
 | 
			
		||||
script: py.test -v
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							@@ -169,12 +169,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `go_run` – appends `.go` extension when compiling/running Go programs
 | 
			
		||||
* `grep_recursive` – adds `-r` when you trying to grep directory;
 | 
			
		||||
* `has_exists_script` – prepends `./` when script/binary exists;
 | 
			
		||||
* `history` – tries to replace command with most similar command from history;
 | 
			
		||||
* `java` – removes `.java` extension when running Java programs;
 | 
			
		||||
* `javac` – appends missing `.java` when compiling Java files;
 | 
			
		||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
 | 
			
		||||
* `ls_lah` – adds -lah to ls;
 | 
			
		||||
* `man` – change manual section;
 | 
			
		||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
 | 
			
		||||
* `mercurial` – fixes wrong `hg` commands;
 | 
			
		||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
 | 
			
		||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
 | 
			
		||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
 | 
			
		||||
@@ -184,7 +186,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
 | 
			
		||||
* `python_execute` – appends missing `.py` when executing Python files;
 | 
			
		||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args'
 | 
			
		||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
 | 
			
		||||
* `sed` – adds missing '/' to `sed`'s `s` commands;
 | 
			
		||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
 | 
			
		||||
* `sl_ls` – changes `sl` to `ls`;
 | 
			
		||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
 | 
			
		||||
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
 | 
			
		||||
@@ -251,7 +253,8 @@ The Fuck has a few settings parameters which can be changed in `~/.thefuck/setti
 | 
			
		||||
* `require_confirmation` – requires confirmation before running new command, by default `False`;
 | 
			
		||||
* `wait_command` – max amount of time in seconds for getting previous command output;
 | 
			
		||||
* `no_colors` – disable colored output;
 | 
			
		||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first.
 | 
			
		||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first;
 | 
			
		||||
* `debug` – enabled debug output, by default `False`;
 | 
			
		||||
 | 
			
		||||
Example of `settings.py`:
 | 
			
		||||
 | 
			
		||||
@@ -261,6 +264,7 @@ require_confirmation = True
 | 
			
		||||
wait_command = 10
 | 
			
		||||
no_colors = False
 | 
			
		||||
priority = {'sudo': 100, 'no_command': 9999}
 | 
			
		||||
debug = False
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Or via environment variables:
 | 
			
		||||
@@ -270,7 +274,8 @@ Or via environment variables:
 | 
			
		||||
* `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.
 | 
			
		||||
rule with lower `priority` will be matched first;
 | 
			
		||||
* `THEFUCK_DEBUG` – enables debug output, `true/false`.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,3 +2,4 @@ pytest
 | 
			
		||||
mock
 | 
			
		||||
pytest-mock
 | 
			
		||||
wheel
 | 
			
		||||
setuptools>=17.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								setup.py
									
									
									
									
									
								
							@@ -11,12 +11,10 @@ elif (3, 0) < sys.version_info < (3, 3):
 | 
			
		||||
          ' ({}.{} detected).'.format(*sys.version_info[:2]))
 | 
			
		||||
    sys.exit(-1)
 | 
			
		||||
 | 
			
		||||
VERSION = '1.48'
 | 
			
		||||
VERSION = '1.49.1'
 | 
			
		||||
 | 
			
		||||
install_requires = ['psutil', 'colorama', 'six']
 | 
			
		||||
 | 
			
		||||
if sys.version_info < (3, 4):
 | 
			
		||||
    install_requires.append('pathlib')
 | 
			
		||||
extras_require = {':python_version<"3.4"': ['pathlib']}
 | 
			
		||||
 | 
			
		||||
setup(name='thefuck',
 | 
			
		||||
      version=VERSION,
 | 
			
		||||
@@ -30,6 +28,7 @@ setup(name='thefuck',
 | 
			
		||||
      include_package_data=True,
 | 
			
		||||
      zip_safe=False,
 | 
			
		||||
      install_requires=install_requires,
 | 
			
		||||
      extras_require=extras_require,
 | 
			
		||||
      entry_points={'console_scripts': [
 | 
			
		||||
          'thefuck = thefuck.main:main',
 | 
			
		||||
          'thefuck-alias = thefuck.shells:app_alias']})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def no_memoize(monkeypatch):
 | 
			
		||||
    monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
 | 
			
		||||
@@ -1,14 +1,22 @@
 | 
			
		||||
from mock import Mock
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck.rules.cp_omitting_directory import match, get_new_command
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_match():
 | 
			
		||||
    assert match(Mock(script='cp dir', stderr="cp: omitting directory 'dir'"),
 | 
			
		||||
                 None)
 | 
			
		||||
    assert not match(Mock(script='some dir',
 | 
			
		||||
                          stderr="cp: omitting directory 'dir'"), None)
 | 
			
		||||
    assert not match(Mock(script='cp dir', stderr=""), None)
 | 
			
		||||
@pytest.mark.parametrize('script, stderr', [
 | 
			
		||||
    ('cp dir', 'cp: dor: is a directory'),
 | 
			
		||||
    ('cp dir', "cp: omitting directory 'dir'")])
 | 
			
		||||
def test_match(script, stderr):
 | 
			
		||||
    assert match(Command(script, stderr=stderr), None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('script, stderr', [
 | 
			
		||||
    ('some dir', 'cp: dor: is a directory'),
 | 
			
		||||
    ('some dir', "cp: omitting directory 'dir'"),
 | 
			
		||||
    ('cp dir', '')])
 | 
			
		||||
def test_not_match(script, stderr):
 | 
			
		||||
    assert not match(Command(script, stderr=stderr), None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_new_command():
 | 
			
		||||
    assert get_new_command(Mock(script='cp dir'), None) == 'cp -a dir'
 | 
			
		||||
    assert get_new_command(Command(script='cp dir'), None) == 'cp -a dir'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								tests/rules/test_history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								tests/rules/test_history.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck.rules.history import match, get_new_command
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def history(mocker):
 | 
			
		||||
    return mocker.patch('thefuck.rules.history.get_history',
 | 
			
		||||
                        return_value=['ls cat', 'diff x', 'nocommand x'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def callables(mocker):
 | 
			
		||||
    return mocker.patch('thefuck.rules.history.get_all_callables',
 | 
			
		||||
                        return_value=['diff', 'ls'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
 | 
			
		||||
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
 | 
			
		||||
def test_match(script):
 | 
			
		||||
    assert match(Command(script=script), None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
 | 
			
		||||
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
 | 
			
		||||
def test_not_match(script):
 | 
			
		||||
    assert not match(Command(script=script), None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('history', 'callables', 'no_memoize')
 | 
			
		||||
@pytest.mark.parametrize('script, result', [
 | 
			
		||||
    ('ls cet', 'ls cat'),
 | 
			
		||||
    ('daff x', 'diff x')])
 | 
			
		||||
def test_get_new_command(script, result):
 | 
			
		||||
    assert get_new_command(Command(script), None) == result
 | 
			
		||||
@@ -1,36 +1,42 @@
 | 
			
		||||
from mock import patch, Mock
 | 
			
		||||
from thefuck.rules.no_command import match, get_new_command, _get_all_callables
 | 
			
		||||
import pytest
 | 
			
		||||
from thefuck.rules.no_command import match, get_new_command, get_all_callables
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch('thefuck.rules.no_command._safe', return_value=[])
 | 
			
		||||
@patch('thefuck.rules.no_command.get_aliases',
 | 
			
		||||
       return_value=['vim', 'apt-get', 'fsck', 'fuck'])
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def _safe(mocker):
 | 
			
		||||
    mocker.patch('thefuck.rules.no_command._safe', return_value=[])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(autouse=True)
 | 
			
		||||
def get_aliases(mocker):
 | 
			
		||||
    mocker.patch('thefuck.rules.no_command.get_aliases',
 | 
			
		||||
                 return_value=['vim', 'apt-get', 'fsck', 'fuck'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('no_memoize')
 | 
			
		||||
def test_get_all_callables(*args):
 | 
			
		||||
    all_callables = _get_all_callables()
 | 
			
		||||
    all_callables = get_all_callables()
 | 
			
		||||
    assert 'vim' in all_callables
 | 
			
		||||
    assert 'fsck' in all_callables
 | 
			
		||||
    assert 'fuck' not in all_callables
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch('thefuck.rules.no_command._safe', return_value=[])
 | 
			
		||||
@patch('thefuck.rules.no_command.get_aliases',
 | 
			
		||||
       return_value=['vim', 'apt-get', 'fsck', 'fuck'])
 | 
			
		||||
@pytest.mark.usefixtures('no_memoize')
 | 
			
		||||
def test_match(*args):
 | 
			
		||||
    assert match(Mock(stderr='vom: not found', script='vom file.py'), None)
 | 
			
		||||
    assert match(Mock(stderr='fucck: not found', script='fucck'), None)
 | 
			
		||||
    assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None)
 | 
			
		||||
    assert not match(Mock(stderr='some text', script='vom file.py'), None)
 | 
			
		||||
    assert match(Command(stderr='vom: not found', script='vom file.py'), None)
 | 
			
		||||
    assert match(Command(stderr='fucck: not found', script='fucck'), None)
 | 
			
		||||
    assert not match(Command(stderr='qweqwe: not found', script='qweqwe'), None)
 | 
			
		||||
    assert not match(Command(stderr='some text', script='vom file.py'), None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@patch('thefuck.rules.no_command._safe', return_value=[])
 | 
			
		||||
@patch('thefuck.rules.no_command.get_aliases',
 | 
			
		||||
       return_value=['vim', 'apt-get', 'fsck', 'fuck'])
 | 
			
		||||
@pytest.mark.usefixtures('no_memoize')
 | 
			
		||||
def test_get_new_command(*args):
 | 
			
		||||
    assert get_new_command(
 | 
			
		||||
        Mock(stderr='vom: not found',
 | 
			
		||||
             script='vom file.py'),
 | 
			
		||||
        Command(stderr='vom: not found',
 | 
			
		||||
                script='vom file.py'),
 | 
			
		||||
        None) == 'vim file.py'
 | 
			
		||||
    assert get_new_command(
 | 
			
		||||
        Mock(stderr='fucck: not found',
 | 
			
		||||
             script='fucck'),
 | 
			
		||||
        None) == 'fsck'
 | 
			
		||||
        Command(stderr='fucck: not found',
 | 
			
		||||
                script='fucck'),
 | 
			
		||||
        Command) == 'fsck'
 | 
			
		||||
 
 | 
			
		||||
@@ -16,4 +16,4 @@ def test_match(tmux_ambiguous):
 | 
			
		||||
 | 
			
		||||
def test_get_new_command(tmux_ambiguous):
 | 
			
		||||
    assert get_new_command(Command('tmux list', stderr=tmux_ambiguous), None)\
 | 
			
		||||
        == 'tmux list-buffers'
 | 
			
		||||
        == 'tmux list-keys'
 | 
			
		||||
 
 | 
			
		||||
@@ -5,3 +5,10 @@ from thefuck import logs
 | 
			
		||||
def test_color():
 | 
			
		||||
    assert logs.color('red', Mock(no_colors=False)) == 'red'
 | 
			
		||||
    assert logs.color('red', Mock(no_colors=True)) == ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_debug(capsys):
 | 
			
		||||
    logs.debug('test', Mock(no_colors=True, debug=True))
 | 
			
		||||
    assert capsys.readouterr() == ('', 'DEBUG: test\n')
 | 
			
		||||
    logs.debug('test', Mock(no_colors=True, debug=False))
 | 
			
		||||
    assert capsys.readouterr() == ('', '')
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ class TestGetMatchedRule(object):
 | 
			
		||||
    def test_when_rule_failed(self, capsys):
 | 
			
		||||
        main.get_matched_rule(
 | 
			
		||||
            Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
 | 
			
		||||
            Mock(no_colors=True))
 | 
			
		||||
            Mock(no_colors=True, debug=False))
 | 
			
		||||
        assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +126,7 @@ class TestRunRule(object):
 | 
			
		||||
 | 
			
		||||
    def test_run_rule_with_side_effect(self, capsys):
 | 
			
		||||
        side_effect = Mock()
 | 
			
		||||
        settings = Mock()
 | 
			
		||||
        settings = Mock(debug=False)
 | 
			
		||||
        command = Command()
 | 
			
		||||
        main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
 | 
			
		||||
                           side_effect=side_effect),
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,16 @@ def isfile(mocker):
 | 
			
		||||
    return mocker.patch('os.path.isfile', return_value=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
@pytest.mark.usefixtures('isfile')
 | 
			
		||||
def history_lines(mocker):
 | 
			
		||||
    def aux(lines):
 | 
			
		||||
        mock = mocker.patch('io.open')
 | 
			
		||||
        mock.return_value.__enter__\
 | 
			
		||||
            .return_value.__iter__.return_value = lines
 | 
			
		||||
    return aux
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGeneric(object):
 | 
			
		||||
    @pytest.fixture
 | 
			
		||||
    def shell(self):
 | 
			
		||||
@@ -38,6 +48,12 @@ class TestGeneric(object):
 | 
			
		||||
        assert 'thefuck' in shell.app_alias()
 | 
			
		||||
        assert 'TF_ALIAS' in shell.app_alias()
 | 
			
		||||
 | 
			
		||||
    def test_get_history(self, history_lines, shell):
 | 
			
		||||
        history_lines(['ls', 'rm'])
 | 
			
		||||
        # We don't know what to do in generic shell with history lines,
 | 
			
		||||
        # so just ignore them:
 | 
			
		||||
        assert list(shell.get_history()) == []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('isfile')
 | 
			
		||||
class TestBash(object):
 | 
			
		||||
@@ -85,6 +101,10 @@ class TestBash(object):
 | 
			
		||||
        assert 'thefuck' in shell.app_alias()
 | 
			
		||||
        assert 'TF_ALIAS' in shell.app_alias()
 | 
			
		||||
 | 
			
		||||
    def test_get_history(self, history_lines, shell):
 | 
			
		||||
        history_lines(['ls', 'rm'])
 | 
			
		||||
        assert list(shell.get_history()) == ['ls', 'rm']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('isfile')
 | 
			
		||||
class TestFish(object):
 | 
			
		||||
@@ -96,18 +116,34 @@ class TestFish(object):
 | 
			
		||||
    def Popen(self, mocker):
 | 
			
		||||
        mock = mocker.patch('thefuck.shells.Popen')
 | 
			
		||||
        mock.return_value.stdout.read.return_value = (
 | 
			
		||||
            b'fish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nmath')
 | 
			
		||||
            b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
 | 
			
		||||
            b'man\nmath\npopd\npushd\nruby')
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture
 | 
			
		||||
    def environ(self, monkeypatch):
 | 
			
		||||
        data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.os.environ', data)
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.usefixture('environ')
 | 
			
		||||
    def test_get_overridden_aliases(self, shell, environ):
 | 
			
		||||
        assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('before, after', [
 | 
			
		||||
        ('cd', 'cd'),
 | 
			
		||||
        ('pwd', 'pwd'),
 | 
			
		||||
        ('fuck', 'fish -ic "fuck"'),
 | 
			
		||||
        ('find', 'find'),
 | 
			
		||||
        ('funced', 'fish -ic "funced"'),
 | 
			
		||||
        ('grep', 'grep'),
 | 
			
		||||
        ('awk', 'awk'),
 | 
			
		||||
        ('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
 | 
			
		||||
        ('man', 'man'),
 | 
			
		||||
        ('open', 'open'),
 | 
			
		||||
        ('vim', 'vim'),
 | 
			
		||||
        ('ll', 'fish -ic "ll"')])  # Fish has no aliases but functions
 | 
			
		||||
        ('ll', 'fish -ic "ll"'),
 | 
			
		||||
        ('ls', 'ls')])  # Fish has no aliases but functions
 | 
			
		||||
    def test_from_shell(self, before, after, shell):
 | 
			
		||||
        assert shell.from_shell(before) == after
 | 
			
		||||
 | 
			
		||||
@@ -129,10 +165,12 @@ class TestFish(object):
 | 
			
		||||
                                       'fuck': 'fuck',
 | 
			
		||||
                                       'funced': 'funced',
 | 
			
		||||
                                       'funcsave': 'funcsave',
 | 
			
		||||
                                       'grep': 'grep',
 | 
			
		||||
                                       'history': 'history',
 | 
			
		||||
                                       'll': 'll',
 | 
			
		||||
                                       'math': 'math'}
 | 
			
		||||
                                       'math': 'math',
 | 
			
		||||
                                       'popd': 'popd',
 | 
			
		||||
                                       'pushd': 'pushd',
 | 
			
		||||
                                       'ruby': 'ruby'}
 | 
			
		||||
 | 
			
		||||
    def test_app_alias(self, shell):
 | 
			
		||||
        assert 'function fuck' in shell.app_alias()
 | 
			
		||||
@@ -187,3 +225,7 @@ class TestZsh(object):
 | 
			
		||||
        assert 'alias fuck' in shell.app_alias()
 | 
			
		||||
        assert 'thefuck' in shell.app_alias()
 | 
			
		||||
        assert 'TF_ALIAS' in shell.app_alias()
 | 
			
		||||
 | 
			
		||||
    def test_get_history(self, history_lines, shell):
 | 
			
		||||
        history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
 | 
			
		||||
        assert list(shell.get_history()) == ['ls', 'rm']
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,15 @@ def test_memoize():
 | 
			
		||||
    fn.assert_called_once_with()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixtures('no_memoize')
 | 
			
		||||
def test_no_memoize():
 | 
			
		||||
    fn = Mock(__name__='fn')
 | 
			
		||||
    memoized = memoize(fn)
 | 
			
		||||
    memoized()
 | 
			
		||||
    memoized()
 | 
			
		||||
    assert fn.call_count == 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGetClosest(object):
 | 
			
		||||
 | 
			
		||||
    def test_when_can_match(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -29,13 +29,15 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
 | 
			
		||||
                    'wait_command': 3,
 | 
			
		||||
                    'require_confirmation': False,
 | 
			
		||||
                    'no_colors': False,
 | 
			
		||||
                    'debug': 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'}
 | 
			
		||||
               'THEFUCK_PRIORITY': 'priority',
 | 
			
		||||
               'THEFUCK_DEBUG': 'debug'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
 | 
			
		||||
@@ -87,7 +89,7 @@ def _val_from_env(env, attr):
 | 
			
		||||
        return dict(_priority_from_env(val))
 | 
			
		||||
    elif attr == 'wait_command':
 | 
			
		||||
        return int(val)
 | 
			
		||||
    elif attr in ('require_confirmation', 'no_colors'):
 | 
			
		||||
    elif attr in ('require_confirmation', 'no_colors', 'debug'):
 | 
			
		||||
        return val.lower() == 'true'
 | 
			
		||||
    else:
 | 
			
		||||
        return val
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from pprint import pformat
 | 
			
		||||
import sys
 | 
			
		||||
from traceback import format_exception
 | 
			
		||||
import colorama
 | 
			
		||||
@@ -52,3 +53,12 @@ def failed(msg, settings):
 | 
			
		||||
        msg=msg,
 | 
			
		||||
        red=color(colorama.Fore.RED, settings),
 | 
			
		||||
        reset=color(colorama.Style.RESET_ALL, settings)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def debug(msg, settings):
 | 
			
		||||
    if settings.debug:
 | 
			
		||||
        sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
 | 
			
		||||
            msg=msg,
 | 
			
		||||
            reset=color(colorama.Style.RESET_ALL, settings),
 | 
			
		||||
            blue=color(colorama.Fore.BLUE, settings),
 | 
			
		||||
            bold=color(colorama.Style.BRIGHT, settings)))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
from imp import load_source
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from os.path import expanduser
 | 
			
		||||
from pprint import pformat
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
@@ -79,6 +80,7 @@ def get_command(settings, args):
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    script = shells.from_shell(script)
 | 
			
		||||
    logs.debug('Call: {}'.format(script), settings)
 | 
			
		||||
    result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
 | 
			
		||||
                   env=dict(os.environ, LANG='C'))
 | 
			
		||||
    if wait_output(settings, result):
 | 
			
		||||
@@ -90,6 +92,7 @@ def get_matched_rule(command, rules, settings):
 | 
			
		||||
    """Returns first matched rule for command."""
 | 
			
		||||
    for rule in rules:
 | 
			
		||||
        try:
 | 
			
		||||
            logs.debug(u'Trying rule: {}'.format(rule.name), settings)
 | 
			
		||||
            if rule.match(command, settings):
 | 
			
		||||
                return rule
 | 
			
		||||
        except Exception:
 | 
			
		||||
@@ -125,12 +128,21 @@ def main():
 | 
			
		||||
    colorama.init()
 | 
			
		||||
    user_dir = setup_user_dir()
 | 
			
		||||
    settings = conf.get_settings(user_dir)
 | 
			
		||||
    logs.debug('Run with settings: {}'.format(pformat(settings)), settings)
 | 
			
		||||
 | 
			
		||||
    command = get_command(settings, sys.argv)
 | 
			
		||||
    if command:
 | 
			
		||||
        logs.debug('Received stdout: {}'.format(command.stdout), settings)
 | 
			
		||||
        logs.debug('Received stderr: {}'.format(command.stderr), settings)
 | 
			
		||||
 | 
			
		||||
        rules = get_rules(user_dir, settings)
 | 
			
		||||
        logs.debug(
 | 
			
		||||
            'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
 | 
			
		||||
            settings)
 | 
			
		||||
 | 
			
		||||
        matched_rule = get_matched_rule(command, rules, settings)
 | 
			
		||||
        if matched_rule:
 | 
			
		||||
            logs.debug('Matched rule: {}'.format(matched_rule.name), settings)
 | 
			
		||||
            run_rule(matched_rule, command, settings)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,9 @@ from thefuck.utils import sudo_support
 | 
			
		||||
 | 
			
		||||
@sudo_support
 | 
			
		||||
def match(command, settings):
 | 
			
		||||
    stderr = command.stderr.lower()
 | 
			
		||||
    return command.script.startswith('cp ') \
 | 
			
		||||
        and 'cp: omitting directory' in command.stderr.lower()
 | 
			
		||||
        and ('omitting directory' in stderr or 'is a directory' in stderr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sudo_support
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								thefuck/rules/history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								thefuck/rules/history.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
from difflib import get_close_matches
 | 
			
		||||
from thefuck.shells import get_history
 | 
			
		||||
from thefuck.utils import get_closest, memoize
 | 
			
		||||
from thefuck.rules.no_command import get_all_callables
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@memoize
 | 
			
		||||
def _history_of_exists_without_current(command):
 | 
			
		||||
    callables = get_all_callables()
 | 
			
		||||
    return [line for line in get_history()
 | 
			
		||||
            if line != command.script
 | 
			
		||||
            and line.split(' ')[0] in callables]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def match(command, settings):
 | 
			
		||||
    return len(get_close_matches(command.script,
 | 
			
		||||
                                 _history_of_exists_without_current(command)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command, settings):
 | 
			
		||||
    return get_closest(command.script,
 | 
			
		||||
                       _history_of_exists_without_current(command))
 | 
			
		||||
 | 
			
		||||
priority = 9999
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
from difflib import get_close_matches
 | 
			
		||||
import os
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from thefuck.utils import sudo_support
 | 
			
		||||
from thefuck.utils import sudo_support, memoize
 | 
			
		||||
from thefuck.shells import thefuck_alias, get_aliases
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -12,7 +12,8 @@ def _safe(fn, fallback):
 | 
			
		||||
        return fallback
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_all_callables():
 | 
			
		||||
@memoize
 | 
			
		||||
def get_all_callables():
 | 
			
		||||
    tf_alias = thefuck_alias()
 | 
			
		||||
    return [exe.name
 | 
			
		||||
            for path in os.environ.get('PATH', '').split(':')
 | 
			
		||||
@@ -25,14 +26,14 @@ def _get_all_callables():
 | 
			
		||||
def match(command, settings):
 | 
			
		||||
    return 'not found' in command.stderr and \
 | 
			
		||||
           bool(get_close_matches(command.script.split(' ')[0],
 | 
			
		||||
                                  _get_all_callables()))
 | 
			
		||||
                                  get_all_callables()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@sudo_support
 | 
			
		||||
def get_new_command(command, settings):
 | 
			
		||||
    old_command = command.script.split(' ')[0]
 | 
			
		||||
    new_command = get_close_matches(old_command,
 | 
			
		||||
                                    _get_all_callables())[0]
 | 
			
		||||
                                    get_all_callables())[0]
 | 
			
		||||
    return ' '.join([new_command] + command.script.split(' ')[1:])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ patterns = ['permission denied',
 | 
			
		||||
            'must be root',
 | 
			
		||||
            'need to be root',
 | 
			
		||||
            'need root',
 | 
			
		||||
            'only root can do that']
 | 
			
		||||
            'only root can do that',
 | 
			
		||||
            'authentication is required']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def match(command, settings):
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
from thefuck.utils import get_closest
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -8,7 +9,12 @@ def match(command, settings):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command, settings):
 | 
			
		||||
    cmd = re.match(r"ambiguous command: (.*), could be: ([^, \n]*)",
 | 
			
		||||
    cmd = re.match(r"ambiguous command: (.*), could be: (.*)",
 | 
			
		||||
                   command.stderr)
 | 
			
		||||
 | 
			
		||||
    return command.script.replace(cmd.group(1), cmd.group(2))
 | 
			
		||||
    old_cmd = cmd.group(1)
 | 
			
		||||
    suggestions = [cmd.strip() for cmd in cmd.group(2).split(',')]
 | 
			
		||||
 | 
			
		||||
    new_cmd = get_closest(old_cmd, suggestions)
 | 
			
		||||
 | 
			
		||||
    return command.script.replace(old_cmd, new_cmd)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,9 @@ from collections import defaultdict
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from time import time
 | 
			
		||||
import os
 | 
			
		||||
import io
 | 
			
		||||
from psutil import Process
 | 
			
		||||
import six
 | 
			
		||||
from .utils import DEVNULL, memoize
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -48,6 +50,26 @@ class Generic(object):
 | 
			
		||||
            with open(history_file_name, 'a') as history:
 | 
			
		||||
                history.write(self._get_history_line(command_script))
 | 
			
		||||
 | 
			
		||||
    def _script_from_history(self, line):
 | 
			
		||||
        """Returns prepared history line.
 | 
			
		||||
 | 
			
		||||
        Should return a blank line if history line is corrupted or empty.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def get_history(self):
 | 
			
		||||
        """Returns list of history entries."""
 | 
			
		||||
        history_file_name = self._get_history_file_name()
 | 
			
		||||
        if os.path.isfile(history_file_name):
 | 
			
		||||
            with io.open(history_file_name, 'r',
 | 
			
		||||
                         encoding='utf-8', errors='ignore') as history:
 | 
			
		||||
                for line in history:
 | 
			
		||||
                    prepared = self._script_from_history(line)\
 | 
			
		||||
                                   .strip()
 | 
			
		||||
                    if prepared:
 | 
			
		||||
                        yield prepared
 | 
			
		||||
 | 
			
		||||
    def and_(self, *commands):
 | 
			
		||||
        return u' && '.join(commands)
 | 
			
		||||
 | 
			
		||||
@@ -63,7 +85,6 @@ class Bash(Generic):
 | 
			
		||||
            value = value[1:-1]
 | 
			
		||||
        return name, value
 | 
			
		||||
 | 
			
		||||
    @memoize
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
 | 
			
		||||
                     shell=True)
 | 
			
		||||
@@ -79,12 +100,24 @@ class Bash(Generic):
 | 
			
		||||
    def _get_history_line(self, command_script):
 | 
			
		||||
        return u'{}\n'.format(command_script)
 | 
			
		||||
 | 
			
		||||
    def _script_from_history(self, line):
 | 
			
		||||
        print(line)
 | 
			
		||||
        return line
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Fish(Generic):
 | 
			
		||||
 | 
			
		||||
    def _get_overridden_aliases(self):
 | 
			
		||||
        overridden_aliases = os.environ.get('TF_OVERRIDDEN_ALIASES', '').strip()
 | 
			
		||||
        if overridden_aliases:
 | 
			
		||||
            return [alias.strip() for alias in overridden_aliases.split(',')]
 | 
			
		||||
        else:
 | 
			
		||||
            return ['cd', 'grep', 'ls', 'man', 'open']
 | 
			
		||||
 | 
			
		||||
    def app_alias(self):
 | 
			
		||||
        return ("function fuck -d 'Correct your previous console command'\n"
 | 
			
		||||
        return ("set TF_ALIAS fuck\n"
 | 
			
		||||
                "function fuck -d 'Correct your previous console command'\n"
 | 
			
		||||
                "    set -l exit_code $status\n"
 | 
			
		||||
                "    set -l TF_ALIAS fuck\n"
 | 
			
		||||
                "    set -l eval_script"
 | 
			
		||||
                " (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
 | 
			
		||||
                "    set -l fucked_up_commandd $history[1]\n"
 | 
			
		||||
@@ -96,12 +129,12 @@ class Fish(Generic):
 | 
			
		||||
                "    end\n"
 | 
			
		||||
                "end")
 | 
			
		||||
 | 
			
		||||
    @memoize
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        overridden = self._get_overridden_aliases()
 | 
			
		||||
        proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
 | 
			
		||||
                     shell=True)
 | 
			
		||||
        functions = proc.stdout.read().decode('utf-8').strip().split('\n')
 | 
			
		||||
        return {function: function for function in functions}
 | 
			
		||||
        return {func: func for func in functions if func not in overridden}
 | 
			
		||||
 | 
			
		||||
    def _expand_aliases(self, command_script):
 | 
			
		||||
        aliases = self.get_aliases()
 | 
			
		||||
@@ -137,7 +170,6 @@ class Zsh(Generic):
 | 
			
		||||
            value = value[1:-1]
 | 
			
		||||
        return name, value
 | 
			
		||||
 | 
			
		||||
    @memoize
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
 | 
			
		||||
                     shell=True)
 | 
			
		||||
@@ -153,6 +185,12 @@ class Zsh(Generic):
 | 
			
		||||
    def _get_history_line(self, command_script):
 | 
			
		||||
        return u': {}:0;{}\n'.format(int(time()), command_script)
 | 
			
		||||
 | 
			
		||||
    def _script_from_history(self, line):
 | 
			
		||||
        if ';' in line:
 | 
			
		||||
            return line.split(';', 1)[1]
 | 
			
		||||
        else:
 | 
			
		||||
            return ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tcsh(Generic):
 | 
			
		||||
    def app_alias(self):
 | 
			
		||||
@@ -162,7 +200,6 @@ class Tcsh(Generic):
 | 
			
		||||
        name, value = alias.split("\t", 1)
 | 
			
		||||
        return name, value
 | 
			
		||||
 | 
			
		||||
    @memoize
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
 | 
			
		||||
                     shell=True)
 | 
			
		||||
@@ -219,5 +256,11 @@ def and_(*commands):
 | 
			
		||||
    return _get_shell().and_(*commands)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@memoize
 | 
			
		||||
def get_aliases():
 | 
			
		||||
    return list(_get_shell().get_aliases().keys())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@memoize
 | 
			
		||||
def get_history():
 | 
			
		||||
    return list(_get_shell().get_history())
 | 
			
		||||
 
 | 
			
		||||
@@ -80,12 +80,13 @@ def memoize(fn):
 | 
			
		||||
    @wraps(fn)
 | 
			
		||||
    def wrapper(*args, **kwargs):
 | 
			
		||||
        key = pickle.dumps((args, kwargs))
 | 
			
		||||
        if key not in memo:
 | 
			
		||||
        if key not in memo or memoize.disabled:
 | 
			
		||||
            memo[key] = fn(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        return memo[key]
 | 
			
		||||
 | 
			
		||||
    return wrapper
 | 
			
		||||
memoize.disabled = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_closest(word, possibilities, n=3, cutoff=0.6):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user