From abe287a52b748ff8f8760aa2c5bad2c476ee1a0c Mon Sep 17 00:00:00 2001 From: nvbn Date: Sat, 23 Jan 2016 05:06:22 +0300 Subject: [PATCH 1/6] #N/A: Split `shells` module --- tests/shells/__init__.py | 0 tests/shells/conftest.py | 21 +++ tests/shells/test_bash.py | 60 +++++++ tests/shells/test_fish.py | 86 +++++++++ tests/shells/test_generic.py | 44 +++++ tests/shells/test_zsh.py | 62 +++++++ tests/test_shells.py | 260 --------------------------- thefuck/shells.py | 334 ----------------------------------- thefuck/shells/__init__.py | 75 ++++++++ thefuck/shells/bash.py | 42 +++++ thefuck/shells/fish.py | 68 +++++++ thefuck/shells/generic.py | 89 ++++++++++ thefuck/shells/tcsh.py | 34 ++++ thefuck/shells/zsh.py | 43 +++++ 14 files changed, 624 insertions(+), 594 deletions(-) create mode 100644 tests/shells/__init__.py create mode 100644 tests/shells/conftest.py create mode 100644 tests/shells/test_bash.py create mode 100644 tests/shells/test_fish.py create mode 100644 tests/shells/test_generic.py create mode 100644 tests/shells/test_zsh.py delete mode 100644 tests/test_shells.py delete mode 100644 thefuck/shells.py create mode 100644 thefuck/shells/__init__.py create mode 100644 thefuck/shells/bash.py create mode 100644 thefuck/shells/fish.py create mode 100644 thefuck/shells/generic.py create mode 100644 thefuck/shells/tcsh.py create mode 100644 thefuck/shells/zsh.py diff --git a/tests/shells/__init__.py b/tests/shells/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/shells/conftest.py b/tests/shells/conftest.py new file mode 100644 index 00000000..8fcbced2 --- /dev/null +++ b/tests/shells/conftest.py @@ -0,0 +1,21 @@ +import pytest + + +@pytest.fixture +def builtins_open(mocker): + return mocker.patch('six.moves.builtins.open') + + +@pytest.fixture +def isfile(mocker): + return mocker.patch('os.path.isfile', return_value=True) + + +@pytest.fixture +@pytest.mark.usefixtures('isfile') +def history_lines(mocker): + def aux(lines): + mock = mocker.patch('io.open') + mock.return_value.__enter__ \ + .return_value.readlines.return_value = lines + return aux diff --git a/tests/shells/test_bash.py b/tests/shells/test_bash.py new file mode 100644 index 00000000..d4b57c7e --- /dev/null +++ b/tests/shells/test_bash.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import pytest +from thefuck.shells import Bash + + +@pytest.mark.usefixtures('isfile') +class TestBash(object): + @pytest.fixture + def shell(self): + return Bash() + + @pytest.fixture(autouse=True) + def Popen(self, mocker): + mock = mocker.patch('thefuck.shells.bash.Popen') + mock.return_value.stdout.read.return_value = ( + b'alias fuck=\'eval $(thefuck $(fc -ln -1))\'\n' + 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'), + ('fuck', 'eval $(thefuck $(fc -ln -1))'), + ('awk', 'awk'), + ('ll', 'ls -alF')]) + def test_from_shell(self, before, after, shell): + assert shell.from_shell(before) == after + + def test_to_shell(self, shell): + assert shell.to_shell('pwd') == 'pwd' + + @pytest.mark.parametrize('entry, entry_utf8', [ + ('ls', 'ls\n'), + (u'echo café', 'echo café\n')]) + def test_put_to_history(self, entry, entry_utf8, builtins_open, shell): + shell.put_to_history(entry) + builtins_open.return_value.__enter__.return_value. \ + write.assert_called_once_with(entry_utf8) + + def test_and_(self, shell): + assert shell.and_('ls', 'cd') == 'ls && cd' + + def test_get_aliases(self, shell): + assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', + 'l': 'ls -CF', + 'la': 'ls -A', + 'll': 'ls -alF'} + + def test_app_alias(self, shell): + assert 'alias fuck' in shell.app_alias('fuck') + assert 'alias FUCK' in shell.app_alias('FUCK') + assert 'thefuck' in shell.app_alias('fuck') + assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') + assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') + + def test_get_history(self, history_lines, shell): + history_lines(['ls', 'rm']) + assert list(shell.get_history()) == ['ls', 'rm'] diff --git a/tests/shells/test_fish.py b/tests/shells/test_fish.py new file mode 100644 index 00000000..2e1a296c --- /dev/null +++ b/tests/shells/test_fish.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +import pytest +from thefuck.shells import Fish + + +@pytest.mark.usefixtures('isfile') +class TestFish(object): + @pytest.fixture + def shell(self): + return Fish() + + @pytest.fixture(autouse=True) + def Popen(self, mocker): + mock = mocker.patch('thefuck.shells.fish.Popen') + mock.return_value.stdout.read.return_value = ( + 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.fish.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"'), + ('ls', 'ls')]) # Fish has no aliases but functions + def test_from_shell(self, before, after, shell): + assert shell.from_shell(before) == after + + def test_to_shell(self, shell): + assert shell.to_shell('pwd') == 'pwd' + + @pytest.mark.parametrize('entry, entry_utf8', [ + ('ls', '- cmd: ls\n when: 1430707243\n'), + (u'echo café', '- cmd: echo café\n when: 1430707243\n')]) + def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell): + mocker.patch('thefuck.shells.fish.time', + return_value=1430707243.3517463) + shell.put_to_history(entry) + builtins_open.return_value.__enter__.return_value. \ + write.assert_called_once_with(entry_utf8) + + def test_and_(self, shell): + assert shell.and_('foo', 'bar') == 'foo; and bar' + + def test_get_aliases(self, shell): + assert shell.get_aliases() == {'fish_config': 'fish_config', + 'fuck': 'fuck', + 'funced': 'funced', + 'funcsave': 'funcsave', + 'history': 'history', + 'll': 'll', + 'math': 'math', + 'popd': 'popd', + 'pushd': 'pushd', + 'ruby': 'ruby'} + + def test_app_alias(self, shell): + assert 'function fuck' in shell.app_alias('fuck') + assert 'function FUCK' in shell.app_alias('FUCK') + assert 'thefuck' in shell.app_alias('fuck') + assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') + assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') + + def test_get_history(self, history_lines, shell): + history_lines(['- cmd: ls', ' when: 1432613911', + '- cmd: rm', ' when: 1432613916']) + assert list(shell.get_history()) == ['ls', 'rm'] diff --git a/tests/shells/test_generic.py b/tests/shells/test_generic.py new file mode 100644 index 00000000..a135ff8d --- /dev/null +++ b/tests/shells/test_generic.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +import pytest +from thefuck.shells import Generic + + +class TestGeneric(object): + @pytest.fixture + def shell(self): + return Generic() + + def test_from_shell(self, shell): + assert shell.from_shell('pwd') == 'pwd' + + def test_to_shell(self, shell): + assert shell.to_shell('pwd') == 'pwd' + + def test_put_to_history(self, builtins_open, shell): + assert shell.put_to_history('ls') is None + assert shell.put_to_history(u'echo café') is None + assert builtins_open.call_count == 0 + + def test_and_(self, shell): + assert shell.and_('ls', 'cd') == 'ls && cd' + + def test_get_aliases(self, shell): + assert shell.get_aliases() == {} + + def test_app_alias(self, shell): + assert 'alias fuck' in shell.app_alias('fuck') + assert 'alias FUCK' in shell.app_alias('FUCK') + assert 'thefuck' in shell.app_alias('fuck') + assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') + assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') + + 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()) == [] + + def test_split_command(self, shell): + assert shell.split_command('ls') == ['ls'] + assert shell.split_command(u'echo café') == [u'echo', u'café'] diff --git a/tests/shells/test_zsh.py b/tests/shells/test_zsh.py new file mode 100644 index 00000000..fd327ee7 --- /dev/null +++ b/tests/shells/test_zsh.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import pytest +from thefuck.shells.zsh import Zsh + + +@pytest.mark.usefixtures('isfile') +class TestZsh(object): + @pytest.fixture + def shell(self): + return Zsh() + + @pytest.fixture(autouse=True) + def Popen(self, mocker): + mock = mocker.patch('thefuck.shells.zsh.Popen') + mock.return_value.stdout.read.return_value = ( + b'fuck=\'eval $(thefuck $(fc -ln -1 | tail -n 1))\'\n' + b'l=\'ls -CF\'\n' + b'la=\'ls -A\'\n' + b'll=\'ls -alF\'') + return mock + + @pytest.mark.parametrize('before, after', [ + ('fuck', 'eval $(thefuck $(fc -ln -1 | tail -n 1))'), + ('pwd', 'pwd'), + ('ll', 'ls -alF')]) + def test_from_shell(self, before, after, shell): + assert shell.from_shell(before) == after + + def test_to_shell(self, shell): + assert shell.to_shell('pwd') == 'pwd' + + @pytest.mark.parametrize('entry, entry_utf8', [ + ('ls', ': 1430707243:0;ls\n'), + (u'echo café', ': 1430707243:0;echo café\n')]) + def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell): + mocker.patch('thefuck.shells.zsh.time', + return_value=1430707243.3517463) + shell.put_to_history(entry) + builtins_open.return_value.__enter__.return_value. \ + write.assert_called_once_with(entry_utf8) + + def test_and_(self, shell): + assert shell.and_('ls', 'cd') == 'ls && cd' + + def test_get_aliases(self, shell): + assert shell.get_aliases() == { + 'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))', + 'l': 'ls -CF', + 'la': 'ls -A', + 'll': 'ls -alF'} + + def test_app_alias(self, shell): + assert 'alias fuck' in shell.app_alias('fuck') + assert 'alias FUCK' in shell.app_alias('FUCK') + assert 'thefuck' in shell.app_alias('fuck') + assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') + assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') + + def test_get_history(self, history_lines, shell): + history_lines([': 1432613911:0;ls', ': 1432613916:0;rm']) + assert list(shell.get_history()) == ['ls', 'rm'] diff --git a/tests/test_shells.py b/tests/test_shells.py deleted file mode 100644 index 1cea2fb9..00000000 --- a/tests/test_shells.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- - -import pytest -from thefuck import shells - - -@pytest.fixture -def builtins_open(mocker): - return mocker.patch('six.moves.builtins.open') - - -@pytest.fixture -def isfile(mocker): - return mocker.patch('os.path.isfile', return_value=True) - - -@pytest.fixture -@pytest.mark.usefixtures('isfile') -def history_lines(mocker): - def aux(lines): - mock = mocker.patch('io.open') - mock.return_value.__enter__\ - .return_value.readlines.return_value = lines - return aux - - -class TestGeneric(object): - @pytest.fixture - def shell(self): - return shells.Generic() - - def test_from_shell(self, shell): - assert shell.from_shell('pwd') == 'pwd' - - def test_to_shell(self, shell): - assert shell.to_shell('pwd') == 'pwd' - - def test_put_to_history(self, builtins_open, shell): - assert shell.put_to_history('ls') is None - assert shell.put_to_history(u'echo café') is None - assert builtins_open.call_count == 0 - - def test_and_(self, shell): - assert shell.and_('ls', 'cd') == 'ls && cd' - - def test_get_aliases(self, shell): - assert shell.get_aliases() == {} - - def test_app_alias(self, shell): - assert 'alias fuck' in shell.app_alias('fuck') - assert 'alias FUCK' in shell.app_alias('FUCK') - assert 'thefuck' in shell.app_alias('fuck') - assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') - assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') - - 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()) == [] - - def test_split_command(self, shell): - assert shell.split_command('ls') == ['ls'] - assert shell.split_command(u'echo café') == [u'echo', u'café'] - - -@pytest.mark.usefixtures('isfile') -class TestBash(object): - @pytest.fixture - def shell(self): - return shells.Bash() - - @pytest.fixture(autouse=True) - def Popen(self, mocker): - mock = mocker.patch('thefuck.shells.Popen') - mock.return_value.stdout.read.return_value = ( - b'alias fuck=\'eval $(thefuck $(fc -ln -1))\'\n' - 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'), - ('fuck', 'eval $(thefuck $(fc -ln -1))'), - ('awk', 'awk'), - ('ll', 'ls -alF')]) - def test_from_shell(self, before, after, shell): - assert shell.from_shell(before) == after - - def test_to_shell(self, shell): - assert shell.to_shell('pwd') == 'pwd' - - @pytest.mark.parametrize('entry, entry_utf8', [ - ('ls', 'ls\n'), - (u'echo café', 'echo café\n')]) - def test_put_to_history(self, entry, entry_utf8, builtins_open, shell): - shell.put_to_history(entry) - builtins_open.return_value.__enter__.return_value. \ - write.assert_called_once_with(entry_utf8) - - def test_and_(self, shell): - assert shell.and_('ls', 'cd') == 'ls && cd' - - def test_get_aliases(self, shell): - assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', - 'l': 'ls -CF', - 'la': 'ls -A', - 'll': 'ls -alF'} - - def test_app_alias(self, shell): - assert 'alias fuck' in shell.app_alias('fuck') - assert 'alias FUCK' in shell.app_alias('FUCK') - assert 'thefuck' in shell.app_alias('fuck') - assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') - assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') - - 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): - @pytest.fixture - def shell(self): - return shells.Fish() - - @pytest.fixture(autouse=True) - def Popen(self, mocker): - mock = mocker.patch('thefuck.shells.Popen') - mock.return_value.stdout.read.return_value = ( - 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"'), - ('ls', 'ls')]) # Fish has no aliases but functions - def test_from_shell(self, before, after, shell): - assert shell.from_shell(before) == after - - def test_to_shell(self, shell): - assert shell.to_shell('pwd') == 'pwd' - - @pytest.mark.parametrize('entry, entry_utf8', [ - ('ls', '- cmd: ls\n when: 1430707243\n'), - (u'echo café', '- cmd: echo café\n when: 1430707243\n')]) - def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell): - mocker.patch('thefuck.shells.time', - return_value=1430707243.3517463) - shell.put_to_history(entry) - builtins_open.return_value.__enter__.return_value. \ - write.assert_called_once_with(entry_utf8) - - def test_and_(self, shell): - assert shell.and_('foo', 'bar') == 'foo; and bar' - - def test_get_aliases(self, shell): - assert shell.get_aliases() == {'fish_config': 'fish_config', - 'fuck': 'fuck', - 'funced': 'funced', - 'funcsave': 'funcsave', - 'history': 'history', - 'll': 'll', - 'math': 'math', - 'popd': 'popd', - 'pushd': 'pushd', - 'ruby': 'ruby'} - - def test_app_alias(self, shell): - assert 'function fuck' in shell.app_alias('fuck') - assert 'function FUCK' in shell.app_alias('FUCK') - assert 'thefuck' in shell.app_alias('fuck') - assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') - assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') - - def test_get_history(self, history_lines, shell): - history_lines(['- cmd: ls', ' when: 1432613911', - '- cmd: rm', ' when: 1432613916']) - assert list(shell.get_history()) == ['ls', 'rm'] - - -@pytest.mark.usefixtures('isfile') -class TestZsh(object): - @pytest.fixture - def shell(self): - return shells.Zsh() - - @pytest.fixture(autouse=True) - def Popen(self, mocker): - mock = mocker.patch('thefuck.shells.Popen') - mock.return_value.stdout.read.return_value = ( - b'fuck=\'eval $(thefuck $(fc -ln -1 | tail -n 1))\'\n' - b'l=\'ls -CF\'\n' - b'la=\'ls -A\'\n' - b'll=\'ls -alF\'') - return mock - - @pytest.mark.parametrize('before, after', [ - ('fuck', 'eval $(thefuck $(fc -ln -1 | tail -n 1))'), - ('pwd', 'pwd'), - ('ll', 'ls -alF')]) - def test_from_shell(self, before, after, shell): - assert shell.from_shell(before) == after - - def test_to_shell(self, shell): - assert shell.to_shell('pwd') == 'pwd' - - @pytest.mark.parametrize('entry, entry_utf8', [ - ('ls', ': 1430707243:0;ls\n'), - (u'echo café', ': 1430707243:0;echo café\n')]) - def test_put_to_history(self, entry, entry_utf8, builtins_open, mocker, shell): - mocker.patch('thefuck.shells.time', - return_value=1430707243.3517463) - shell.put_to_history(entry) - builtins_open.return_value.__enter__.return_value. \ - write.assert_called_once_with(entry_utf8) - - def test_and_(self, shell): - assert shell.and_('ls', 'cd') == 'ls && cd' - - def test_get_aliases(self, shell): - assert shell.get_aliases() == { - 'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))', - 'l': 'ls -CF', - 'la': 'ls -A', - 'll': 'ls -alF'} - - def test_app_alias(self, shell): - assert 'alias fuck' in shell.app_alias('fuck') - assert 'alias FUCK' in shell.app_alias('FUCK') - assert 'thefuck' in shell.app_alias('fuck') - assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck') - assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck') - - def test_get_history(self, history_lines, shell): - history_lines([': 1432613911:0;ls', ': 1432613916:0;rm']) - assert list(shell.get_history()) == ['ls', 'rm'] diff --git a/thefuck/shells.py b/thefuck/shells.py deleted file mode 100644 index d14a2d77..00000000 --- a/thefuck/shells.py +++ /dev/null @@ -1,334 +0,0 @@ -"""Module with shell specific actions, each shell class should -implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and -`get_aliases` methods. - -""" -from collections import defaultdict -from psutil import Process -from subprocess import Popen, PIPE -from time import time -import io -import os -import shlex -import sys -import six -from .utils import DEVNULL, memoize, cache -from .conf import settings -from . import logs - - -class Generic(object): - def get_aliases(self): - return {} - - def _expand_aliases(self, command_script): - aliases = self.get_aliases() - binary = command_script.split(' ')[0] - if binary in aliases: - return command_script.replace(binary, aliases[binary], 1) - else: - return command_script - - def from_shell(self, command_script): - """Prepares command before running in app.""" - return self._expand_aliases(command_script) - - def to_shell(self, command_script): - """Prepares command for running in shell.""" - return command_script - - def app_alias(self, fuck): - return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \ - "thefuck $(fc -ln -1))'".format(fuck) - - def _get_history_file_name(self): - return '' - - def _get_history_line(self, command_script): - return '' - - def put_to_history(self, command_script): - """Puts command script to shell history.""" - history_file_name = self._get_history_file_name() - if os.path.isfile(history_file_name): - with open(history_file_name, 'a') as history: - entry = self._get_history_line(command_script) - if six.PY2: - history.write(entry.encode('utf-8')) - else: - history.write(entry) - - 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_file: - - lines = history_file.readlines() - if settings.history_limit: - lines = lines[-settings.history_limit:] - - for line in lines: - prepared = self._script_from_history(line) \ - .strip() - if prepared: - yield prepared - - def and_(self, *commands): - return u' && '.join(commands) - - def how_to_configure(self): - return - - def split_command(self, command): - """Split the command using shell-like syntax.""" - if six.PY2: - return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))] - return shlex.split(command) - - def quote(self, s): - """Return a shell-escaped version of the string s.""" - - if six.PY2: - from pipes import quote - else: - from shlex import quote - - return quote(s) - - def _script_from_history(self, line): - return line - - -class Bash(Generic): - def app_alias(self, fuck): - return "alias {0}='eval " \ - "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 thefuck $(fc -ln -1));" \ - " history -r'".format(fuck) - - 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 - - @memoize - @cache('.bashrc', '.bash_profile') - def get_aliases(self): - proc = Popen(['bash', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) - return dict( - self._parse_alias(alias) - for alias in proc.stdout.read().decode('utf-8').split('\n') - if alias and '=' in alias) - - def _get_history_file_name(self): - return os.environ.get("HISTFILE", - os.path.expanduser('~/.bash_history')) - - def _get_history_line(self, command_script): - return u'{}\n'.format(command_script) - - def how_to_configure(self): - if os.path.join(os.path.expanduser('~'), '.bashrc'): - config = '~/.bashrc' - elif os.path.join(os.path.expanduser('~'), '.bash_profile'): - config = '~/.bashrc' - else: - config = 'bash config' - return 'eval $(thefuck --alias)', config - - -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, fuck): - return ('function {0} -d "Correct your previous console command"\n' - ' set -l exit_code $status\n' - ' set -l fucked_up_command $history[1]\n' - ' env TF_ALIAS={0} PYTHONIOENCODING=utf-8' - ' thefuck $fucked_up_command | read -l unfucked_command\n' - ' if [ "$unfucked_command" != "" ]\n' - ' eval $unfucked_command\n' - ' if test $exit_code -ne 0\n' - ' history --delete $fucked_up_command\n' - ' history --merge ^ /dev/null\n' - ' return 0\n' - ' end\n' - ' end\n' - 'end').format(fuck) - - @memoize - @cache('.config/fish/config.fish', '.config/fish/functions') - def get_aliases(self): - overridden = self._get_overridden_aliases() - proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL) - functions = proc.stdout.read().decode('utf-8').strip().split('\n') - return {func: func for func in functions if func not in overridden} - - def _expand_aliases(self, command_script): - aliases = self.get_aliases() - binary = command_script.split(' ')[0] - if binary in aliases: - return u'fish -ic "{}"'.format(command_script.replace('"', r'\"')) - else: - return command_script - - def from_shell(self, command_script): - """Prepares command before running in app.""" - return self._expand_aliases(command_script) - - def _get_history_file_name(self): - return os.path.expanduser('~/.config/fish/fish_history') - - def _get_history_line(self, command_script): - return u'- cmd: {}\n when: {}\n'.format(command_script, int(time())) - - def _script_from_history(self, line): - if '- cmd: ' in line: - return line.split('- cmd: ', 1)[1] - else: - return '' - - def and_(self, *commands): - return u'; and '.join(commands) - - def how_to_configure(self): - return 'eval thefuck --alias', '~/.config/fish/config.fish' - - -class Zsh(Generic): - def app_alias(self, fuck): - return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8" \ - " thefuck $(fc -ln -1 | tail -n 1));" \ - " fc -R'".format(fuck) - - 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 - - @memoize - @cache('.zshrc') - def get_aliases(self): - proc = Popen(['zsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) - return dict( - self._parse_alias(alias) - for alias in proc.stdout.read().decode('utf-8').split('\n') - if alias and '=' in alias) - - def _get_history_file_name(self): - return os.environ.get("HISTFILE", - os.path.expanduser('~/.zsh_history')) - - def _get_history_line(self, command_script): - return u': {}:0;{}\n'.format(int(time()), command_script) - - def _script_from_history(self, line): - if ';' in line: - return line.split(';', 1)[1] - else: - return '' - - def how_to_configure(self): - return 'eval $(thefuck --alias)', '~/.zshrc' - - -class Tcsh(Generic): - def app_alias(self, fuck): - return ("alias {0} 'setenv TF_ALIAS {0} && " - "set fucked_cmd=`history -h 2 | head -n 1` && " - "eval `thefuck ${{fucked_cmd}}`'").format(fuck) - - def _parse_alias(self, alias): - name, value = alias.split("\t", 1) - return name, value - - @memoize - def get_aliases(self): - proc = Popen(['tcsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) - return dict( - self._parse_alias(alias) - for alias in proc.stdout.read().decode('utf-8').split('\n') - if alias and '\t' in alias) - - def _get_history_file_name(self): - return os.environ.get("HISTFILE", - os.path.expanduser('~/.history')) - - def _get_history_line(self, command_script): - return u'#+{}\n{}\n'.format(int(time()), command_script) - - def how_to_configure(self): - return 'eval `thefuck --alias`', '~/.tcshrc' - - -shells = defaultdict(Generic, { - 'bash': Bash(), - 'fish': Fish(), - 'zsh': Zsh(), - 'csh': Tcsh(), - 'tcsh': Tcsh()}) - - -@memoize -def _get_shell(): - try: - shell = Process(os.getpid()).parent().name() - except TypeError: - shell = Process(os.getpid()).parent.name - return shells[shell] - - -def from_shell(command): - return _get_shell().from_shell(command) - - -def to_shell(command): - return _get_shell().to_shell(command) - - -def app_alias(alias): - return _get_shell().app_alias(alias) - - -def thefuck_alias(): - return os.environ.get('TF_ALIAS', 'fuck') - - -def put_to_history(command): - try: - return _get_shell().put_to_history(command) - except IOError: - logs.exception("Can't update history", sys.exc_info()) - - -def and_(*commands): - return _get_shell().and_(*commands) - - -def get_aliases(): - return list(_get_shell().get_aliases().keys()) - - -def split_command(command): - return _get_shell().split_command(command) - - -def quote(s): - return _get_shell().quote(s) - - -@memoize -def get_history(): - return list(_get_shell().get_history()) - - -def how_to_configure(): - return _get_shell().how_to_configure() diff --git a/thefuck/shells/__init__.py b/thefuck/shells/__init__.py new file mode 100644 index 00000000..af9142b1 --- /dev/null +++ b/thefuck/shells/__init__.py @@ -0,0 +1,75 @@ +from collections import defaultdict +from psutil import Process +import os +import sys +from ..utils import memoize +from .. import logs +from .bash import Bash +from .fish import Fish +from .generic import Generic +from .tcsh import Tcsh +from .zsh import Zsh + +shells = defaultdict(Generic, + bash=Bash(), + fish=Fish(), + zsh=Zsh(), + csh=Tcsh(), + tcsh=Tcsh()) + + +@memoize +def _get_shell(): + try: + shell = Process(os.getpid()).parent().name() + except TypeError: + shell = Process(os.getpid()).parent.name + return shells[shell] + + +def from_shell(command): + return _get_shell().from_shell(command) + + +def to_shell(command): + return _get_shell().to_shell(command) + + +def app_alias(alias): + return _get_shell().app_alias(alias) + + +def thefuck_alias(): + return os.environ.get('TF_ALIAS', 'fuck') + + +def put_to_history(command): + try: + return _get_shell().put_to_history(command) + except IOError: + logs.exception("Can't update history", sys.exc_info()) + + +def and_(*commands): + return _get_shell().and_(*commands) + + +def get_aliases(): + return list(_get_shell().get_aliases().keys()) + + +def split_command(command): + return _get_shell().split_command(command) + + +def quote(s): + return _get_shell().quote(s) + + +@memoize +def get_history(): + return list(_get_shell().get_history()) + + +def how_to_configure(): + return _get_shell().how_to_configure() diff --git a/thefuck/shells/bash.py b/thefuck/shells/bash.py new file mode 100644 index 00000000..9e7a1c4b --- /dev/null +++ b/thefuck/shells/bash.py @@ -0,0 +1,42 @@ +from subprocess import Popen, PIPE +import os +from ..utils import DEVNULL, memoize, cache +from .generic import Generic + + +class Bash(Generic): + def app_alias(self, fuck): + return "alias {0}='eval " \ + "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 thefuck $(fc -ln -1));" \ + " history -r'".format(fuck) + + 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 + + @memoize + @cache('.bashrc', '.bash_profile') + def get_aliases(self): + proc = Popen(['bash', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) + return dict( + self._parse_alias(alias) + for alias in proc.stdout.read().decode('utf-8').split('\n') + if alias and '=' in alias) + + def _get_history_file_name(self): + return os.environ.get("HISTFILE", + os.path.expanduser('~/.bash_history')) + + def _get_history_line(self, command_script): + return u'{}\n'.format(command_script) + + def how_to_configure(self): + if os.path.join(os.path.expanduser('~'), '.bashrc'): + config = '~/.bashrc' + elif os.path.join(os.path.expanduser('~'), '.bash_profile'): + config = '~/.bashrc' + else: + config = 'bash config' + return 'eval $(thefuck --alias)', config diff --git a/thefuck/shells/fish.py b/thefuck/shells/fish.py new file mode 100644 index 00000000..b617439e --- /dev/null +++ b/thefuck/shells/fish.py @@ -0,0 +1,68 @@ +from subprocess import Popen, PIPE +from time import time +import os +from ..utils import DEVNULL, memoize, cache +from .generic import Generic + + +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, fuck): + return ('function {0} -d "Correct your previous console command"\n' + ' set -l exit_code $status\n' + ' set -l fucked_up_command $history[1]\n' + ' env TF_ALIAS={0} PYTHONIOENCODING=utf-8' + ' thefuck $fucked_up_command | read -l unfucked_command\n' + ' if [ "$unfucked_command" != "" ]\n' + ' eval $unfucked_command\n' + ' if test $exit_code -ne 0\n' + ' history --delete $fucked_up_command\n' + ' history --merge ^ /dev/null\n' + ' return 0\n' + ' end\n' + ' end\n' + 'end').format(fuck) + + @memoize + @cache('.config/fish/config.fish', '.config/fish/functions') + def get_aliases(self): + overridden = self._get_overridden_aliases() + proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL) + functions = proc.stdout.read().decode('utf-8').strip().split('\n') + return {func: func for func in functions if func not in overridden} + + def _expand_aliases(self, command_script): + aliases = self.get_aliases() + binary = command_script.split(' ')[0] + if binary in aliases: + return u'fish -ic "{}"'.format(command_script.replace('"', r'\"')) + else: + return command_script + + def from_shell(self, command_script): + """Prepares command before running in app.""" + return self._expand_aliases(command_script) + + def _get_history_file_name(self): + return os.path.expanduser('~/.config/fish/fish_history') + + def _get_history_line(self, command_script): + return u'- cmd: {}\n when: {}\n'.format(command_script, int(time())) + + def _script_from_history(self, line): + if '- cmd: ' in line: + return line.split('- cmd: ', 1)[1] + else: + return '' + + def and_(self, *commands): + return u'; and '.join(commands) + + def how_to_configure(self): + return 'eval thefuck --alias', '~/.config/fish/config.fish' diff --git a/thefuck/shells/generic.py b/thefuck/shells/generic.py new file mode 100644 index 00000000..896792cf --- /dev/null +++ b/thefuck/shells/generic.py @@ -0,0 +1,89 @@ +import io +import os +import shlex +import six +from ..conf import settings + + +class Generic(object): + def get_aliases(self): + return {} + + def _expand_aliases(self, command_script): + aliases = self.get_aliases() + binary = command_script.split(' ')[0] + if binary in aliases: + return command_script.replace(binary, aliases[binary], 1) + else: + return command_script + + def from_shell(self, command_script): + """Prepares command before running in app.""" + return self._expand_aliases(command_script) + + def to_shell(self, command_script): + """Prepares command for running in shell.""" + return command_script + + def app_alias(self, fuck): + return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \ + "thefuck $(fc -ln -1))'".format(fuck) + + def _get_history_file_name(self): + return '' + + def _get_history_line(self, command_script): + return '' + + def put_to_history(self, command_script): + """Puts command script to shell history.""" + history_file_name = self._get_history_file_name() + if os.path.isfile(history_file_name): + with open(history_file_name, 'a') as history: + entry = self._get_history_line(command_script) + if six.PY2: + history.write(entry.encode('utf-8')) + else: + history.write(entry) + + 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_file: + + lines = history_file.readlines() + if settings.history_limit: + lines = lines[-settings.history_limit:] + + for line in lines: + prepared = self._script_from_history(line) \ + .strip() + if prepared: + yield prepared + + def and_(self, *commands): + return u' && '.join(commands) + + def how_to_configure(self): + return + + def split_command(self, command): + """Split the command using shell-like syntax.""" + if six.PY2: + return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))] + return shlex.split(command) + + def quote(self, s): + """Return a shell-escaped version of the string s.""" + + if six.PY2: + from pipes import quote + else: + from shlex import quote + + return quote(s) + + def _script_from_history(self, line): + return line diff --git a/thefuck/shells/tcsh.py b/thefuck/shells/tcsh.py new file mode 100644 index 00000000..6c355caa --- /dev/null +++ b/thefuck/shells/tcsh.py @@ -0,0 +1,34 @@ +from subprocess import Popen, PIPE +from time import time +import os +from ..utils import DEVNULL, memoize +from .generic import Generic + + +class Tcsh(Generic): + def app_alias(self, fuck): + return ("alias {0} 'setenv TF_ALIAS {0} && " + "set fucked_cmd=`history -h 2 | head -n 1` && " + "eval `thefuck ${{fucked_cmd}}`'").format(fuck) + + def _parse_alias(self, alias): + name, value = alias.split("\t", 1) + return name, value + + @memoize + def get_aliases(self): + proc = Popen(['tcsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) + return dict( + self._parse_alias(alias) + for alias in proc.stdout.read().decode('utf-8').split('\n') + if alias and '\t' in alias) + + def _get_history_file_name(self): + return os.environ.get("HISTFILE", + os.path.expanduser('~/.history')) + + def _get_history_line(self, command_script): + return u'#+{}\n{}\n'.format(int(time()), command_script) + + def how_to_configure(self): + return 'eval `thefuck --alias`', '~/.tcshrc' diff --git a/thefuck/shells/zsh.py b/thefuck/shells/zsh.py new file mode 100644 index 00000000..058ca369 --- /dev/null +++ b/thefuck/shells/zsh.py @@ -0,0 +1,43 @@ +from subprocess import Popen, PIPE +from time import time +import os +from ..utils import DEVNULL, memoize, cache +from .generic import Generic + + +class Zsh(Generic): + def app_alias(self, fuck): + return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8" \ + " thefuck $(fc -ln -1 | tail -n 1));" \ + " fc -R'".format(fuck) + + 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 + + @memoize + @cache('.zshrc') + def get_aliases(self): + proc = Popen(['zsh', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL) + return dict( + self._parse_alias(alias) + for alias in proc.stdout.read().decode('utf-8').split('\n') + if alias and '=' in alias) + + def _get_history_file_name(self): + return os.environ.get("HISTFILE", + os.path.expanduser('~/.zsh_history')) + + def _get_history_line(self, command_script): + return u': {}:0;{}\n'.format(int(time()), command_script) + + def _script_from_history(self, line): + if ';' in line: + return line.split(';', 1)[1] + else: + return '' + + def how_to_configure(self): + return 'eval $(thefuck --alias)', '~/.zshrc' From 94f8652175ae10a48d14db543ea729f949feb329 Mon Sep 17 00:00:00 2001 From: nvbn Date: Sat, 23 Jan 2016 05:06:33 +0300 Subject: [PATCH 2/6] #N/A: Add tests for tcsh --- tests/shells/test_tcsh.py | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/shells/test_tcsh.py diff --git a/tests/shells/test_tcsh.py b/tests/shells/test_tcsh.py new file mode 100644 index 00000000..c9353369 --- /dev/null +++ b/tests/shells/test_tcsh.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import pytest +from thefuck.shells.tcsh import Tcsh + + +@pytest.mark.usefixtures('isfile') +class TestTcsh(object): + @pytest.fixture + def shell(self): + return Tcsh() + + @pytest.fixture(autouse=True) + def Popen(self, mocker): + mock = mocker.patch('thefuck.shells.tcsh.Popen') + mock.return_value.stdout.read.return_value = ( + b'fuck\teval $(thefuck $(fc -ln -1))\n' + b'l\tls -CF\n' + b'la\tls -A\n' + b'll\tls -alF') + return mock + + @pytest.mark.parametrize('before, after', [ + ('pwd', 'pwd'), + ('fuck', 'eval $(thefuck $(fc -ln -1))'), + ('awk', 'awk'), + ('ll', 'ls -alF')]) + def test_from_shell(self, before, after, shell): + assert shell.from_shell(before) == after + + def test_to_shell(self, shell): + assert shell.to_shell('pwd') == 'pwd' + + @pytest.mark.parametrize('entry, entry_utf8', [ + ('ls', '#+1430707243\nls\n'), + (u'echo café', '#+1430707243\necho café\n')]) + def test_put_to_history(self, entry, entry_utf8, builtins_open, shell, mocker): + mocker.patch('thefuck.shells.tcsh.time', + return_value=1430707243.3517463) + shell.put_to_history(entry) + builtins_open.return_value.__enter__.return_value. \ + write.assert_called_once_with(entry_utf8) + + def test_and_(self, shell): + assert shell.and_('ls', 'cd') == 'ls && cd' + + def test_get_aliases(self, shell): + assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))', + 'l': 'ls -CF', + 'la': 'ls -A', + 'll': 'ls -alF'} + + def test_app_alias(self, shell): + assert 'alias fuck' in shell.app_alias('fuck') + assert 'alias FUCK' in shell.app_alias('FUCK') + assert 'thefuck' in shell.app_alias('fuck') + + def test_get_history(self, history_lines, shell): + history_lines(['ls', 'rm']) + assert list(shell.get_history()) == ['ls', 'rm'] From 9b129fad08e1ea564599afa8158f40902d65b38c Mon Sep 17 00:00:00 2001 From: nvbn Date: Sun, 24 Jan 2016 04:39:27 +0300 Subject: [PATCH 3/6] #N/A: Add docsting in `shells` --- thefuck/shells/__init__.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/thefuck/shells/__init__.py b/thefuck/shells/__init__.py index af9142b1..7cbe5d4b 100644 --- a/thefuck/shells/__init__.py +++ b/thefuck/shells/__init__.py @@ -1,7 +1,10 @@ -from collections import defaultdict -from psutil import Process +"""Package with shell specific actions, each shell class should +implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and +`get_aliases` methods. +""" import os import sys +from psutil import Process from ..utils import memoize from .. import logs from .bash import Bash @@ -10,12 +13,11 @@ from .generic import Generic from .tcsh import Tcsh from .zsh import Zsh -shells = defaultdict(Generic, - bash=Bash(), - fish=Fish(), - zsh=Zsh(), - csh=Tcsh(), - tcsh=Tcsh()) +shells = {'bash': Bash, + 'fish': Fish, + 'zsh': Zsh, + 'csh': Tcsh, + 'tcsh': Tcsh} @memoize @@ -24,9 +26,10 @@ def _get_shell(): shell = Process(os.getpid()).parent().name() except TypeError: shell = Process(os.getpid()).parent.name - return shells[shell] + return shells.get(shell, Generic)() +# Public interface of current shell: def from_shell(command): return _get_shell().from_shell(command) From a2ec5aa3ff3c58bf982db062b48d9dd2b8738c68 Mon Sep 17 00:00:00 2001 From: nvbn Date: Fri, 29 Jan 2016 12:22:31 +0300 Subject: [PATCH 4/6] #441: Move function for getting current alias to `utils` --- tests/rules/test_history.py | 2 +- thefuck/main.py | 4 ++-- thefuck/rules/history.py | 6 +++--- thefuck/rules/switch_lang.py | 5 ++--- thefuck/shells/__init__.py | 4 ---- thefuck/utils.py | 8 ++++++-- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/rules/test_history.py b/tests/rules/test_history.py index 6422743b..e50d0afd 100644 --- a/tests/rules/test_history.py +++ b/tests/rules/test_history.py @@ -12,7 +12,7 @@ def history(mocker): @pytest.fixture def alias(mocker): - return mocker.patch('thefuck.rules.history.thefuck_alias', + return mocker.patch('thefuck.rules.history.get_alias', return_value='fuck') diff --git a/thefuck/main.py b/thefuck/main.py index 76468a9b..c4e075bc 100644 --- a/thefuck/main.py +++ b/thefuck/main.py @@ -11,7 +11,7 @@ from . import logs, types, shells from .conf import settings from .corrector import get_corrected_commands from .exceptions import EmptyCommand -from .utils import get_installation_info +from .utils import get_installation_info, get_alias from .ui import select_command @@ -42,7 +42,7 @@ def print_alias(entry_point=True): else: position = 2 - alias = shells.thefuck_alias() + alias = get_alias() if len(sys.argv) > position: alias = sys.argv[position] print(shells.app_alias(alias)) diff --git a/thefuck/rules/history.py b/thefuck/rules/history.py index 0f187d99..31a3d2f0 100644 --- a/thefuck/rules/history.py +++ b/thefuck/rules/history.py @@ -1,6 +1,6 @@ from difflib import get_close_matches -from thefuck.shells import get_history, thefuck_alias -from thefuck.utils import get_closest, memoize, get_all_executables +from thefuck.shells import get_history +from thefuck.utils import get_closest, memoize, get_all_executables, get_alias def _not_corrected(history, tf_alias): @@ -17,7 +17,7 @@ def _not_corrected(history, tf_alias): @memoize def _history_of_exists_without_current(command): history = get_history() - tf_alias = thefuck_alias() + tf_alias = get_alias() executables = get_all_executables() return [line for line in _not_corrected(history, tf_alias) if not line.startswith(tf_alias) and not line == command.script diff --git a/thefuck/rules/switch_lang.py b/thefuck/rules/switch_lang.py index 23f1422d..b1abb63d 100644 --- a/thefuck/rules/switch_lang.py +++ b/thefuck/rules/switch_lang.py @@ -1,6 +1,5 @@ # -*- encoding: utf-8 -*- -from thefuck.shells import thefuck_alias -from thefuck.utils import memoize +from thefuck.utils import memoize, get_alias target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''' @@ -35,7 +34,7 @@ def match(command): return False matched_layout = _get_matched_layout(command) return matched_layout and \ - _switch_command(command, matched_layout) != thefuck_alias() + _switch_command(command, matched_layout) != get_alias() def get_new_command(command): diff --git a/thefuck/shells/__init__.py b/thefuck/shells/__init__.py index 7cbe5d4b..870c1ea6 100644 --- a/thefuck/shells/__init__.py +++ b/thefuck/shells/__init__.py @@ -42,10 +42,6 @@ def app_alias(alias): return _get_shell().app_alias(alias) -def thefuck_alias(): - return os.environ.get('TF_ALIAS', 'fuck') - - def put_to_history(command): try: return _get_shell().put_to_history(command) diff --git a/thefuck/utils.py b/thefuck/utils.py index 101bcd25..01b75183 100644 --- a/thefuck/utils.py +++ b/thefuck/utils.py @@ -93,7 +93,7 @@ def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True): @memoize def get_all_executables(): - from thefuck.shells import thefuck_alias, get_aliases + from thefuck.shells import get_aliases def _safe(fn, fallback): try: @@ -101,7 +101,7 @@ def get_all_executables(): except OSError: return fallback - tf_alias = thefuck_alias() + tf_alias = get_alias() tf_entry_points = get_installation_info().get_entry_map()\ .get('console_scripts', {})\ .keys() @@ -260,3 +260,7 @@ def compatibility_call(fn, *args): def get_installation_info(): return pkg_resources.require('thefuck')[0] + + +def get_alias(): + return os.environ.get('TF_ALIAS', 'fuck') From b5dc7aab6d6f8bb0a98089687a40f2477db94e4b Mon Sep 17 00:00:00 2001 From: nvbn Date: Fri, 29 Jan 2016 12:30:31 +0300 Subject: [PATCH 5/6] #441: Remove all logic from shells methods wrappers --- thefuck/shells/__init__.py | 10 +++------- thefuck/shells/generic.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/thefuck/shells/__init__.py b/thefuck/shells/__init__.py index 870c1ea6..a194f038 100644 --- a/thefuck/shells/__init__.py +++ b/thefuck/shells/__init__.py @@ -43,10 +43,7 @@ def app_alias(alias): def put_to_history(command): - try: - return _get_shell().put_to_history(command) - except IOError: - logs.exception("Can't update history", sys.exc_info()) + return _get_shell().put_to_history(command) def and_(*commands): @@ -54,7 +51,7 @@ def and_(*commands): def get_aliases(): - return list(_get_shell().get_aliases().keys()) + return _get_shell().get_aliases() def split_command(command): @@ -65,9 +62,8 @@ def quote(s): return _get_shell().quote(s) -@memoize def get_history(): - return list(_get_shell().get_history()) + return _get_shell().get_history() def how_to_configure(): diff --git a/thefuck/shells/generic.py b/thefuck/shells/generic.py index 896792cf..248aab45 100644 --- a/thefuck/shells/generic.py +++ b/thefuck/shells/generic.py @@ -2,7 +2,11 @@ import io import os import shlex import six +import sys + +from ..utils import memoize from ..conf import settings +from .. import logs class Generic(object): @@ -35,7 +39,13 @@ class Generic(object): def _get_history_line(self, command_script): return '' - def put_to_history(self, command_script): + def put_to_history(self, command): + try: + return self._put_to_history(command) + except IOError: + logs.exception("Can't update history", sys.exc_info()) + + def _put_to_history(self, command_script): """Puts command script to shell history.""" history_file_name = self._get_history_file_name() if os.path.isfile(history_file_name): @@ -46,7 +56,11 @@ class Generic(object): else: history.write(entry) + @memoize def get_history(self): + return list(self._get_history_lines()) + + def _get_history_lines(self): """Returns list of history entries.""" history_file_name = self._get_history_file_name() if os.path.isfile(history_file_name): From fe07fcaa62d5999513d1dc8d6fb7d7d26e6a64b5 Mon Sep 17 00:00:00 2001 From: nvbn Date: Fri, 29 Jan 2016 13:09:40 +0300 Subject: [PATCH 6/6] #441: Remove shells methods wrappers --- tests/conftest.py | 14 ++++++++ tests/rules/conftest.py | 6 ---- tests/rules/test_git_branch_list.py | 4 +-- tests/rules/test_history.py | 2 +- tests/shells/conftest.py | 1 + tests/shells/test_bash.py | 2 +- tests/shells/test_fish.py | 2 +- tests/shells/test_tcsh.py | 2 +- tests/shells/test_zsh.py | 2 +- tests/test_types.py | 5 --- tests/test_utils.py | 2 +- thefuck/main.py | 15 +++++---- thefuck/rules/apt_get.py | 4 +-- thefuck/rules/cd_mkdir.py | 4 +-- thefuck/rules/dirty_untar.py | 6 ++-- thefuck/rules/dirty_unzip.py | 5 +-- thefuck/rules/fix_file.py | 4 +-- thefuck/rules/git_add.py | 4 +-- thefuck/rules/git_branch_list.py | 4 +-- thefuck/rules/git_checkout.py | 5 +-- thefuck/rules/git_pull.py | 4 +-- thefuck/rules/git_push_pull.py | 6 ++-- thefuck/rules/git_stash.py | 4 +-- thefuck/rules/history.py | 4 +-- thefuck/rules/no_such_file.py | 4 +-- thefuck/rules/pacman.py | 4 +-- thefuck/rules/sed_unterminated_s.py | 4 +-- thefuck/rules/touch.py | 4 +-- thefuck/rules/tsuru_login.py | 4 +-- thefuck/rules/vagrant_up.py | 6 ++-- thefuck/shells/__init__.py | 50 +++-------------------------- thefuck/specific/git.py | 5 +-- thefuck/types.py | 9 +++--- thefuck/utils.py | 4 +-- 34 files changed, 86 insertions(+), 119 deletions(-) delete mode 100644 tests/rules/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py index d66d9beb..ad1f5394 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,10 @@ from pathlib import Path import pytest +from thefuck import shells from thefuck import conf +shells.shell = shells.Generic() + def pytest_addoption(parser): """Adds `--run-without-docker` argument.""" @@ -46,3 +49,14 @@ def functional(request): @pytest.fixture def source_root(): return Path(__file__).parent.parent.resolve() + + +@pytest.fixture +def set_shell(monkeypatch, request): + def _set(cls): + shell = cls() + monkeypatch.setattr('thefuck.shells.shell', shell) + request.addfinalizer() + return shell + + return _set diff --git a/tests/rules/conftest.py b/tests/rules/conftest.py deleted file mode 100644 index 99a5c0d3..00000000 --- a/tests/rules/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -import pytest - - -@pytest.fixture(autouse=True) -def generic_shell(monkeypatch): - monkeypatch.setattr('thefuck.shells.and_', lambda *x: u' && '.join(x)) diff --git a/tests/rules/test_git_branch_list.py b/tests/rules/test_git_branch_list.py index 79302c58..cb160e3d 100644 --- a/tests/rules/test_git_branch_list.py +++ b/tests/rules/test_git_branch_list.py @@ -1,5 +1,5 @@ -from thefuck import shells from thefuck.rules.git_branch_list import match, get_new_command +from thefuck.shells import shell from tests.utils import Command @@ -16,4 +16,4 @@ def test_not_match(): def test_get_new_command(): assert (get_new_command(Command('git branch list')) == - shells.and_('git branch --delete list', 'git branch')) + shell.and_('git branch --delete list', 'git branch')) diff --git a/tests/rules/test_history.py b/tests/rules/test_history.py index e50d0afd..a9b2505a 100644 --- a/tests/rules/test_history.py +++ b/tests/rules/test_history.py @@ -5,7 +5,7 @@ from tests.utils import Command @pytest.fixture def history(mocker): - return mocker.patch('thefuck.rules.history.get_history', + return mocker.patch('thefuck.shells.shell.get_history', return_value=['le cat', 'fuck', 'ls cat', 'diff x', 'nocommand x']) diff --git a/tests/shells/conftest.py b/tests/shells/conftest.py index 8fcbced2..de22a744 100644 --- a/tests/shells/conftest.py +++ b/tests/shells/conftest.py @@ -18,4 +18,5 @@ def history_lines(mocker): mock = mocker.patch('io.open') mock.return_value.__enter__ \ .return_value.readlines.return_value = lines + return aux diff --git a/tests/shells/test_bash.py b/tests/shells/test_bash.py index d4b57c7e..13f3fdda 100644 --- a/tests/shells/test_bash.py +++ b/tests/shells/test_bash.py @@ -4,7 +4,7 @@ import pytest from thefuck.shells import Bash -@pytest.mark.usefixtures('isfile') +@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache') class TestBash(object): @pytest.fixture def shell(self): diff --git a/tests/shells/test_fish.py b/tests/shells/test_fish.py index 2e1a296c..9103d918 100644 --- a/tests/shells/test_fish.py +++ b/tests/shells/test_fish.py @@ -4,7 +4,7 @@ import pytest from thefuck.shells import Fish -@pytest.mark.usefixtures('isfile') +@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache') class TestFish(object): @pytest.fixture def shell(self): diff --git a/tests/shells/test_tcsh.py b/tests/shells/test_tcsh.py index c9353369..3aed208c 100644 --- a/tests/shells/test_tcsh.py +++ b/tests/shells/test_tcsh.py @@ -4,7 +4,7 @@ import pytest from thefuck.shells.tcsh import Tcsh -@pytest.mark.usefixtures('isfile') +@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache') class TestTcsh(object): @pytest.fixture def shell(self): diff --git a/tests/shells/test_zsh.py b/tests/shells/test_zsh.py index fd327ee7..d81bd808 100644 --- a/tests/shells/test_zsh.py +++ b/tests/shells/test_zsh.py @@ -4,7 +4,7 @@ import pytest from thefuck.shells.zsh import Zsh -@pytest.mark.usefixtures('isfile') +@pytest.mark.usefixtures('isfile', 'no_memoize', 'no_cache') class TestZsh(object): @pytest.fixture def shell(self): diff --git a/tests/test_types.py b/tests/test_types.py index c5f16445..d68dc465 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -103,11 +103,6 @@ class TestCommand(object): monkeypatch.setattr('thefuck.types.Command._wait_output', staticmethod(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_from_script_calls(self, Popen, settings): settings.env = {} assert Command.from_raw_script( diff --git a/tests/test_utils.py b/tests/test_utils.py index 53027675..72078275 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -50,7 +50,7 @@ class TestGetClosest(object): @pytest.fixture def get_aliases(mocker): - mocker.patch('thefuck.shells.get_aliases', + mocker.patch('thefuck.shells.shell.get_aliases', return_value=['vim', 'apt-get', 'fsck', 'fuck']) diff --git a/thefuck/main.py b/thefuck/main.py index c4e075bc..506a1a06 100644 --- a/thefuck/main.py +++ b/thefuck/main.py @@ -7,7 +7,8 @@ from argparse import ArgumentParser from warnings import warn from pprint import pformat import sys -from . import logs, types, shells +from . import logs, types +from .shells import shell from .conf import settings from .corrector import get_corrected_commands from .exceptions import EmptyCommand @@ -45,7 +46,7 @@ def print_alias(entry_point=True): alias = get_alias() if len(sys.argv) > position: alias = sys.argv[position] - print(shells.app_alias(alias)) + print(shell.app_alias(alias)) def how_to_configure_alias(): @@ -55,17 +56,17 @@ def how_to_configure_alias(): """ settings.init() - logs.how_to_configure_alias(shells.how_to_configure()) + logs.how_to_configure_alias(shell.how_to_configure()) def main(): parser = ArgumentParser(prog='thefuck') version = get_installation_info().version parser.add_argument( - '-v', '--version', - action='version', - version='The Fuck {} using Python {}'.format( - version, sys.version.split()[0])) + '-v', '--version', + action='version', + version='The Fuck {} using Python {}'.format( + version, sys.version.split()[0])) parser.add_argument('-a', '--alias', action='store_true', help='[custom-alias-name] prints alias for current shell') diff --git a/thefuck/rules/apt_get.py b/thefuck/rules/apt_get.py index 883687c3..853c8784 100644 --- a/thefuck/rules/apt_get.py +++ b/thefuck/rules/apt_get.py @@ -1,5 +1,5 @@ -from thefuck import shells from thefuck.utils import memoize +from thefuck.shells import shell try: import CommandNotFound @@ -26,5 +26,5 @@ def match(command): def get_new_command(command): name = get_package(command.script) - formatme = shells.and_('sudo apt-get install {}', '{}') + formatme = shell.and_('sudo apt-get install {}', '{}') return formatme.format(name, command.script) diff --git a/thefuck/rules/cd_mkdir.py b/thefuck/rules/cd_mkdir.py index b37a237e..faba704e 100644 --- a/thefuck/rules/cd_mkdir.py +++ b/thefuck/rules/cd_mkdir.py @@ -1,7 +1,7 @@ import re -from thefuck import shells from thefuck.utils import for_app from thefuck.specific.sudo import sudo_support +from thefuck.shells import shell @sudo_support @@ -14,5 +14,5 @@ def match(command): @sudo_support def get_new_command(command): - repl = shells.and_('mkdir -p \\1', 'cd \\1') + repl = shell.and_('mkdir -p \\1', 'cd \\1') return re.sub(r'^cd (.*)', repl, command.script) diff --git a/thefuck/rules/dirty_untar.py b/thefuck/rules/dirty_untar.py index 25d02a64..d94958b9 100644 --- a/thefuck/rules/dirty_untar.py +++ b/thefuck/rules/dirty_untar.py @@ -1,7 +1,7 @@ import tarfile import os -from thefuck import shells from thefuck.utils import for_app +from thefuck.shells import shell tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz', @@ -33,8 +33,8 @@ def match(command): def get_new_command(command): - dir = shells.quote(_tar_file(command.script_parts)[1]) - return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \ + dir = shell.quote(_tar_file(command.script_parts)[1]) + return shell.and_('mkdir -p {dir}', '{cmd} -C {dir}') \ .format(dir=dir, cmd=command.script) diff --git a/thefuck/rules/dirty_unzip.py b/thefuck/rules/dirty_unzip.py index 8423a5ae..23878f5f 100644 --- a/thefuck/rules/dirty_unzip.py +++ b/thefuck/rules/dirty_unzip.py @@ -1,7 +1,7 @@ import os import zipfile from thefuck.utils import for_app -from thefuck.shells import quote +from thefuck.shells import shell def _is_bad_zip(file): @@ -38,7 +38,8 @@ def match(command): def get_new_command(command): - return u'{} -d {}'.format(command.script, quote(_zip_file(command)[:-4])) + return u'{} -d {}'.format( + command.script, shell.quote(_zip_file(command)[:-4])) def side_effect(old_cmd, command): diff --git a/thefuck/rules/fix_file.py b/thefuck/rules/fix_file.py index fcae4a67..a83034d4 100644 --- a/thefuck/rules/fix_file.py +++ b/thefuck/rules/fix_file.py @@ -2,7 +2,7 @@ import re import os from thefuck.utils import memoize, default_settings from thefuck.conf import settings -from thefuck import shells +from thefuck.shells import shell # order is important: only the first match is considered @@ -75,4 +75,4 @@ def get_new_command(command): file=m.group('file'), line=m.group('line')) - return shells.and_(editor_call, command.script) + return shell.and_(editor_call, command.script) diff --git a/thefuck/rules/git_add.py b/thefuck/rules/git_add.py index 452797b0..0cf1b05a 100644 --- a/thefuck/rules/git_add.py +++ b/thefuck/rules/git_add.py @@ -1,5 +1,5 @@ import re -from thefuck import shells +from thefuck.shells import shell from thefuck.specific.git import git_support @@ -15,5 +15,5 @@ def get_new_command(command): r"error: pathspec '([^']*)' " r"did not match any file\(s\) known to git.", command.stderr)[0] - formatme = shells.and_('git add -- {}', '{}') + formatme = shell.and_('git add -- {}', '{}') return formatme.format(missing_file, command.script) diff --git a/thefuck/rules/git_branch_list.py b/thefuck/rules/git_branch_list.py index 4c77b149..bc259afb 100644 --- a/thefuck/rules/git_branch_list.py +++ b/thefuck/rules/git_branch_list.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.specific.git import git_support @@ -11,4 +11,4 @@ def match(command): @git_support def get_new_command(command): - return shells.and_('git branch --delete list', 'git branch') + return shell.and_('git branch --delete list', 'git branch') diff --git a/thefuck/rules/git_checkout.py b/thefuck/rules/git_checkout.py index d0869c10..458b90c0 100644 --- a/thefuck/rules/git_checkout.py +++ b/thefuck/rules/git_checkout.py @@ -1,8 +1,9 @@ import re import subprocess -from thefuck import shells, utils +from thefuck import utils from thefuck.utils import replace_argument from thefuck.specific.git import git_support +from thefuck.shells import shell @git_support @@ -34,5 +35,5 @@ def get_new_command(command): if closest_branch: return replace_argument(command.script, missing_file, closest_branch) else: - return shells.and_('git branch {}', '{}').format( + return shell.and_('git branch {}', '{}').format( missing_file, command.script) diff --git a/thefuck/rules/git_pull.py b/thefuck/rules/git_pull.py index 3c04f7d2..7978802f 100644 --- a/thefuck/rules/git_pull.py +++ b/thefuck/rules/git_pull.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.specific.git import git_support @@ -14,4 +14,4 @@ def get_new_command(command): branch = line.split(' ')[-1] set_upstream = line.replace('', 'origin')\ .replace('', branch) - return shells.and_(set_upstream, command.script) + return shell.and_(set_upstream, command.script) diff --git a/thefuck/rules/git_push_pull.py b/thefuck/rules/git_push_pull.py index 8560198e..c4e4e41b 100644 --- a/thefuck/rules/git_push_pull.py +++ b/thefuck/rules/git_push_pull.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.utils import replace_argument from thefuck.specific.git import git_support @@ -13,5 +13,5 @@ def match(command): @git_support def get_new_command(command): - return shells.and_(replace_argument(command.script, 'push', 'pull'), - command.script) + return shell.and_(replace_argument(command.script, 'push', 'pull'), + command.script) diff --git a/thefuck/rules/git_stash.py b/thefuck/rules/git_stash.py index df7f3850..9dd5f502 100644 --- a/thefuck/rules/git_stash.py +++ b/thefuck/rules/git_stash.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.specific.git import git_support @@ -11,5 +11,5 @@ def match(command): @git_support def get_new_command(command): - formatme = shells.and_('git stash', '{}') + formatme = shell.and_('git stash', '{}') return formatme.format(command.script) diff --git a/thefuck/rules/history.py b/thefuck/rules/history.py index 31a3d2f0..96f84502 100644 --- a/thefuck/rules/history.py +++ b/thefuck/rules/history.py @@ -1,5 +1,5 @@ from difflib import get_close_matches -from thefuck.shells import get_history +from thefuck.shells import shell from thefuck.utils import get_closest, memoize, get_all_executables, get_alias @@ -16,7 +16,7 @@ def _not_corrected(history, tf_alias): @memoize def _history_of_exists_without_current(command): - history = get_history() + history = shell.get_history() tf_alias = get_alias() executables = get_all_executables() return [line for line in _not_corrected(history, tf_alias) diff --git a/thefuck/rules/no_such_file.py b/thefuck/rules/no_such_file.py index 68a989b5..c7d23849 100644 --- a/thefuck/rules/no_such_file.py +++ b/thefuck/rules/no_such_file.py @@ -1,5 +1,5 @@ import re -from thefuck import shells +from thefuck.shells import shell patterns = ( @@ -26,5 +26,5 @@ def get_new_command(command): file = file[0] dir = file[0:file.rfind('/')] - formatme = shells.and_('mkdir -p {}', '{}') + formatme = shell.and_('mkdir -p {}', '{}') return formatme.format(dir, command.script) diff --git a/thefuck/rules/pacman.py b/thefuck/rules/pacman.py index 67b5bd22..13a08d65 100644 --- a/thefuck/rules/pacman.py +++ b/thefuck/rules/pacman.py @@ -1,5 +1,5 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env -from thefuck import shells +from thefuck.shells import shell def match(command): @@ -9,7 +9,7 @@ def match(command): def get_new_command(command): packages = get_pkgfile(command.script) - formatme = shells.and_('{} -S {}', '{}') + formatme = shell.and_('{} -S {}', '{}') return [formatme.format(pacman, package, command.script) for package in packages] diff --git a/thefuck/rules/sed_unterminated_s.py b/thefuck/rules/sed_unterminated_s.py index 1fb6233d..11968066 100644 --- a/thefuck/rules/sed_unterminated_s.py +++ b/thefuck/rules/sed_unterminated_s.py @@ -1,5 +1,5 @@ import shlex -from thefuck.shells import quote +from thefuck.shells import shell from thefuck.utils import for_app @@ -15,4 +15,4 @@ def get_new_command(command): if e.startswith(('s/', '-es/')) and e[-1] != '/': script[i] += '/' - return ' '.join(map(quote, script)) + return ' '.join(map(shell.quote, script)) diff --git a/thefuck/rules/touch.py b/thefuck/rules/touch.py index 07bbbc8d..4da351a2 100644 --- a/thefuck/rules/touch.py +++ b/thefuck/rules/touch.py @@ -1,5 +1,5 @@ import re -from thefuck import shells +from thefuck.shells import shell from thefuck.utils import for_app @@ -10,4 +10,4 @@ def match(command): def get_new_command(command): path = re.findall(r"touch: cannot touch '(.+)/.+':", command.stderr)[0] - return shells.and_(u'mkdir -p {}'.format(path), command.script) + return shell.and_(u'mkdir -p {}'.format(path), command.script) diff --git a/thefuck/rules/tsuru_login.py b/thefuck/rules/tsuru_login.py index ce4de2dd..5f0be702 100644 --- a/thefuck/rules/tsuru_login.py +++ b/thefuck/rules/tsuru_login.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.utils import for_app @@ -9,4 +9,4 @@ def match(command): def get_new_command(command): - return shells.and_('tsuru login', command.script) + return shell.and_('tsuru login', command.script) diff --git a/thefuck/rules/vagrant_up.py b/thefuck/rules/vagrant_up.py index d166bec1..135dd60b 100644 --- a/thefuck/rules/vagrant_up.py +++ b/thefuck/rules/vagrant_up.py @@ -1,4 +1,4 @@ -from thefuck import shells +from thefuck.shells import shell from thefuck.utils import for_app @@ -13,8 +13,8 @@ def get_new_command(command): if len(cmds) >= 3: machine = cmds[2] - startAllInstances = shells.and_("vagrant up", command.script) + startAllInstances = shell.and_("vagrant up", command.script) if machine is None: return startAllInstances else: - return [shells.and_("vagrant up " + machine, command.script), startAllInstances] + return [shell.and_("vagrant up " + machine, command.script), startAllInstances] diff --git a/thefuck/shells/__init__.py b/thefuck/shells/__init__.py index a194f038..414b8ee7 100644 --- a/thefuck/shells/__init__.py +++ b/thefuck/shells/__init__.py @@ -3,10 +3,7 @@ implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and `get_aliases` methods. """ import os -import sys from psutil import Process -from ..utils import memoize -from .. import logs from .bash import Bash from .fish import Fish from .generic import Generic @@ -20,51 +17,12 @@ shells = {'bash': Bash, 'tcsh': Tcsh} -@memoize def _get_shell(): try: - shell = Process(os.getpid()).parent().name() + shell_name = Process(os.getpid()).parent().name() except TypeError: - shell = Process(os.getpid()).parent.name - return shells.get(shell, Generic)() + shell_name = Process(os.getpid()).parent.name + return shells.get(shell_name, Generic)() -# Public interface of current shell: -def from_shell(command): - return _get_shell().from_shell(command) - - -def to_shell(command): - return _get_shell().to_shell(command) - - -def app_alias(alias): - return _get_shell().app_alias(alias) - - -def put_to_history(command): - return _get_shell().put_to_history(command) - - -def and_(*commands): - return _get_shell().and_(*commands) - - -def get_aliases(): - return _get_shell().get_aliases() - - -def split_command(command): - return _get_shell().split_command(command) - - -def quote(s): - return _get_shell().quote(s) - - -def get_history(): - return _get_shell().get_history() - - -def how_to_configure(): - return _get_shell().how_to_configure() +shell = _get_shell() diff --git a/thefuck/specific/git.py b/thefuck/specific/git.py index a3654cc3..6c2ba4fc 100644 --- a/thefuck/specific/git.py +++ b/thefuck/specific/git.py @@ -1,7 +1,7 @@ import re from decorator import decorator from ..utils import is_app -from ..shells import quote, split_command +from ..shells import shell @decorator @@ -23,7 +23,8 @@ def git_support(fn, command): # 'commit' '--amend' # which is surprising and does not allow to easily test for # eg. 'git commit' - expansion = ' '.join(map(quote, split_command(search.group(2)))) + expansion = ' '.join(shell.quote(part) + for part in shell.split_command(search.group(2))) new_script = command.script.replace(alias, expansion) command = command.update(script=new_script) diff --git a/thefuck/types.py b/thefuck/types.py index f5864658..5fff1f4b 100644 --- a/thefuck/types.py +++ b/thefuck/types.py @@ -4,7 +4,8 @@ import os import sys import six from psutil import Process, TimeoutExpired -from . import logs, shells +from . import logs +from .shells import shell from .conf import settings, DEFAULT_PRIORITY, ALL_ENABLED from .exceptions import EmptyCommand from .utils import compatibility_call @@ -29,7 +30,7 @@ class Command(object): def script_parts(self): if not hasattr(self, '_script_parts'): try: - self._script_parts = shells.split_command(self.script) + self._script_parts = shell.split_command(self.script) except Exception: logs.debug(u"Can't split command script {} because:\n {}".format( self, sys.exc_info())) @@ -93,7 +94,7 @@ class Command(object): script = ' '.join(raw_script) script = script.strip() - return shells.from_shell(script) + return shell.from_shell(script) @classmethod def from_raw_script(cls, raw_script): @@ -279,7 +280,7 @@ class CorrectedCommand(object): if self.side_effect: compatibility_call(self.side_effect, old_cmd, self.script) if settings.alter_history: - shells.put_to_history(self.script) + shell.put_to_history(self.script) # This depends on correct setting of PYTHONIOENCODING by the alias: logs.debug(u'PYTHONIOENCODING: {}'.format( os.environ.get('PYTHONIOENCODING', '>-not-set-<'))) diff --git a/thefuck/utils.py b/thefuck/utils.py index 01b75183..a5e43158 100644 --- a/thefuck/utils.py +++ b/thefuck/utils.py @@ -93,7 +93,7 @@ def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True): @memoize def get_all_executables(): - from thefuck.shells import get_aliases + from thefuck.shells import shell def _safe(fn, fallback): try: @@ -110,7 +110,7 @@ def get_all_executables(): for exe in _safe(lambda: list(Path(path).iterdir()), []) if not _safe(exe.is_dir, True) and exe.name not in tf_entry_points] - aliases = [alias for alias in get_aliases() if alias != tf_alias] + aliases = [alias for alias in shell.get_aliases() if alias != tf_alias] return bins + aliases