1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-01 15:42:06 +00:00

Compare commits

...

12 Commits
1.32 ... 1.35

Author SHA1 Message Date
nvbn
a8ff2375c0 Bump to 1.35 2015-05-04 05:01:56 +02:00
Vladimir Iakovlev
80bfbec422 Update README.md 2015-05-04 05:00:11 +02:00
nvbn
3f2fe0d275 #89 #152 Use shell history 2015-05-04 04:44:16 +02:00
nvbn
72ac9650f9 Bump to 1.34 2015-05-03 13:25:01 +02:00
nvbn
93c90d5758 #157 Don't fail if can't get brew commands 2015-05-03 13:24:33 +02:00
nvbn
3ce8c1187c Make thefuck-alias depends on current shell 2015-05-03 13:04:33 +02:00
nvbn
bcd3154121 Bump to 1.33 2015-05-03 12:59:37 +02:00
nvbn
fcc2a1a40a #128 #69 add support of shell specific actions, add alias expansion for bash and zsh 2015-05-03 12:46:01 +02:00
nvbn
938f1df035 Remove not used fixture 2015-05-02 04:56:23 +02:00
nvbn
2acfea3350 #1 s/last_script/last_command/, s/last_fixed_script/last_fixed_command/ 2015-05-02 04:32:07 +02:00
nvbn
dd1861955c Refine tests 2015-05-02 04:29:55 +02:00
nvbn
ba601644d6 #1 Add history of last commands, allow fuck more than once 2015-05-01 08:38:38 +02:00
9 changed files with 443 additions and 182 deletions

View File

@@ -1,5 +1,7 @@
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck)
**Aliases changed in 1.34.**
Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
@@ -102,14 +104,20 @@ sudo pip install thefuck
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
And add to `.bashrc` or `.zshrc` or `.bash_profile`(for OSX):
And add to `.bashrc` or `.bash_profile`(for OSX):
```bash
alias fuck='eval $(thefuck $(fc -ln -1))'
alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
# You can use whatever you want as an alias, like for Mondays:
alias FUCK='fuck'
```
Or in your `.zshrc`:
```bash
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
```
Alternatively, you can redirect the output of `thefuck-alias`:
```bash

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
VERSION = '1.32'
VERSION = '1.35'
setup(name='thefuck',

View File

@@ -1,89 +1,100 @@
import pytest
import six
from mock import patch, Mock
from mock import Mock
from thefuck import conf
from tests.utils import Rule
def test_default():
assert Rule('test', enabled_by_default=True) in conf.DEFAULT_RULES
assert Rule('test', enabled_by_default=False) not in conf.DEFAULT_RULES
assert Rule('test', enabled_by_default=False) in (conf.DEFAULT_RULES + ['test'])
@pytest.mark.parametrize('enabled, rules, result', [
(True, conf.DEFAULT_RULES, True),
(False, conf.DEFAULT_RULES, False),
(False, conf.DEFAULT_RULES + ['test'], True)])
def test_default(enabled, rules, result):
assert (Rule('test', enabled_by_default=enabled) in rules) == result
def test_settings_defaults():
with patch('thefuck.conf.load_source', return_value=object()), \
patch('thefuck.conf.os.environ', new_callable=lambda: {}):
for key, val in conf.DEFAULT_SETTINGS.items():
assert getattr(conf.get_settings(Mock()), key) == val
@pytest.fixture
def load_source(monkeypatch):
mock = Mock()
monkeypatch.setattr('thefuck.conf.load_source', mock)
return mock
def test_settings_from_file():
with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)), \
patch('thefuck.conf.os.environ', new_callable=lambda: {}):
@pytest.fixture
def environ(monkeypatch):
data = {}
monkeypatch.setattr('thefuck.conf.os.environ', data)
return data
@pytest.mark.usefixture('environ')
def test_settings_defaults(load_source):
load_source.return_value = object()
for key, val in conf.DEFAULT_SETTINGS.items():
assert getattr(conf.get_settings(Mock()), key) == val
@pytest.mark.usefixture('environ')
class TestSettingsFromFile(object):
def test_from_file(self, load_source):
load_source.return_value = Mock(rules=['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)
settings = conf.get_settings(Mock())
assert settings.rules == ['test']
assert settings.wait_command == 10
assert settings.require_confirmation is True
assert settings.no_colors is True
def test_settings_from_file_with_DEFAULT():
with patch('thefuck.conf.load_source', return_value=Mock(rules=conf.DEFAULT_RULES + ['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)), \
patch('thefuck.conf.os.environ', new_callable=lambda: {}):
def test_from_file_with_DEFAULT(self, load_source):
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)
settings = conf.get_settings(Mock())
assert settings.rules == conf.DEFAULT_RULES + ['test']
def test_settings_from_env():
with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'],
wait_command=10)), \
patch('thefuck.conf.os.environ',
new_callable=lambda: {'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
'THEFUCK_NO_COLORS': 'false'}):
@pytest.mark.usefixture('load_source')
class TestSettingsFromEnv(object):
def test_from_env(self, environ):
environ.update({'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
'THEFUCK_NO_COLORS': 'false'})
settings = conf.get_settings(Mock())
assert settings.rules == ['bash', 'lisp']
assert settings.wait_command == 55
assert settings.require_confirmation is True
assert settings.no_colors is False
def test_settings_from_env_with_DEFAULT():
with patch('thefuck.conf.load_source', return_value=Mock()), \
patch('thefuck.conf.os.environ', new_callable=lambda: {'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}):
def test_from_env_with_DEFAULT(self, environ):
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
settings = conf.get_settings(Mock())
assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp']
def test_initialize_settings_file_ignore_if_exists():
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
assert settings_path_mock.is_file.call_count == 1
assert not settings_path_mock.open.called
class TestInitializeSettingsFile(object):
def test_ignore_if_exists(self):
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
assert settings_path_mock.is_file.call_count == 1
assert not settings_path_mock.open.called
def test_initialize_settings_file_create_if_exists_not():
settings_file = six.StringIO()
settings_path_mock = Mock(
is_file=Mock(return_value=False),
open=Mock(return_value=Mock(
__exit__=lambda *args: None, __enter__=lambda *args: settings_file
)),
)
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
settings_file_contents = settings_file.getvalue()
assert settings_path_mock.is_file.call_count == 1
assert settings_path_mock.open.call_count == 1
assert conf.SETTINGS_HEADER in settings_file_contents
for setting in conf.DEFAULT_SETTINGS.items():
assert '# {} = {}\n'.format(*setting) in settings_file_contents
settings_file.close()
def test_create_if_doesnt_exists(self):
settings_file = six.StringIO()
settings_path_mock = Mock(
is_file=Mock(return_value=False),
open=Mock(return_value=Mock(
__exit__=lambda *args: None, __enter__=lambda *args: settings_file)))
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
settings_file_contents = settings_file.getvalue()
assert settings_path_mock.is_file.call_count == 1
assert settings_path_mock.open.call_count == 1
assert conf.SETTINGS_HEADER in settings_file_contents
for setting in conf.DEFAULT_SETTINGS.items():
assert '# {} = {}\n'.format(*setting) in settings_file_contents
settings_file.close()

View File

@@ -1,115 +1,155 @@
import pytest
from subprocess import PIPE
from pathlib import PosixPath, Path
from mock import patch, Mock
from mock import Mock
from thefuck import main, conf, types
from tests.utils import Rule, Command
def test_load_rule():
def test_load_rule(monkeypatch):
match = object()
get_new_command = object()
with patch('thefuck.main.load_source',
return_value=Mock(
match=match,
get_new_command=get_new_command,
enabled_by_default=True)) as load_source:
assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command)
load_source.assert_called_once_with('bash', '/rules/bash.py')
load_source = Mock()
load_source.return_value = Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True)
monkeypatch.setattr('thefuck.main.load_source', load_source)
assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command)
load_source.assert_called_once_with('bash', '/rules/bash.py')
def test_get_rules():
with patch('thefuck.main.Path.glob') as glob, \
patch('thefuck.main.load_source',
lambda x, _: Mock(match=x, get_new_command=x,
enabled_by_default=True)):
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
assert list(main.get_rules(
Path('~'),
Mock(rules=conf.DEFAULT_RULES))) \
== [Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp'),
Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp')]
assert list(main.get_rules(
Path('~'),
Mock(rules=types.RulesNamesList(['bash'])))) \
== [Rule('bash', 'bash', 'bash'),
Rule('bash', 'bash', 'bash')]
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp'),
Rule('bash', 'bash', 'bash'),
Rule('lisp', 'lisp', 'lisp')]),
(types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'),
Rule('bash', 'bash', 'bash')])])
def test_get_rules(monkeypatch, conf_rules, rules):
monkeypatch.setattr(
'thefuck.main.Path.glob',
lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')])
monkeypatch.setattr('thefuck.main.load_source',
lambda x, _: Mock(match=x, get_new_command=x,
enabled_by_default=True))
assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules
def test_get_command():
with patch('thefuck.main.Popen') as Popen, \
patch('thefuck.main.os.environ',
new_callable=lambda: {}), \
patch('thefuck.main.wait_output',
return_value=True):
class TestGetCommand(object):
@pytest.fixture(autouse=True)
def Popen(self, monkeypatch):
Popen = Mock()
Popen.return_value.stdout.read.return_value = b'stdout'
Popen.return_value.stderr.read.return_value = b'stderr'
assert main.get_command(Mock(), ['thefuck', 'apt-get',
'search', 'vim']) \
monkeypatch.setattr('thefuck.main.Popen', Popen)
return Popen
@pytest.fixture(autouse=True)
def prepare(self, monkeypatch):
monkeypatch.setattr('thefuck.main.os.environ', {})
monkeypatch.setattr('thefuck.main.wait_output', lambda *_: True)
@pytest.fixture(autouse=True)
def generic_shell(self, monkeypatch):
monkeypatch.setattr('thefuck.shells.from_shell', lambda x: x)
monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
def test_get_command_calls(self, Popen):
assert main.get_command(Mock(),
['thefuck', 'apt-get', 'search', 'vim']) \
== Command('apt-get search vim', 'stdout', 'stderr')
Popen.assert_called_once_with('apt-get search vim',
shell=True,
stdout=PIPE,
stderr=PIPE,
env={'LANG': 'C'})
assert main.get_command(Mock(), ['']) is None
@pytest.mark.parametrize('args, result', [
(['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')])
def test_get_command_script(self, args, result):
if result:
assert main.get_command(Mock(), args).script == result
else:
assert main.get_command(Mock(), args) is None
def test_get_matched_rule(capsys):
rules = [Rule('', lambda x, _: x.script == 'cd ..'),
Rule('', lambda *_: False),
Rule('rule', Mock(side_effect=OSError('Denied')))]
assert main.get_matched_rule(Command('ls'),
rules, Mock(no_colors=True)) is None
assert main.get_matched_rule(Command('cd ..'),
rules, Mock(no_colors=True)) == rules[0]
assert capsys.readouterr()[1].split('\n')[0] \
== '[WARN] Rule rule:'
class TestGetMatchedRule(object):
def test_no_match(self):
assert main.get_matched_rule(
Command('ls'), [Rule('', lambda *_: False)],
Mock(no_colors=True)) is None
def test_match(self):
rule = Rule('', lambda x, _: x.script == 'cd ..')
assert main.get_matched_rule(
Command('cd ..'), [rule], Mock(no_colors=True)) == rule
def test_when_rule_failed(self, capsys):
main.get_matched_rule(
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
Mock(no_colors=True))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
def test_run_rule(capsys):
with patch('thefuck.main.confirm', return_value=True):
class TestRunRule(object):
@pytest.fixture(autouse=True)
def confirm(self, monkeypatch):
mock = Mock(return_value=True)
monkeypatch.setattr('thefuck.main.confirm', mock)
return mock
def test_run_rule(self, capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
None, None)
Command(), None)
assert capsys.readouterr() == ('new-command\n', '')
# With side effect:
def test_run_rule_with_side_effect(self, capsys):
side_effect = Mock()
settings = Mock()
command = Mock()
command = Command()
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
side_effect=side_effect),
command, settings)
assert capsys.readouterr() == ('new-command\n', '')
side_effect.assert_called_once_with(command, settings)
with patch('thefuck.main.confirm', return_value=False):
def test_when_not_comfirmed(self, capsys, confirm):
confirm.return_value = False
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
None, None)
Command(), None)
assert capsys.readouterr() == ('', '')
def test_confirm(capsys):
# When confirmation not required:
assert main.confirm('command', None, Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n')
# With side effect and without confirmation:
assert main.confirm('command', Mock(), Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command*\n')
# When confirmation required and confirmed:
with patch('thefuck.main.sys.stdin.read', return_value='\n'):
assert main.confirm(
'command', None, Mock(require_confirmation=True,
no_colors=True))
class TestConfirm(object):
@pytest.fixture
def stdin(self, monkeypatch):
mock = Mock(return_value='\n')
monkeypatch.setattr('sys.stdin.read', mock)
return mock
def test_when_not_required(self, capsys):
assert main.confirm('command', None, Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n')
def test_with_side_effect_and_without_confirmation(self, capsys):
assert main.confirm('command', Mock(), Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command*\n')
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed(self, capsys, stdin):
assert main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]')
# With side effect:
assert main.confirm(
'command', Mock(), Mock(require_confirmation=True,
no_colors=True))
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin):
assert main.confirm('command', Mock(), Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]')
# When confirmation required and ctrl+c:
with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt):
assert not main.confirm('command', None,
Mock(require_confirmation=True,
no_colors=True))
def test_when_confirmation_required_and_aborted(self, capsys, stdin):
stdin.side_effect = KeyboardInterrupt
assert not main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n')

85
tests/test_shells.py Normal file
View File

@@ -0,0 +1,85 @@
import pytest
from mock import Mock, MagicMock
from thefuck import shells
@pytest.fixture
def builtins_open(monkeypatch):
mock = MagicMock()
monkeypatch.setattr('six.moves.builtins.open', mock)
return mock
@pytest.fixture
def isfile(monkeypatch):
mock = Mock(return_value=True)
monkeypatch.setattr('os.path.isfile', mock)
return mock
class TestGeneric(object):
def test_from_shell(self):
assert shells.Generic().from_shell('pwd') == 'pwd'
def test_to_shell(self):
assert shells.Generic().to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open):
assert shells.Generic().put_to_history('ls') is None
assert builtins_open.call_count == 0
@pytest.mark.usefixtures('isfile')
class TestBash(object):
@pytest.fixture(autouse=True)
def Popen(self, monkeypatch):
mock = Mock()
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = (
b'alias l=\'ls -CF\'\n'
b'alias la=\'ls -A\'\n'
b'alias ll=\'ls -alF\'')
return mock
@pytest.mark.parametrize('before, after', [
('pwd', 'pwd'),
('ll', 'ls -alF')])
def test_from_shell(self, before, after):
assert shells.Bash().from_shell(before) == after
def test_to_shell(self):
assert shells.Bash().to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open):
shells.Bash().put_to_history('ls')
builtins_open.return_value.__enter__.return_value.\
write.assert_called_once_with('ls\n')
@pytest.mark.usefixtures('isfile')
class TestZsh(object):
@pytest.fixture(autouse=True)
def Popen(self, monkeypatch):
mock = Mock()
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = (
b'l=\'ls -CF\'\n'
b'la=\'ls -A\'\n'
b'll=\'ls -alF\'')
return mock
@pytest.mark.parametrize('before, after', [
('pwd', 'pwd'),
('ll', 'ls -alF')])
def test_from_shell(self, before, after):
assert shells.Zsh().from_shell(before) == after
def test_to_shell(self):
assert shells.Zsh().to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, monkeypatch):
monkeypatch.setattr('thefuck.shells.time',
lambda: 1430707243.3517463)
shells.Zsh().put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with(': 1430707243:0;ls\n')

View File

@@ -1,26 +1,26 @@
import pytest
from mock import Mock
from thefuck.utils import sudo_support, wrap_settings
from thefuck.types import Settings
from tests.utils import Command
def test_wrap_settings():
@pytest.mark.parametrize('override, old, new', [
({'key': 'val'}, {}, {'key': 'val'}),
({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})])
def test_wrap_settings(override, old, new):
fn = lambda _, settings: settings
assert wrap_settings({'key': 'val'})(fn)(None, Settings({})) \
== {'key': 'val'}
assert wrap_settings({'key': 'new-val'})(fn)(
None, Settings({'key': 'val'})) == {'key': 'new-val'}
assert wrap_settings(override)(fn)(None, Settings(old)) == new
def test_sudo_support():
fn = Mock(return_value=True, __name__='')
assert sudo_support(fn)(Command('sudo ls'), None)
fn.assert_called_once_with(Command('ls'), None)
fn.return_value = False
assert not sudo_support(fn)(Command('sudo ls'), None)
fn.return_value = 'pwd'
assert sudo_support(fn)(Command('sudo ls'), None) == 'sudo pwd'
assert sudo_support(fn)(Command('ls'), None) == 'pwd'
@pytest.mark.parametrize('return_value, command, called, result', [
('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'),
('ls -lah', 'ls', 'ls', 'ls -lah'),
(True, 'sudo ls', 'ls', True),
(True, 'ls', 'ls', True),
(False, 'sudo ls', 'ls', False),
(False, 'ls', 'ls', False)])
def test_sudo_support(return_value, command, called, result):
fn = Mock(return_value=return_value, __name__='')
assert sudo_support(fn)(Command(command), None) == result
fn.assert_called_once_with(Command(called), None)

View File

@@ -6,7 +6,8 @@ import os
import sys
from psutil import Process, TimeoutExpired
import colorama
from . import logs, conf, types
import six
from . import logs, conf, types, shells
def setup_user_dir():
@@ -61,7 +62,7 @@ def wait_output(settings, popen):
def get_command(settings, args):
"""Creates command from `args` and executes it."""
if sys.version_info[0] < 3:
if six.PY2:
script = ' '.join(arg.decode('utf-8') for arg in args[1:])
else:
script = ' '.join(args[1:])
@@ -69,6 +70,7 @@ def get_command(settings, args):
if not script:
return
script = shells.from_shell(script)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
env=dict(os.environ, LANG='C'))
if wait_output(settings, result):
@@ -103,20 +105,16 @@ def confirm(new_command, side_effect, settings):
def run_rule(rule, command, settings):
"""Runs command from rule for passed command."""
new_command = rule.get_new_command(command, settings)
new_command = shells.to_shell(rule.get_new_command(command, settings))
if confirm(new_command, rule.side_effect, settings):
if rule.side_effect:
rule.side_effect(command, settings)
shells.put_to_history(new_command)
print(new_command)
def is_second_run(command):
"""Is it the second run of `fuck`?"""
return command.script.startswith('fuck')
def alias():
print("\nalias fuck='eval $(thefuck $(fc -ln -1))'\n")
print(shells.app_alias())
def main():
@@ -126,10 +124,6 @@ def main():
command = get_command(settings, sys.argv)
if command:
if is_second_run(command):
logs.failed("Can't fuck twice", settings)
return
rules = get_rules(user_dir, settings)
matched_rule = get_matched_rule(command, rules, settings)
if matched_rule:

View File

@@ -2,7 +2,7 @@ import difflib
import os
import re
import subprocess
import thefuck.logs
BREW_CMD_PATH = '/Library/Homebrew/cmd'
TAP_PATH = '/Library/Taps'
@@ -10,7 +10,7 @@ TAP_CMD_PATH = '/%s/%s/cmd'
def _get_brew_path_prefix():
'''To get brew path'''
"""To get brew path"""
try:
return subprocess.check_output(['brew', '--prefix']).strip()
except:
@@ -18,18 +18,18 @@ def _get_brew_path_prefix():
def _get_brew_commands(brew_path_prefix):
'''To get brew default commands on local environment'''
"""To get brew default commands on local environment"""
brew_cmd_path = brew_path_prefix + BREW_CMD_PATH
commands = (name.replace('.rb', '') for name in os.listdir(brew_cmd_path)
if name.endswith('.rb'))
commands = [name.replace('.rb', '') for name in os.listdir(brew_cmd_path)
if name.endswith('.rb')]
return commands
def _get_brew_tap_specific_commands(brew_path_prefix):
'''To get tap's specific commands
https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115'''
"""To get tap's specific commands
https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115"""
commands = []
brew_taps_path = brew_path_prefix + TAP_PATH
@@ -61,17 +61,20 @@ def _get_directory_names_only(path):
return [d for d in os.listdir(path)
if os.path.isdir(os.path.join(path, d))]
brew_commands = []
brew_path_prefix = _get_brew_path_prefix()
# Failback commands for testing (Based on Homebrew 0.9.5)
brew_commands = ['info', 'home', 'options', 'install', 'uninstall',
'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
'doctor', 'create', 'edit']
if brew_path_prefix:
brew_commands += _get_brew_commands(brew_path_prefix)
brew_commands += _get_brew_tap_specific_commands(brew_path_prefix)
else:
# Failback commands for testing (Based on Homebrew 0.9.5)
brew_commands = ['info', 'home', 'options', 'install', 'uninstall',
'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
'doctor', 'create', 'edit']
try:
brew_commands = _get_brew_commands(brew_path_prefix) \
+ _get_brew_tap_specific_commands(brew_path_prefix)
except OSError:
pass
def _get_similar_commands(command):

120
thefuck/shells.py Normal file
View File

@@ -0,0 +1,120 @@
"""Module with shell specific actions, each shell class should
implement `from_shell`, `to_shell`, `app_alias` and `put_to_history`
methods.
"""
from collections import defaultdict
from subprocess import Popen, PIPE
from time import time
import os
from psutil import Process
FNULL = open(os.devnull, 'w')
class Generic(object):
def _get_aliases(self):
return {}
def _expand_aliases(self, command_script):
aliases = self._get_aliases()
binary = command_script.split(' ')[0]
if binary in aliases:
return command_script.replace(binary, aliases[binary], 1)
else:
return command_script
def from_shell(self, command_script):
"""Prepares command before running in app."""
return self._expand_aliases(command_script)
def to_shell(self, command_script):
"""Prepares command for running in shell."""
return command_script
def app_alias(self):
return "\nalias fuck='eval $(thefuck $(fc -ln -1))'\n"
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:
history.write(self._get_history_line(command_script))
class Bash(Generic):
def _parse_alias(self, alias):
name, value = alias.replace('alias ', '', 1).split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
value = value[1:-1]
return name, value
def _get_aliases(self):
proc = Popen('bash -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias)
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)
class Zsh(Generic):
def _parse_alias(self, alias):
name, value = alias.split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
value = value[1:-1]
return name, value
def _get_aliases(self):
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=FNULL, shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias)
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)
shells = defaultdict(lambda: Generic(), {
'bash': Bash(),
'zsh': Zsh()})
def _get_shell():
shell = Process(os.getpid()).parent().cmdline()[0]
return shells[shell]
def from_shell(command):
return _get_shell().from_shell(command)
def to_shell(command):
return _get_shell().to_shell(command)
def app_alias():
return _get_shell().app_alias()
def put_to_history(command):
return _get_shell().put_to_history(command)