mirror of
https://github.com/nvbn/thefuck.git
synced 2025-01-31 02:01:13 +00:00
#611: Allow to configure alias automatically by calling fuck
twice
This commit is contained in:
parent
2379573cf2
commit
14a9cd85aa
2
setup.py
2
setup.py
@ -51,4 +51,4 @@ setup(name='thefuck',
|
||||
extras_require=extras_require,
|
||||
entry_points={'console_scripts': [
|
||||
'thefuck = thefuck.main:main',
|
||||
'fuck = thefuck.main:how_to_configure_alias']})
|
||||
'fuck = thefuck.not_configured:main']})
|
||||
|
@ -20,3 +20,11 @@ def history_lines(mocker):
|
||||
.return_value.readlines.return_value = lines
|
||||
|
||||
return aux
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_exists(mocker):
|
||||
path_mock = mocker.patch('thefuck.shells.generic.Path')
|
||||
return path_mock.return_value \
|
||||
.expanduser.return_value \
|
||||
.exists
|
||||
|
@ -61,3 +61,12 @@ class TestBash(object):
|
||||
command = 'git log -p'
|
||||
command_parts = ['git', 'log', '-p']
|
||||
assert shell.split_command(command) == command_parts
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
@ -95,3 +95,12 @@ class TestFish(object):
|
||||
shell.put_to_history(entry)
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with(entry_utf8)
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
@ -37,3 +37,6 @@ class TestGeneric(object):
|
||||
def test_split_command(self, shell):
|
||||
assert shell.split_command('ls') == ['ls']
|
||||
assert shell.split_command(u'echo café') == [u'echo', u'café']
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert shell.how_to_configure() is None
|
||||
|
@ -17,3 +17,6 @@ class TestPowershell(object):
|
||||
assert 'function fuck' in shell.app_alias('fuck')
|
||||
assert 'function FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
@ -48,3 +48,12 @@ class TestTcsh(object):
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
@ -55,3 +55,12 @@ class TestZsh(object):
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
119
tests/test_not_configured.py
Normal file
119
tests/test_not_configured.py
Normal file
@ -0,0 +1,119 @@
|
||||
import pytest
|
||||
from thefuck.shells.generic import ShellConfiguration
|
||||
from thefuck.not_configured import main
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def usage_tracker(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.not_configured._get_not_configured_usage_tracker_path')
|
||||
|
||||
|
||||
def _assert_tracker_updated(usage_tracker, pid):
|
||||
usage_tracker.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value \
|
||||
.write.assert_called_once_with(str(pid))
|
||||
|
||||
|
||||
def _change_tracker(usage_tracker, pid):
|
||||
usage_tracker.return_value.exists.return_value = True
|
||||
usage_tracker.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value \
|
||||
.read.return_value = str(pid)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_pid(mocker):
|
||||
return mocker.patch('thefuck.not_configured._get_shell_pid')
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell(mocker):
|
||||
shell = mocker.patch('thefuck.not_configured.shell')
|
||||
shell.get_history.return_value = []
|
||||
shell.how_to_configure.return_value = ShellConfiguration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path='/tmp/.bashrc',
|
||||
reload='bash',
|
||||
can_configure_automatically=True)
|
||||
return shell
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_config(mocker):
|
||||
path_mock = mocker.patch('thefuck.not_configured.Path')
|
||||
return path_mock.return_value \
|
||||
.expanduser.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def logs(mocker):
|
||||
return mocker.patch('thefuck.not_configured.logs')
|
||||
|
||||
|
||||
def test_for_generic_shell(shell, logs):
|
||||
shell.how_to_configure.return_value = None
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_first_run(usage_tracker, shell_pid, logs):
|
||||
shell_pid.return_value = 12
|
||||
usage_tracker.return_value.exists.return_value = False
|
||||
main()
|
||||
_assert_tracker_updated(usage_tracker, 12)
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_run_after_other_commands(usage_tracker, shell_pid, shell, logs):
|
||||
shell_pid.return_value = 12
|
||||
shell.get_history.return_value = ['fuck', 'ls']
|
||||
_change_tracker(usage_tracker, 12)
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_first_run_from_current_shell(usage_tracker, shell_pid,
|
||||
shell, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 55)
|
||||
main()
|
||||
_assert_tracker_updated(usage_tracker, 12)
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_when_cant_configure_automatically(shell_pid, shell, logs):
|
||||
shell_pid.return_value = 12
|
||||
shell.how_to_configure.return_value = ShellConfiguration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path='/tmp/.bashrc',
|
||||
reload='bash',
|
||||
can_configure_automatically=False)
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_when_already_configured(usage_tracker, shell_pid,
|
||||
shell, shell_config, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 12)
|
||||
shell_config.read.return_value = 'eval $(thefuck --alias)'
|
||||
main()
|
||||
logs.already_configured.assert_called_once()
|
||||
|
||||
|
||||
def test_when_successfuly_configured(usage_tracker, shell_pid,
|
||||
shell, shell_config, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 12)
|
||||
shell_config.read.return_value = ''
|
||||
main()
|
||||
shell_config.write.assert_any_call('eval $(thefuck --alias)')
|
||||
logs.configured_successfully.assert_called_once()
|
@ -91,6 +91,33 @@ def how_to_configure_alias(configuration_details):
|
||||
"changes with {bold}{reload}{reset} or restart your shell.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
**configuration_details))
|
||||
**configuration_details._asdict()))
|
||||
|
||||
if configuration_details.can_configure_automatically:
|
||||
print(
|
||||
"Or run {bold}fuck{reset} second time for configuring"
|
||||
" it automatically.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL)))
|
||||
|
||||
print('More details - https://github.com/nvbn/thefuck#manual-installation')
|
||||
|
||||
|
||||
def already_configured(configuration_details):
|
||||
print(
|
||||
"Seems like {bold}fuck{reset} alias already configured!\n"
|
||||
"For applying changes run {bold}{reload}{reset}"
|
||||
" or restart your shell.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
reload=configuration_details.reload))
|
||||
|
||||
|
||||
def configured_successfully(configuration_details):
|
||||
print(
|
||||
"{bold}fuck{reset} alias configured successfully!\n"
|
||||
"For applying changes run {bold}{reload}{reset}"
|
||||
" or restart your shell.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
reload=configuration_details.reload))
|
||||
|
@ -46,16 +46,6 @@ def print_alias():
|
||||
print(shell.app_alias(alias))
|
||||
|
||||
|
||||
def how_to_configure_alias():
|
||||
"""Shows useful information about how-to configure alias.
|
||||
|
||||
It'll be only visible when user type fuck and when alias isn't configured.
|
||||
|
||||
"""
|
||||
settings.init()
|
||||
logs.how_to_configure_alias(shell.how_to_configure())
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(prog='thefuck')
|
||||
version = get_installation_info().version
|
||||
|
86
thefuck/not_configured.py
Normal file
86
thefuck/not_configured.py
Normal file
@ -0,0 +1,86 @@
|
||||
# Initialize output before importing any module, that can use colorama.
|
||||
from .system import init_output
|
||||
|
||||
init_output()
|
||||
|
||||
import os # noqa: E402
|
||||
from psutil import Process # noqa: E402
|
||||
from . import logs # noqa: E402
|
||||
from .shells import shell # noqa: E402
|
||||
from .conf import settings # noqa: E402
|
||||
from .system import Path # noqa: E402
|
||||
from .utils import get_cache_dir # noqa: E402
|
||||
|
||||
|
||||
def _get_shell_pid():
|
||||
"""Returns parent process pid."""
|
||||
proc = Process(os.getpid())
|
||||
|
||||
try:
|
||||
return proc.parent().pid
|
||||
except TypeError:
|
||||
return proc.parent.pid
|
||||
|
||||
|
||||
def _get_not_configured_usage_tracker_path():
|
||||
"""Returns path of special file where we store latest shell pid."""
|
||||
return Path(get_cache_dir()).joinpath('thefuck.last_not_configured_run')
|
||||
|
||||
|
||||
def _record_first_run():
|
||||
"""Records shell pid to tracker file."""
|
||||
with _get_not_configured_usage_tracker_path().open('w') as tracker:
|
||||
tracker.write(str(_get_shell_pid()))
|
||||
|
||||
|
||||
def _is_second_run():
|
||||
"""Returns `True` when we know that `fuck` called second time."""
|
||||
tracker_path = _get_not_configured_usage_tracker_path()
|
||||
if not tracker_path.exists() or not shell.get_history()[-1] == 'fuck':
|
||||
return False
|
||||
|
||||
current_pid = _get_shell_pid()
|
||||
with tracker_path.open('r') as tracker:
|
||||
return tracker.read() == str(current_pid)
|
||||
|
||||
|
||||
def _is_already_configured(configuration_details):
|
||||
"""Returns `True` when alias already in shell config."""
|
||||
path = Path(configuration_details.path).expanduser()
|
||||
with path.open('r') as shell_config:
|
||||
return configuration_details.content in shell_config.read()
|
||||
|
||||
|
||||
def _configure(configuration_details):
|
||||
"""Adds alias to shell config."""
|
||||
path = Path(configuration_details.path).expanduser()
|
||||
with path.open('a') as shell_config:
|
||||
shell_config.write('\n')
|
||||
shell_config.write(configuration_details.content)
|
||||
shell_config.write('\n')
|
||||
|
||||
|
||||
def main():
|
||||
"""Shows useful information about how-to configure alias on a first run
|
||||
and configure automatically on a second.
|
||||
|
||||
It'll be only visible when user type fuck and when alias isn't configured.
|
||||
|
||||
"""
|
||||
settings.init()
|
||||
configuration_details = shell.how_to_configure()
|
||||
if (
|
||||
configuration_details and
|
||||
configuration_details.can_configure_automatically
|
||||
):
|
||||
if _is_already_configured(configuration_details):
|
||||
logs.already_configured(configuration_details)
|
||||
return
|
||||
elif _is_second_run():
|
||||
_configure(configuration_details)
|
||||
logs.configured_successfully(configuration_details)
|
||||
return
|
||||
else:
|
||||
_record_first_run()
|
||||
|
||||
logs.how_to_configure_alias(configuration_details)
|
@ -45,8 +45,7 @@ class Bash(Generic):
|
||||
else:
|
||||
config = 'bash config'
|
||||
|
||||
return {
|
||||
'content': 'eval $(thefuck --alias)',
|
||||
'path': config,
|
||||
'reload': u'source {}'.format(config),
|
||||
}
|
||||
return self._create_shell_configuration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path=config,
|
||||
reload=u'source {}'.format(config))
|
||||
|
@ -67,11 +67,10 @@ class Fish(Generic):
|
||||
return u'; and '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return {
|
||||
'content': r"eval (thefuck --alias | tr '\n' ';')",
|
||||
'path': '~/.config/fish/config.fish',
|
||||
'reload': 'fish',
|
||||
}
|
||||
return self._create_shell_configuration(
|
||||
content=r"eval (thefuck --alias | tr '\n' ';')",
|
||||
path='~/.config/fish/config.fish',
|
||||
reload='fish')
|
||||
|
||||
def put_to_history(self, command):
|
||||
try:
|
||||
|
@ -2,8 +2,14 @@ import io
|
||||
import os
|
||||
import shlex
|
||||
import six
|
||||
from collections import namedtuple
|
||||
from ..utils import memoize
|
||||
from ..conf import settings
|
||||
from ..system import Path
|
||||
|
||||
|
||||
ShellConfiguration = namedtuple('ShellConfiguration', (
|
||||
'content', 'path', 'reload', 'can_configure_automatically'))
|
||||
|
||||
|
||||
class Generic(object):
|
||||
@ -116,3 +122,10 @@ class Generic(object):
|
||||
'shift', 'shopt', 'source', 'suspend', 'test', 'times', 'trap',
|
||||
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
|
||||
'until', 'wait', 'while']
|
||||
|
||||
def _create_shell_configuration(self, content, path, reload):
|
||||
return ShellConfiguration(
|
||||
content=content,
|
||||
path=path,
|
||||
reload=reload,
|
||||
can_configure_automatically=Path(path).expanduser().exists())
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .generic import Generic
|
||||
from .generic import Generic, ShellConfiguration
|
||||
|
||||
|
||||
class Powershell(Generic):
|
||||
@ -18,8 +18,8 @@ class Powershell(Generic):
|
||||
return u' -and '.join('({0})'.format(c) for c in commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return {
|
||||
'content': 'iex "thefuck --alias"',
|
||||
'path': '$profile',
|
||||
'reload': '& $profile',
|
||||
}
|
||||
return ShellConfiguration(
|
||||
content='iex "thefuck --alias"',
|
||||
path='$profile',
|
||||
reload='& $profile',
|
||||
can_configure_automatically=False)
|
||||
|
@ -31,8 +31,7 @@ class Tcsh(Generic):
|
||||
return u'#+{}\n{}\n'.format(int(time()), command_script)
|
||||
|
||||
def how_to_configure(self):
|
||||
return {
|
||||
'content': 'eval `thefuck --alias`',
|
||||
'path': '~/.tcshrc',
|
||||
'reload': 'tcsh',
|
||||
}
|
||||
return self._create_shell_configuration(
|
||||
content='eval `thefuck --alias`',
|
||||
path='~/.tcshrc',
|
||||
reload='tcsh')
|
||||
|
@ -45,8 +45,7 @@ class Zsh(Generic):
|
||||
return ''
|
||||
|
||||
def how_to_configure(self):
|
||||
return {
|
||||
'content': 'eval $(thefuck --alias)',
|
||||
'path': '~/.zshrc',
|
||||
'reload': 'source ~/.zshrc',
|
||||
}
|
||||
return self._create_shell_configuration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path='~/.zshrc',
|
||||
reload='source ~/.zshrc')
|
||||
|
@ -178,6 +178,21 @@ def for_app(*app_names, **kwargs):
|
||||
return decorator(_for_app)
|
||||
|
||||
|
||||
def get_cache_dir():
|
||||
default_xdg_cache_dir = os.path.expanduser("~/.cache")
|
||||
cache_dir = os.getenv("XDG_CACHE_HOME", default_xdg_cache_dir)
|
||||
|
||||
# Ensure the cache_path exists, Python 2 does not have the exist_ok
|
||||
# parameter
|
||||
try:
|
||||
os.makedirs(cache_dir)
|
||||
except OSError:
|
||||
if not os.path.isdir(cache_dir):
|
||||
raise
|
||||
|
||||
return cache_dir
|
||||
|
||||
|
||||
def cache(*depends_on):
|
||||
"""Caches function result in temporary file.
|
||||
|
||||
@ -194,21 +209,6 @@ def cache(*depends_on):
|
||||
except OSError:
|
||||
return '0'
|
||||
|
||||
def _get_cache_path():
|
||||
default_xdg_cache_dir = os.path.expanduser("~/.cache")
|
||||
cache_dir = os.getenv("XDG_CACHE_HOME", default_xdg_cache_dir)
|
||||
cache_path = Path(cache_dir).joinpath('thefuck').as_posix()
|
||||
|
||||
# Ensure the cache_path exists, Python 2 does not have the exist_ok
|
||||
# parameter
|
||||
try:
|
||||
os.makedirs(cache_dir)
|
||||
except OSError:
|
||||
if not os.path.isdir(cache_dir):
|
||||
raise
|
||||
|
||||
return cache_path
|
||||
|
||||
@decorator
|
||||
def _cache(fn, *args, **kwargs):
|
||||
if cache.disabled:
|
||||
@ -219,7 +219,8 @@ def cache(*depends_on):
|
||||
key = '{}.{}'.format(fn.__module__, repr(fn).split('at')[0])
|
||||
|
||||
etag = '.'.join(_get_mtime(name) for name in depends_on)
|
||||
cache_path = _get_cache_path()
|
||||
cache_dir = get_cache_dir()
|
||||
cache_path = Path(cache_dir).joinpath('thefuck').as_posix()
|
||||
|
||||
try:
|
||||
with closing(shelve.open(cache_path)) as db:
|
||||
|
Loading…
x
Reference in New Issue
Block a user