1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-01-31 02:01:13 +00:00

Merge pull request #1 from nvbn/master

Synchronize with nvbn's repo.
This commit is contained in:
秋纫 2015-04-22 09:23:20 +08:00
commit d2356c570e
11 changed files with 197 additions and 21 deletions

View File

@ -71,8 +71,8 @@ REPL-y 0.3.1
...
```
If you are scary to blindly run changed command, there's `require_confirmation`
[settings](#Settings) option:
If you are scared to blindly run changed command, there's `require_confirmation`
[settings](#settings) option:
```bash
➜ apt-get install vim

31
release.py Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env python
from subprocess import call
import re
version = None
def get_new_setup_py_lines():
global version
with open('setup.py', 'r') as sf:
current_setup = sf.readlines()
for line in current_setup:
if line.startswith('VERSION = '):
major, minor = re.findall(r"VERSION = '(\d+)\.(\d+)'", line)[0]
version = "{}.{}".format(major, int(minor) + 1)
yield "VERSION = '{}'\n".format(version)
else:
yield line
lines = list(get_new_setup_py_lines())
with open('setup.py', 'w') as sf:
sf.writelines(lines)
call('git pull', shell=True)
call('git commit -am "Bump to {}"'.format(version), shell=True)
call('git tag {}'.format(version), shell=True)
call('git push', shell=True)
call('git push --tags', shell=True)
call('python setup.py sdist upload', shell=True)

View File

@ -1,14 +1,18 @@
from setuptools import setup, find_packages
VERSION = '1.26'
setup(name='thefuck',
version="1.22",
version=VERSION,
description="Magnificent app which corrects your previous console command",
author='Vladimir Iakovlev',
author_email='nvbn.rm@gmail.com',
url='https://github.com/nvbn/thefuck',
license='MIT',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
packages=find_packages(exclude=['ez_setup', 'examples',
'tests', 'release']),
include_package_data=True,
zip_safe=False,
install_requires=['pathlib', 'psutil'],

View File

@ -0,0 +1,12 @@
from thefuck.main import Command
from thefuck.rules.cd_parent import match, get_new_command
def test_match():
assert match(Command('cd..', '', 'cd..: command not found'), None)
assert not match(Command('', '', ''), None)
def test_get_new_command():
assert get_new_command(
Command('cd..', '', ''), None) == 'cd ..'

View File

@ -0,0 +1,69 @@
import os
import pytest
from mock import Mock
from thefuck.main import Command
from thefuck.rules.ssh_known_hosts import match, get_new_command, remove_offending_keys
@pytest.fixture
def ssh_error(tmpdir):
path = os.path.join(str(tmpdir), 'known_hosts')
def reset(path):
with open(path, 'w') as fh:
lines = [
'123.234.567.890 asdjkasjdakjsd\n'
'98.765.432.321 ejioweojwejrosj\n'
'111.222.333.444 qwepoiwqepoiss\n'
]
fh.writelines(lines)
def known_hosts(path):
with open(path, 'r') as fh:
return fh.readlines()
reset(path)
errormsg = u"""@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
b6:cb:07:34:c0:a0:94:d3:0d:69:83:31:f4:c5:20:9b.
Please contact your system administrator.
Add correct host key in {0} to get rid of this message.
Offending RSA key in {0}:2
RSA host key for {1} has changed and you have requested strict checking.
Host key verification failed.""".format(path, '98.765.432.321')
return errormsg, path, reset, known_hosts
def test_match(ssh_error):
errormsg, _, _, _ = ssh_error
assert match(Command('ssh', '', errormsg), None)
assert match(Command('ssh', '', errormsg), None)
assert match(Command('scp something something', '', errormsg), None)
assert match(Command('scp something something', '', errormsg), None)
assert not match(Command('', '', errormsg), None)
assert not match(Command('notssh', '', errormsg), None)
assert not match(Command('ssh', '', ''), None)
def test_remove_offending_keys(ssh_error):
errormsg, path, reset, known_hosts = ssh_error
command = Command('ssh user@host', '', errormsg)
remove_offending_keys(command, None)
expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n']
assert known_hosts(path) == expected
def test_get_new_command(ssh_error, monkeypatch):
errormsg, _, _, _ = ssh_error
method = Mock()
monkeypatch.setattr('thefuck.rules.ssh_known_hosts.remove_offending_keys', method)
assert get_new_command(Command('ssh user@host', '', errormsg), None) == 'ssh user@host'
assert method.call_count

View File

@ -7,8 +7,13 @@ from thefuck.rules import switch_lang
def test_match():
assert switch_lang.match(Mock(stderr='command not found: фзе-пуе',
script=u'фзе-пуе'), None)
assert switch_lang.match(Mock(stderr='command not found: λσ',
script=u'λσ'), None)
assert not switch_lang.match(Mock(stderr='command not found: pat-get',
script=u'pat-get'), None)
assert not switch_lang.match(Mock(stderr='command not found: ls',
script=u'ls'), None)
assert not switch_lang.match(Mock(stderr='some info',
script=u'фзе-пуе'), None)
@ -16,3 +21,5 @@ def test_match():
def test_get_new_command():
assert switch_lang.get_new_command(
Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim'
assert switch_lang.get_new_command(
Mock(script=u'λσ -λα'), None) == 'ls -la'

View File

@ -24,7 +24,7 @@ def test_load_rule():
return_value=Mock(
match=match,
get_new_command=get_new_command)) as load_source:
assert main.load_rule(Path('/rules/bash.py')) == main.Rule(match, get_new_command)
assert main.load_rule(Path('/rules/bash.py')) == main.Rule('bash', match, get_new_command)
load_source.assert_called_once_with('bash', '/rules/bash.py')
@ -35,14 +35,14 @@ def test_get_rules():
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
assert main.get_rules(
Path('~'),
Mock(rules=None)) == [main.Rule('bash', 'bash'),
main.Rule('lisp', 'lisp'),
main.Rule('bash', 'bash'),
main.Rule('lisp', 'lisp')]
Mock(rules=None)) == [main.Rule('bash', 'bash', 'bash'),
main.Rule('lisp', 'lisp', 'lisp'),
main.Rule('bash', 'bash', 'bash'),
main.Rule('lisp', 'lisp', 'lisp')]
assert main.get_rules(
Path('~'),
Mock(rules=['bash'])) == [main.Rule('bash', 'bash'),
main.Rule('bash', 'bash')]
Mock(rules=['bash'])) == [main.Rule('bash', 'bash', 'bash'),
main.Rule('bash', 'bash', 'bash')]
def test_get_command():
@ -61,24 +61,28 @@ def test_get_command():
stdout=PIPE,
stderr=PIPE,
env={'LANG': 'C'})
assert main.get_command(Mock(), ['']) is None
def test_get_matched_rule():
rules = [main.Rule(lambda x, _: x.script == 'cd ..', None),
main.Rule(lambda *_: False, None)]
def test_get_matched_rule(capsys):
rules = [main.Rule('', lambda x, _: x.script == 'cd ..', None),
main.Rule('', lambda *_: False, None),
main.Rule('rule', Mock(side_effect=OSError('Denied')), None)]
assert main.get_matched_rule(main.Command('ls', '', ''),
rules, None) is None
assert main.get_matched_rule(main.Command('cd ..', '', ''),
rules, None) == rules[0]
assert capsys.readouterr()[1].split('\n')[0]\
== '[WARN] rule: Traceback (most recent call last):'
def test_run_rule(capsys):
with patch('thefuck.main.confirm', return_value=True):
main.run_rule(main.Rule(None, lambda *_: 'new-command'),
main.run_rule(main.Rule('', None, lambda *_: 'new-command'),
None, None)
assert capsys.readouterr() == ('new-command\n', '')
with patch('thefuck.main.confirm', return_value=False):
main.run_rule(main.Rule(None, lambda *_: 'new-command'),
main.run_rule(main.Rule('', None, lambda *_: 'new-command'),
None, None)
assert capsys.readouterr() == ('', '')

View File

@ -5,11 +5,12 @@ from os.path import expanduser
from subprocess import Popen, PIPE
import os
import sys
from traceback import format_exception
from psutil import Process, TimeoutExpired
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
Rule = namedtuple('Rule', ('match', 'get_new_command'))
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command'))
def setup_user_dir():
@ -43,7 +44,8 @@ def is_rule_enabled(settings, rule):
def load_rule(rule):
"""Imports rule module and returns it."""
rule_module = load_source(rule.name[:-3], str(rule))
return Rule(rule_module.match, rule_module.get_new_command)
return Rule(rule.name[:-3], rule_module.match,
rule_module.get_new_command)
def get_rules(user_dir, settings):
@ -80,6 +82,10 @@ def get_command(settings, args):
script = ' '.join(arg.decode('utf-8') for arg in args[1:])
else:
script = ' '.join(args[1:])
if not script:
return
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
env=dict(os.environ, LANG='C'))
if wait_output(settings, result):
@ -90,8 +96,12 @@ def get_command(settings, args):
def get_matched_rule(command, rules, settings):
"""Returns first matched rule for command."""
for rule in rules:
if rule.match(command, settings):
return rule
try:
if rule.match(command, settings):
return rule
except Exception:
sys.stderr.write(u'[WARN] {}: {}---------------------\n\n'.format(
rule.name, ''.join(format_exception(*sys.exc_info()))))
def confirm(new_command, settings):

View File

@ -0,0 +1,37 @@
import re
patterns = [
r'WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!',
r'WARNING: POSSIBLE DNS SPOOFING DETECTED!',
r"Warning: the \S+ host key for '([^']+)' differs from the key for the IP address '([^']+)'",
]
offending_pattern = re.compile(
r'(?:Offending (?:key for IP|\S+ key)|Matching host key) in ([^:]+):(\d+)',
re.MULTILINE)
commands = ['ssh', 'scp']
def match(command, settings):
if not command.script:
return False
if not command.script.split()[0] in commands:
return False
if not any([re.findall(pattern, command.stderr) for pattern in patterns]):
return False
return True
def remove_offending_keys(command, settings):
offending = offending_pattern.findall(command.stderr)
for filepath, lineno in offending:
with open(filepath, 'r') as fh:
lines = fh.readlines()
del lines[int(lineno) - 1]
with open(filepath, 'w') as fh:
fh.writelines(lines)
def get_new_command(command, settings):
remove_offending_keys(command, settings)
return command.script

View File

@ -4,6 +4,7 @@ patterns = ['permission denied',
'you cannot perform this operation unless you are root',
'non-root users cannot',
'Operation not permitted',
'root privilege',
'This command has to be run under the root user.']

View File

@ -3,7 +3,8 @@
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''']
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''',
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
def _get_matched_layout(command):