mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-11-04 00:52:04 +00:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bcd3154121 | ||
| 
						 | 
					fcc2a1a40a | ||
| 
						 | 
					938f1df035 | ||
| 
						 | 
					2acfea3350 | ||
| 
						 | 
					dd1861955c | ||
| 
						 | 
					ba601644d6 | 
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
from setuptools import setup, find_packages
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
VERSION = '1.32'
 | 
			
		||||
VERSION = '1.33'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
setup(name='thefuck',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,83 +1,94 @@
 | 
			
		||||
import pytest
 | 
			
		||||
import six
 | 
			
		||||
from mock import patch, Mock
 | 
			
		||||
from mock import Mock
 | 
			
		||||
from thefuck import conf
 | 
			
		||||
from tests.utils import Rule
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_default():
 | 
			
		||||
    assert Rule('test', enabled_by_default=True) in conf.DEFAULT_RULES
 | 
			
		||||
    assert Rule('test', enabled_by_default=False) not in conf.DEFAULT_RULES
 | 
			
		||||
    assert Rule('test', enabled_by_default=False) in (conf.DEFAULT_RULES + ['test'])
 | 
			
		||||
@pytest.mark.parametrize('enabled, rules, result', [
 | 
			
		||||
    (True, conf.DEFAULT_RULES, True),
 | 
			
		||||
    (False, conf.DEFAULT_RULES, False),
 | 
			
		||||
    (False, conf.DEFAULT_RULES + ['test'], True)])
 | 
			
		||||
def test_default(enabled, rules, result):
 | 
			
		||||
    assert (Rule('test', enabled_by_default=enabled) in rules) == result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_defaults():
 | 
			
		||||
    with patch('thefuck.conf.load_source', return_value=object()), \
 | 
			
		||||
         patch('thefuck.conf.os.environ', new_callable=lambda: {}):
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def load_source(monkeypatch):
 | 
			
		||||
    mock = Mock()
 | 
			
		||||
    monkeypatch.setattr('thefuck.conf.load_source', mock)
 | 
			
		||||
    return mock
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def environ(monkeypatch):
 | 
			
		||||
    data = {}
 | 
			
		||||
    monkeypatch.setattr('thefuck.conf.os.environ', data)
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.usefixture('environ')
 | 
			
		||||
def test_settings_defaults(load_source):
 | 
			
		||||
    load_source.return_value = object()
 | 
			
		||||
    for key, val in conf.DEFAULT_SETTINGS.items():
 | 
			
		||||
        assert getattr(conf.get_settings(Mock()), key) == val
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_from_file():
 | 
			
		||||
    with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'],
 | 
			
		||||
@pytest.mark.usefixture('environ')
 | 
			
		||||
class TestSettingsFromFile(object):
 | 
			
		||||
    def test_from_file(self, load_source):
 | 
			
		||||
        load_source.return_value = Mock(rules=['test'],
 | 
			
		||||
                                        wait_command=10,
 | 
			
		||||
                                        require_confirmation=True,
 | 
			
		||||
                                                             no_colors=True)), \
 | 
			
		||||
         patch('thefuck.conf.os.environ', new_callable=lambda: {}):
 | 
			
		||||
                                        no_colors=True)
 | 
			
		||||
        settings = conf.get_settings(Mock())
 | 
			
		||||
        assert settings.rules == ['test']
 | 
			
		||||
        assert settings.wait_command == 10
 | 
			
		||||
        assert settings.require_confirmation is True
 | 
			
		||||
        assert settings.no_colors is True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_from_file_with_DEFAULT():
 | 
			
		||||
    with patch('thefuck.conf.load_source', return_value=Mock(rules=conf.DEFAULT_RULES + ['test'],
 | 
			
		||||
    def test_from_file_with_DEFAULT(self, load_source):
 | 
			
		||||
        load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
 | 
			
		||||
                                        wait_command=10,
 | 
			
		||||
                                        require_confirmation=True,
 | 
			
		||||
                                                             no_colors=True)), \
 | 
			
		||||
         patch('thefuck.conf.os.environ', new_callable=lambda: {}):
 | 
			
		||||
                                        no_colors=True)
 | 
			
		||||
        settings = conf.get_settings(Mock())
 | 
			
		||||
        assert settings.rules == conf.DEFAULT_RULES + ['test']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_from_env():
 | 
			
		||||
    with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'],
 | 
			
		||||
                                                             wait_command=10)), \
 | 
			
		||||
         patch('thefuck.conf.os.environ',
 | 
			
		||||
               new_callable=lambda: {'THEFUCK_RULES': 'bash:lisp',
 | 
			
		||||
@pytest.mark.usefixture('load_source')
 | 
			
		||||
class TestSettingsFromEnv(object):
 | 
			
		||||
    def test_from_env(self, environ):
 | 
			
		||||
        environ.update({'THEFUCK_RULES': 'bash:lisp',
 | 
			
		||||
                        'THEFUCK_WAIT_COMMAND': '55',
 | 
			
		||||
                        'THEFUCK_REQUIRE_CONFIRMATION': 'true',
 | 
			
		||||
                                     'THEFUCK_NO_COLORS': 'false'}):
 | 
			
		||||
                        'THEFUCK_NO_COLORS': 'false'})
 | 
			
		||||
        settings = conf.get_settings(Mock())
 | 
			
		||||
        assert settings.rules == ['bash', 'lisp']
 | 
			
		||||
        assert settings.wait_command == 55
 | 
			
		||||
        assert settings.require_confirmation is True
 | 
			
		||||
        assert settings.no_colors is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_settings_from_env_with_DEFAULT():
 | 
			
		||||
    with patch('thefuck.conf.load_source', return_value=Mock()), \
 | 
			
		||||
         patch('thefuck.conf.os.environ', new_callable=lambda: {'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}):
 | 
			
		||||
    def test_from_env_with_DEFAULT(self, environ):
 | 
			
		||||
        environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
 | 
			
		||||
        settings = conf.get_settings(Mock())
 | 
			
		||||
        assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_initialize_settings_file_ignore_if_exists():
 | 
			
		||||
class TestInitializeSettingsFile(object):
 | 
			
		||||
    def test_ignore_if_exists(self):
 | 
			
		||||
        settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
 | 
			
		||||
        user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
 | 
			
		||||
        conf.initialize_settings_file(user_dir_mock)
 | 
			
		||||
        assert settings_path_mock.is_file.call_count == 1
 | 
			
		||||
        assert not settings_path_mock.open.called
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_initialize_settings_file_create_if_exists_not():
 | 
			
		||||
    def test_create_if_doesnt_exists(self):
 | 
			
		||||
        settings_file = six.StringIO()
 | 
			
		||||
        settings_path_mock = Mock(
 | 
			
		||||
            is_file=Mock(return_value=False),
 | 
			
		||||
            open=Mock(return_value=Mock(
 | 
			
		||||
            __exit__=lambda *args: None, __enter__=lambda *args: settings_file
 | 
			
		||||
        )),
 | 
			
		||||
    )
 | 
			
		||||
                __exit__=lambda *args: None, __enter__=lambda *args: settings_file)))
 | 
			
		||||
        user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
 | 
			
		||||
        conf.initialize_settings_file(user_dir_mock)
 | 
			
		||||
        settings_file_contents = settings_file.getvalue()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								tests/test_history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								tests/test_history.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from mock import Mock
 | 
			
		||||
from thefuck.history import History
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestHistory(object):
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def process(self, monkeypatch):
 | 
			
		||||
        Process = Mock()
 | 
			
		||||
        Process.return_value.parent.return_value.pid = 1
 | 
			
		||||
        monkeypatch.setattr('thefuck.history.Process', Process)
 | 
			
		||||
        return Process
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def db(self, monkeypatch):
 | 
			
		||||
        class DBMock(dict):
 | 
			
		||||
            def __init__(self):
 | 
			
		||||
                super(DBMock, self).__init__()
 | 
			
		||||
                self.sync = Mock()
 | 
			
		||||
 | 
			
		||||
            def __call__(self, *args, **kwargs):
 | 
			
		||||
                return self
 | 
			
		||||
 | 
			
		||||
        db = DBMock()
 | 
			
		||||
        monkeypatch.setattr('thefuck.history.shelve.open', db)
 | 
			
		||||
        return db
 | 
			
		||||
 | 
			
		||||
    def test_set(self, db):
 | 
			
		||||
        history = History()
 | 
			
		||||
        history.update(last_command='ls',
 | 
			
		||||
                       last_fixed_command=None)
 | 
			
		||||
        assert db == {'1-last_command': 'ls',
 | 
			
		||||
                      '1-last_fixed_command': None}
 | 
			
		||||
 | 
			
		||||
    def test_get(self, db):
 | 
			
		||||
        history = History()
 | 
			
		||||
        db['1-last_command'] = 'cd ..'
 | 
			
		||||
        assert history.last_command == 'cd ..'
 | 
			
		||||
 | 
			
		||||
    def test_get_without_value(self):
 | 
			
		||||
        history = History()
 | 
			
		||||
        assert history.last_command is None
 | 
			
		||||
@@ -1,115 +1,170 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from subprocess import PIPE
 | 
			
		||||
from pathlib import PosixPath, Path
 | 
			
		||||
from mock import patch, Mock
 | 
			
		||||
from mock import Mock
 | 
			
		||||
from thefuck import main, conf, types
 | 
			
		||||
from tests.utils import Rule, Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_load_rule():
 | 
			
		||||
def test_load_rule(monkeypatch):
 | 
			
		||||
    match = object()
 | 
			
		||||
    get_new_command = object()
 | 
			
		||||
    with patch('thefuck.main.load_source',
 | 
			
		||||
               return_value=Mock(
 | 
			
		||||
                   match=match,
 | 
			
		||||
    load_source = Mock()
 | 
			
		||||
    load_source.return_value = Mock(match=match,
 | 
			
		||||
                                    get_new_command=get_new_command,
 | 
			
		||||
                   enabled_by_default=True)) as load_source:
 | 
			
		||||
                                    enabled_by_default=True)
 | 
			
		||||
    monkeypatch.setattr('thefuck.main.load_source', load_source)
 | 
			
		||||
    assert main.load_rule(Path('/rules/bash.py')) \
 | 
			
		||||
           == Rule('bash', match, get_new_command)
 | 
			
		||||
    load_source.assert_called_once_with('bash', '/rules/bash.py')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_rules():
 | 
			
		||||
    with patch('thefuck.main.Path.glob') as glob, \
 | 
			
		||||
            patch('thefuck.main.load_source',
 | 
			
		||||
                  lambda x, _: Mock(match=x, get_new_command=x,
 | 
			
		||||
                                    enabled_by_default=True)):
 | 
			
		||||
        glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
 | 
			
		||||
        assert list(main.get_rules(
 | 
			
		||||
            Path('~'),
 | 
			
		||||
            Mock(rules=conf.DEFAULT_RULES))) \
 | 
			
		||||
               == [Rule('bash', 'bash', 'bash'),
 | 
			
		||||
@pytest.mark.parametrize('conf_rules, rules', [
 | 
			
		||||
    (conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'),
 | 
			
		||||
                          Rule('lisp', 'lisp', 'lisp'),
 | 
			
		||||
                          Rule('bash', 'bash', 'bash'),
 | 
			
		||||
                   Rule('lisp', 'lisp', 'lisp')]
 | 
			
		||||
        assert list(main.get_rules(
 | 
			
		||||
            Path('~'),
 | 
			
		||||
            Mock(rules=types.RulesNamesList(['bash'])))) \
 | 
			
		||||
               == [Rule('bash', 'bash', 'bash'),
 | 
			
		||||
                   Rule('bash', 'bash', 'bash')]
 | 
			
		||||
                          Rule('lisp', 'lisp', 'lisp')]),
 | 
			
		||||
    (types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'),
 | 
			
		||||
                                      Rule('bash', 'bash', 'bash')])])
 | 
			
		||||
def test_get_rules(monkeypatch, conf_rules, rules):
 | 
			
		||||
    monkeypatch.setattr(
 | 
			
		||||
        'thefuck.main.Path.glob',
 | 
			
		||||
        lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')])
 | 
			
		||||
    monkeypatch.setattr('thefuck.main.load_source',
 | 
			
		||||
                        lambda x, _: Mock(match=x, get_new_command=x,
 | 
			
		||||
                                          enabled_by_default=True))
 | 
			
		||||
    assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_command():
 | 
			
		||||
    with patch('thefuck.main.Popen') as Popen, \
 | 
			
		||||
            patch('thefuck.main.os.environ',
 | 
			
		||||
                  new_callable=lambda: {}), \
 | 
			
		||||
            patch('thefuck.main.wait_output',
 | 
			
		||||
                  return_value=True):
 | 
			
		||||
class TestGetCommand(object):
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def Popen(self, monkeypatch):
 | 
			
		||||
        Popen = Mock()
 | 
			
		||||
        Popen.return_value.stdout.read.return_value = b'stdout'
 | 
			
		||||
        Popen.return_value.stderr.read.return_value = b'stderr'
 | 
			
		||||
        assert main.get_command(Mock(), ['thefuck', 'apt-get',
 | 
			
		||||
                                         'search', 'vim']) \
 | 
			
		||||
        monkeypatch.setattr('thefuck.main.Popen', Popen)
 | 
			
		||||
        return Popen
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def prepare(self, monkeypatch):
 | 
			
		||||
        monkeypatch.setattr('thefuck.main.os.environ', {})
 | 
			
		||||
        monkeypatch.setattr('thefuck.main.wait_output', lambda *_: True)
 | 
			
		||||
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def generic_shell(self, monkeypatch):
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.from_shell', lambda x: x)
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
 | 
			
		||||
 | 
			
		||||
    def test_get_command_calls(self, Popen):
 | 
			
		||||
        assert main.get_command(Mock(), Mock(),
 | 
			
		||||
            ['thefuck', 'apt-get', 'search', 'vim']) \
 | 
			
		||||
               == Command('apt-get search vim', 'stdout', 'stderr')
 | 
			
		||||
        Popen.assert_called_once_with('apt-get search vim',
 | 
			
		||||
                                      shell=True,
 | 
			
		||||
                                      stdout=PIPE,
 | 
			
		||||
                                      stderr=PIPE,
 | 
			
		||||
                                      env={'LANG': 'C'})
 | 
			
		||||
        assert main.get_command(Mock(), ['']) is None
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('history, args, result', [
 | 
			
		||||
        (Mock(), [''], None),
 | 
			
		||||
        (Mock(last_command='ls', last_fixed_command='ls -la'),
 | 
			
		||||
         ['thefuck', 'fuck'], 'ls -la'),
 | 
			
		||||
        (Mock(last_command='ls', last_fixed_command='ls -la'),
 | 
			
		||||
         ['thefuck', 'ls'], 'ls -la'),
 | 
			
		||||
        (Mock(last_command='ls', last_fixed_command=''),
 | 
			
		||||
         ['thefuck', 'ls'], 'ls'),
 | 
			
		||||
        (Mock(last_command='ls', last_fixed_command=''),
 | 
			
		||||
         ['thefuck', 'fuck'], 'ls')])
 | 
			
		||||
    def test_get_command_script(self, history, args, result):
 | 
			
		||||
        if result:
 | 
			
		||||
            assert main.get_command(Mock(), history, args).script == result
 | 
			
		||||
        else:
 | 
			
		||||
            assert main.get_command(Mock(), history, args) is None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_matched_rule(capsys):
 | 
			
		||||
    rules = [Rule('', lambda x, _: x.script == 'cd ..'),
 | 
			
		||||
             Rule('', lambda *_: False),
 | 
			
		||||
             Rule('rule', Mock(side_effect=OSError('Denied')))]
 | 
			
		||||
    assert main.get_matched_rule(Command('ls'),
 | 
			
		||||
                                 rules, Mock(no_colors=True)) is None
 | 
			
		||||
    assert main.get_matched_rule(Command('cd ..'),
 | 
			
		||||
                                 rules, Mock(no_colors=True)) == rules[0]
 | 
			
		||||
    assert capsys.readouterr()[1].split('\n')[0] \
 | 
			
		||||
           == '[WARN] Rule rule:'
 | 
			
		||||
class TestGetMatchedRule(object):
 | 
			
		||||
    def test_no_match(self):
 | 
			
		||||
        assert main.get_matched_rule(
 | 
			
		||||
            Command('ls'), [Rule('', lambda *_: False)],
 | 
			
		||||
            Mock(no_colors=True)) is None
 | 
			
		||||
 | 
			
		||||
    def test_match(self):
 | 
			
		||||
        rule = Rule('', lambda x, _: x.script == 'cd ..')
 | 
			
		||||
        assert main.get_matched_rule(
 | 
			
		||||
            Command('cd ..'), [rule], Mock(no_colors=True)) == rule
 | 
			
		||||
 | 
			
		||||
    def test_when_rule_failed(self, capsys):
 | 
			
		||||
        main.get_matched_rule(
 | 
			
		||||
            Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
 | 
			
		||||
            Mock(no_colors=True))
 | 
			
		||||
        assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_run_rule(capsys):
 | 
			
		||||
    with patch('thefuck.main.confirm', return_value=True):
 | 
			
		||||
class TestRunRule(object):
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def confirm(self, monkeypatch):
 | 
			
		||||
        mock = Mock(return_value=True)
 | 
			
		||||
        monkeypatch.setattr('thefuck.main.confirm', mock)
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    def test_run_rule(self, capsys):
 | 
			
		||||
        main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
 | 
			
		||||
                      None, None)
 | 
			
		||||
                      Command(), Mock(), None)
 | 
			
		||||
        assert capsys.readouterr() == ('new-command\n', '')
 | 
			
		||||
        # With side effect:
 | 
			
		||||
 | 
			
		||||
    def test_run_rule_with_side_effect(self, capsys):
 | 
			
		||||
        side_effect = Mock()
 | 
			
		||||
        settings = Mock()
 | 
			
		||||
        command = Mock()
 | 
			
		||||
        command = Command()
 | 
			
		||||
        main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
 | 
			
		||||
                           side_effect=side_effect),
 | 
			
		||||
                      command, settings)
 | 
			
		||||
                      command, Mock(), settings)
 | 
			
		||||
        assert capsys.readouterr() == ('new-command\n', '')
 | 
			
		||||
        side_effect.assert_called_once_with(command, settings)
 | 
			
		||||
    with patch('thefuck.main.confirm', return_value=False):
 | 
			
		||||
 | 
			
		||||
    def test_hisotry_updated(self):
 | 
			
		||||
        history = Mock()
 | 
			
		||||
        main.run_rule(Rule(get_new_command=lambda *_: 'ls -lah'),
 | 
			
		||||
                      Command('ls'), history, None)
 | 
			
		||||
        history.update.assert_called_once_with(last_command='ls',
 | 
			
		||||
                                               last_fixed_command='ls -lah')
 | 
			
		||||
 | 
			
		||||
    def test_when_not_comfirmed(self, capsys, confirm):
 | 
			
		||||
        confirm.return_value = False
 | 
			
		||||
        main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
 | 
			
		||||
                      None, None)
 | 
			
		||||
                      Command(), Mock(), None)
 | 
			
		||||
        assert capsys.readouterr() == ('', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_confirm(capsys):
 | 
			
		||||
    # When confirmation not required:
 | 
			
		||||
class TestConfirm(object):
 | 
			
		||||
    @pytest.fixture
 | 
			
		||||
    def stdin(self, monkeypatch):
 | 
			
		||||
        mock = Mock(return_value='\n')
 | 
			
		||||
        monkeypatch.setattr('sys.stdin.read', mock)
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    def test_when_not_required(self, capsys):
 | 
			
		||||
        assert main.confirm('command', None, Mock(require_confirmation=False))
 | 
			
		||||
        assert capsys.readouterr() == ('', 'command\n')
 | 
			
		||||
    # With side effect and without confirmation:
 | 
			
		||||
 | 
			
		||||
    def test_with_side_effect_and_without_confirmation(self, capsys):
 | 
			
		||||
        assert main.confirm('command', Mock(), Mock(require_confirmation=False))
 | 
			
		||||
        assert capsys.readouterr() == ('', 'command*\n')
 | 
			
		||||
    # When confirmation required and confirmed:
 | 
			
		||||
    with patch('thefuck.main.sys.stdin.read', return_value='\n'):
 | 
			
		||||
        assert main.confirm(
 | 
			
		||||
            'command', None, Mock(require_confirmation=True,
 | 
			
		||||
 | 
			
		||||
    # `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]')
 | 
			
		||||
        # With side effect:
 | 
			
		||||
        assert main.confirm(
 | 
			
		||||
            'command', Mock(), Mock(require_confirmation=True,
 | 
			
		||||
 | 
			
		||||
    # `stdin` fixture should be applied after `capsys`
 | 
			
		||||
    def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin):
 | 
			
		||||
        assert main.confirm('command', Mock(), Mock(require_confirmation=True,
 | 
			
		||||
                                                    no_colors=True))
 | 
			
		||||
        assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]')
 | 
			
		||||
    # When confirmation required and ctrl+c:
 | 
			
		||||
    with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt):
 | 
			
		||||
        assert not main.confirm('command', None,
 | 
			
		||||
                                Mock(require_confirmation=True,
 | 
			
		||||
 | 
			
		||||
    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')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								tests/test_shells.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/test_shells.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from mock import Mock
 | 
			
		||||
from thefuck import shells
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestGeneric(object):
 | 
			
		||||
    def test_from_shell(self):
 | 
			
		||||
        assert shells.Generic().from_shell('pwd') == 'pwd'
 | 
			
		||||
 | 
			
		||||
    def test_to_shell(self):
 | 
			
		||||
        assert shells.Bash().to_shell('pwd') == 'pwd'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBash(object):
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def Popen(self, monkeypatch):
 | 
			
		||||
        mock = Mock()
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.Popen', mock)
 | 
			
		||||
        mock.return_value.stdout.read.return_value = (
 | 
			
		||||
            b'alias l=\'ls -CF\'\n'
 | 
			
		||||
            b'alias la=\'ls -A\'\n'
 | 
			
		||||
            b'alias ll=\'ls -alF\'')
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('before, after', [
 | 
			
		||||
        ('pwd', 'pwd'),
 | 
			
		||||
        ('ll', 'ls -alF')])
 | 
			
		||||
    def test_from_shell(self, before, after):
 | 
			
		||||
        assert shells.Bash().from_shell(before) == after
 | 
			
		||||
 | 
			
		||||
    def test_to_shell(self):
 | 
			
		||||
        assert shells.Bash().to_shell('pwd') == 'pwd'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestZsh(object):
 | 
			
		||||
    @pytest.fixture(autouse=True)
 | 
			
		||||
    def Popen(self, monkeypatch):
 | 
			
		||||
        mock = Mock()
 | 
			
		||||
        monkeypatch.setattr('thefuck.shells.Popen', mock)
 | 
			
		||||
        mock.return_value.stdout.read.return_value = (
 | 
			
		||||
            b'l=\'ls -CF\'\n'
 | 
			
		||||
            b'la=\'ls -A\'\n'
 | 
			
		||||
            b'll=\'ls -alF\'')
 | 
			
		||||
        return mock
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('before, after', [
 | 
			
		||||
        ('pwd', 'pwd'),
 | 
			
		||||
        ('ll', 'ls -alF')])
 | 
			
		||||
    def test_from_shell(self, before, after):
 | 
			
		||||
        assert shells.Zsh().from_shell(before) == after
 | 
			
		||||
 | 
			
		||||
    def test_to_shell(self):
 | 
			
		||||
        assert shells.Zsh().to_shell('pwd') == 'pwd'
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
import pytest
 | 
			
		||||
from mock import Mock
 | 
			
		||||
from thefuck.utils import sudo_support, wrap_settings
 | 
			
		||||
from thefuck.types import Settings
 | 
			
		||||
from tests.utils import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_wrap_settings():
 | 
			
		||||
@pytest.mark.parametrize('override, old, new', [
 | 
			
		||||
    ({'key': 'val'}, {}, {'key': 'val'}),
 | 
			
		||||
    ({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})])
 | 
			
		||||
def test_wrap_settings(override, old, new):
 | 
			
		||||
    fn = lambda _, settings: settings
 | 
			
		||||
    assert wrap_settings({'key': 'val'})(fn)(None, Settings({})) \
 | 
			
		||||
           == {'key': 'val'}
 | 
			
		||||
    assert wrap_settings({'key': 'new-val'})(fn)(
 | 
			
		||||
        None, Settings({'key': 'val'})) == {'key': 'new-val'}
 | 
			
		||||
    assert wrap_settings(override)(fn)(None, Settings(old)) == new
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sudo_support():
 | 
			
		||||
    fn = Mock(return_value=True, __name__='')
 | 
			
		||||
    assert sudo_support(fn)(Command('sudo ls'), None)
 | 
			
		||||
    fn.assert_called_once_with(Command('ls'), None)
 | 
			
		||||
 | 
			
		||||
    fn.return_value = False
 | 
			
		||||
    assert not sudo_support(fn)(Command('sudo ls'), None)
 | 
			
		||||
 | 
			
		||||
    fn.return_value = 'pwd'
 | 
			
		||||
    assert sudo_support(fn)(Command('sudo ls'), None) == 'sudo pwd'
 | 
			
		||||
 | 
			
		||||
    assert sudo_support(fn)(Command('ls'), None) == 'pwd'
 | 
			
		||||
@pytest.mark.parametrize('return_value, command, called, result', [
 | 
			
		||||
    ('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'),
 | 
			
		||||
    ('ls -lah', 'ls', 'ls', 'ls -lah'),
 | 
			
		||||
    (True, 'sudo ls', 'ls', True),
 | 
			
		||||
    (True, 'ls', 'ls', True),
 | 
			
		||||
    (False, 'sudo ls', 'ls', False),
 | 
			
		||||
    (False, 'ls', 'ls', False)])
 | 
			
		||||
def test_sudo_support(return_value, command, called, result):
 | 
			
		||||
    fn = Mock(return_value=return_value, __name__='')
 | 
			
		||||
    assert sudo_support(fn)(Command(command), None) == result
 | 
			
		||||
    fn.assert_called_once_with(Command(called), None)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								thefuck/history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								thefuck/history.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import os
 | 
			
		||||
import shelve
 | 
			
		||||
from tempfile import gettempdir
 | 
			
		||||
from psutil import Process
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class History(object):
 | 
			
		||||
    """Temporary history of commands/fixed-commands dependent on
 | 
			
		||||
    current shell instance.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self._path = os.path.join(gettempdir(), '.thefuck_history')
 | 
			
		||||
        self._pid = Process(os.getpid()).parent().pid
 | 
			
		||||
        self._db = shelve.open(self._path)
 | 
			
		||||
 | 
			
		||||
    def _prepare_key(self, key):
 | 
			
		||||
        return '{}-{}'.format(self._pid, key)
 | 
			
		||||
 | 
			
		||||
    def update(self, **kwargs):
 | 
			
		||||
        self._db.update({self._prepare_key(k): v for k,v in kwargs.items()})
 | 
			
		||||
        self._db.sync()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, item):
 | 
			
		||||
        return self._db.get(self._prepare_key(item))
 | 
			
		||||
@@ -6,7 +6,8 @@ import os
 | 
			
		||||
import sys
 | 
			
		||||
from psutil import Process, TimeoutExpired
 | 
			
		||||
import colorama
 | 
			
		||||
from . import logs, conf, types
 | 
			
		||||
from .history import History
 | 
			
		||||
from . import logs, conf, types, shells
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_user_dir():
 | 
			
		||||
@@ -59,16 +60,22 @@ def wait_output(settings, popen):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_command(settings, args):
 | 
			
		||||
def get_command(settings, history, args):
 | 
			
		||||
    """Creates command from `args` and executes it."""
 | 
			
		||||
    if sys.version_info[0] < 3:
 | 
			
		||||
        script = ' '.join(arg.decode('utf-8') for arg in args[1:])
 | 
			
		||||
    else:
 | 
			
		||||
        script = ' '.join(args[1:])
 | 
			
		||||
 | 
			
		||||
    if script == 'fuck' or script == history.last_command:
 | 
			
		||||
        script = history.last_fixed_command or history.last_command
 | 
			
		||||
 | 
			
		||||
    if not script:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    script = shells.from_shell(script)
 | 
			
		||||
    history.update(last_command=script,
 | 
			
		||||
                   last_fixed_command=None)
 | 
			
		||||
    result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
 | 
			
		||||
                   env=dict(os.environ, LANG='C'))
 | 
			
		||||
    if wait_output(settings, result):
 | 
			
		||||
@@ -101,20 +108,17 @@ def confirm(new_command, side_effect, settings):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_rule(rule, command, settings):
 | 
			
		||||
def run_rule(rule, command, history, settings):
 | 
			
		||||
    """Runs command from rule for passed command."""
 | 
			
		||||
    new_command = rule.get_new_command(command, settings)
 | 
			
		||||
    new_command = shells.to_shell(rule.get_new_command(command, settings))
 | 
			
		||||
    if confirm(new_command, rule.side_effect, settings):
 | 
			
		||||
        if rule.side_effect:
 | 
			
		||||
            rule.side_effect(command, settings)
 | 
			
		||||
        history.update(last_command=command.script,
 | 
			
		||||
                       last_fixed_command=new_command)
 | 
			
		||||
        print(new_command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_second_run(command):
 | 
			
		||||
    """Is it the second run of `fuck`?"""
 | 
			
		||||
    return command.script.startswith('fuck')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def alias():
 | 
			
		||||
    print("\nalias fuck='eval $(thefuck $(fc -ln -1))'\n")
 | 
			
		||||
 | 
			
		||||
@@ -123,17 +127,14 @@ def main():
 | 
			
		||||
    colorama.init()
 | 
			
		||||
    user_dir = setup_user_dir()
 | 
			
		||||
    settings = conf.get_settings(user_dir)
 | 
			
		||||
    history = History()
 | 
			
		||||
 | 
			
		||||
    command = get_command(settings, sys.argv)
 | 
			
		||||
    command = get_command(settings, history, sys.argv)
 | 
			
		||||
    if command:
 | 
			
		||||
        if is_second_run(command):
 | 
			
		||||
            logs.failed("Can't fuck twice", settings)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        rules = get_rules(user_dir, settings)
 | 
			
		||||
        matched_rule = get_matched_rule(command, rules, settings)
 | 
			
		||||
        if matched_rule:
 | 
			
		||||
            run_rule(matched_rule, command, settings)
 | 
			
		||||
            run_rule(matched_rule, command, history, settings)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    logs.failed('No fuck given', settings)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								thefuck/shells.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								thefuck/shells.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
"""Module with shell specific actions, each shell class should
 | 
			
		||||
implement `from_shell` and `to_shell` methods.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
import os
 | 
			
		||||
from psutil import Process
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FNULL = open(os.devnull, 'w')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Generic(object):
 | 
			
		||||
    def _get_aliases(self):
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    def _expand_aliases(self, command_script):
 | 
			
		||||
        aliases = self._get_aliases()
 | 
			
		||||
        binary = command_script.split(' ')[0]
 | 
			
		||||
        if binary in aliases:
 | 
			
		||||
            return command_script.replace(binary, aliases[binary], 1)
 | 
			
		||||
        else:
 | 
			
		||||
            return command_script
 | 
			
		||||
 | 
			
		||||
    def from_shell(self, command_script):
 | 
			
		||||
        """Prepares command before running in app."""
 | 
			
		||||
        return self._expand_aliases(command_script)
 | 
			
		||||
 | 
			
		||||
    def to_shell(self, command_script):
 | 
			
		||||
        """Prepares command for running in shell."""
 | 
			
		||||
        return command_script
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bash(Generic):
 | 
			
		||||
    def _parse_alias(self, alias):
 | 
			
		||||
        name, value = alias.replace('alias ', '', 1).split('=', 1)
 | 
			
		||||
        if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
 | 
			
		||||
            value = value[1:-1]
 | 
			
		||||
        return name, value
 | 
			
		||||
 | 
			
		||||
    def _get_aliases(self):
 | 
			
		||||
        proc = Popen('bash -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
 | 
			
		||||
        return dict(
 | 
			
		||||
            self._parse_alias(alias)
 | 
			
		||||
            for alias in proc.stdout.read().decode('utf-8').split('\n')
 | 
			
		||||
            if alias)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Zsh(Generic):
 | 
			
		||||
    def _parse_alias(self, alias):
 | 
			
		||||
        name, value = alias.split('=', 1)
 | 
			
		||||
        if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
 | 
			
		||||
            value = value[1:-1]
 | 
			
		||||
        return name, value
 | 
			
		||||
 | 
			
		||||
    def _get_aliases(self):
 | 
			
		||||
        proc = Popen('zsh -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
 | 
			
		||||
        return dict(
 | 
			
		||||
            self._parse_alias(alias)
 | 
			
		||||
            for alias in proc.stdout.read().decode('utf-8').split('\n')
 | 
			
		||||
            if alias)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
shells = defaultdict(lambda: Generic(), {
 | 
			
		||||
    'bash': Bash(),
 | 
			
		||||
    'zsh': Zsh()})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_shell():
 | 
			
		||||
    shell = Process(os.getpid()).parent().cmdline()[0]
 | 
			
		||||
    return shells[shell]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_shell(command):
 | 
			
		||||
    return _get_shell().from_shell(command)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_shell(command):
 | 
			
		||||
    return _get_shell().to_shell(command)
 | 
			
		||||
		Reference in New Issue
	
	Block a user