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