mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-10-30 22:54:14 +00:00 
			
		
		
		
	| @@ -71,8 +71,8 @@ REPL-y 0.3.1 | |||||||
| ... | ... | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| If you are scary to blindly run changed command, there's `require_confirmation` | If you are scared to blindly run changed command, there's `require_confirmation` | ||||||
| [settings](#Settings) option: | [settings](#settings) option: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| ➜ apt-get install vim | ➜ apt-get install vim | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								release.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										31
									
								
								release.py
									
									
									
									
									
										Executable 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) | ||||||
							
								
								
									
										8
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,14 +1,18 @@ | |||||||
| from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||||
|  |  | ||||||
|  |  | ||||||
|  | VERSION = '1.26' | ||||||
|  |  | ||||||
|  |  | ||||||
| setup(name='thefuck', | setup(name='thefuck', | ||||||
|       version="1.22", |       version=VERSION, | ||||||
|       description="Magnificent app which corrects your previous console command", |       description="Magnificent app which corrects your previous console command", | ||||||
|       author='Vladimir Iakovlev', |       author='Vladimir Iakovlev', | ||||||
|       author_email='nvbn.rm@gmail.com', |       author_email='nvbn.rm@gmail.com', | ||||||
|       url='https://github.com/nvbn/thefuck', |       url='https://github.com/nvbn/thefuck', | ||||||
|       license='MIT', |       license='MIT', | ||||||
|       packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), |       packages=find_packages(exclude=['ez_setup', 'examples', | ||||||
|  |                                       'tests', 'release']), | ||||||
|       include_package_data=True, |       include_package_data=True, | ||||||
|       zip_safe=False, |       zip_safe=False, | ||||||
|       install_requires=['pathlib', 'psutil'], |       install_requires=['pathlib', 'psutil'], | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								tests/rules/test_cd_parent.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/rules/test_cd_parent.py
									
									
									
									
									
										Normal 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 ..' | ||||||
							
								
								
									
										69
									
								
								tests/rules/test_ssh_known_host.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								tests/rules/test_ssh_known_host.py
									
									
									
									
									
										Normal 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 | ||||||
| @@ -7,8 +7,13 @@ from thefuck.rules import switch_lang | |||||||
| def test_match(): | def test_match(): | ||||||
|     assert switch_lang.match(Mock(stderr='command not found: фзе-пуе', |     assert switch_lang.match(Mock(stderr='command not found: фзе-пуе', | ||||||
|                                   script=u'фзе-пуе'), None) |                                   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', |     assert not switch_lang.match(Mock(stderr='command not found: pat-get', | ||||||
|                                       script=u'pat-get'), None) |                                       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', |     assert not switch_lang.match(Mock(stderr='some info', | ||||||
|                                       script=u'фзе-пуе'), None) |                                       script=u'фзе-пуе'), None) | ||||||
|  |  | ||||||
| @@ -16,3 +21,5 @@ def test_match(): | |||||||
| def test_get_new_command(): | def test_get_new_command(): | ||||||
|     assert switch_lang.get_new_command( |     assert switch_lang.get_new_command( | ||||||
|         Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim' |         Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim' | ||||||
|  |     assert switch_lang.get_new_command( | ||||||
|  |         Mock(script=u'λσ -λα'), None) == 'ls -la' | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ def test_load_rule(): | |||||||
|                return_value=Mock( |                return_value=Mock( | ||||||
|                    match=match, |                    match=match, | ||||||
|                    get_new_command=get_new_command)) as load_source: |                    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') |         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')] |         glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] | ||||||
|         assert main.get_rules( |         assert main.get_rules( | ||||||
|             Path('~'), |             Path('~'), | ||||||
|             Mock(rules=None)) == [main.Rule('bash', 'bash'), |             Mock(rules=None)) == [main.Rule('bash', 'bash', 'bash'), | ||||||
|                                   main.Rule('lisp', 'lisp'), |                                   main.Rule('lisp', 'lisp', 'lisp'), | ||||||
|                                   main.Rule('bash', 'bash'), |                                   main.Rule('bash', 'bash', 'bash'), | ||||||
|                                   main.Rule('lisp', 'lisp')] |                                   main.Rule('lisp', 'lisp', 'lisp')] | ||||||
|         assert main.get_rules( |         assert main.get_rules( | ||||||
|             Path('~'), |             Path('~'), | ||||||
|             Mock(rules=['bash'])) == [main.Rule('bash', 'bash'), |             Mock(rules=['bash'])) == [main.Rule('bash', 'bash', 'bash'), | ||||||
|                                       main.Rule('bash', 'bash')] |                                       main.Rule('bash', 'bash', 'bash')] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_command(): | def test_get_command(): | ||||||
| @@ -61,24 +61,28 @@ def test_get_command(): | |||||||
|                                       stdout=PIPE, |                                       stdout=PIPE, | ||||||
|                                       stderr=PIPE, |                                       stderr=PIPE, | ||||||
|                                       env={'LANG': 'C'}) |                                       env={'LANG': 'C'}) | ||||||
|  |         assert main.get_command(Mock(), ['']) is None | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_matched_rule(): | def test_get_matched_rule(capsys): | ||||||
|     rules = [main.Rule(lambda x, _: x.script == 'cd ..', None), |     rules = [main.Rule('', lambda x, _: x.script == 'cd ..', None), | ||||||
|              main.Rule(lambda *_: False, None)] |              main.Rule('', lambda *_: False, None), | ||||||
|  |              main.Rule('rule', Mock(side_effect=OSError('Denied')), None)] | ||||||
|     assert main.get_matched_rule(main.Command('ls', '', ''), |     assert main.get_matched_rule(main.Command('ls', '', ''), | ||||||
|                                  rules, None) is None |                                  rules, None) is None | ||||||
|     assert main.get_matched_rule(main.Command('cd ..', '', ''), |     assert main.get_matched_rule(main.Command('cd ..', '', ''), | ||||||
|                                  rules, None) == rules[0] |                                  rules, None) == rules[0] | ||||||
|  |     assert capsys.readouterr()[1].split('\n')[0]\ | ||||||
|  |            == '[WARN] rule: Traceback (most recent call last):' | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_run_rule(capsys): | def test_run_rule(capsys): | ||||||
|     with patch('thefuck.main.confirm', return_value=True): |     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) |                       None, None) | ||||||
|         assert capsys.readouterr() == ('new-command\n', '') |         assert capsys.readouterr() == ('new-command\n', '') | ||||||
|     with patch('thefuck.main.confirm', return_value=False): |     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) |                       None, None) | ||||||
|         assert capsys.readouterr() == ('', '') |         assert capsys.readouterr() == ('', '') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,11 +5,12 @@ from os.path import expanduser | |||||||
| from subprocess import Popen, PIPE | from subprocess import Popen, PIPE | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  | from traceback import format_exception | ||||||
| from psutil import Process, TimeoutExpired | from psutil import Process, TimeoutExpired | ||||||
|  |  | ||||||
|  |  | ||||||
| Command = namedtuple('Command', ('script', 'stdout', 'stderr')) | 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(): | def setup_user_dir(): | ||||||
| @@ -43,7 +44,8 @@ def is_rule_enabled(settings, rule): | |||||||
| def load_rule(rule): | def load_rule(rule): | ||||||
|     """Imports rule module and returns it.""" |     """Imports rule module and returns it.""" | ||||||
|     rule_module = load_source(rule.name[:-3], str(rule)) |     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): | 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:]) |         script = ' '.join(arg.decode('utf-8') for arg in args[1:]) | ||||||
|     else: |     else: | ||||||
|         script = ' '.join(args[1:]) |         script = ' '.join(args[1:]) | ||||||
|  |  | ||||||
|  |     if not script: | ||||||
|  |         return | ||||||
|  |  | ||||||
|     result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, |     result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, | ||||||
|                    env=dict(os.environ, LANG='C')) |                    env=dict(os.environ, LANG='C')) | ||||||
|     if wait_output(settings, result): |     if wait_output(settings, result): | ||||||
| @@ -90,8 +96,12 @@ def get_command(settings, args): | |||||||
| def get_matched_rule(command, rules, settings): | def get_matched_rule(command, rules, settings): | ||||||
|     """Returns first matched rule for command.""" |     """Returns first matched rule for command.""" | ||||||
|     for rule in rules: |     for rule in rules: | ||||||
|  |         try: | ||||||
|             if rule.match(command, settings): |             if rule.match(command, settings): | ||||||
|                 return rule |                 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): | def confirm(new_command, settings): | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								thefuck/rules/ssh_known_hosts.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								thefuck/rules/ssh_known_hosts.py
									
									
									
									
									
										Normal 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 | ||||||
| @@ -4,6 +4,7 @@ patterns = ['permission denied', | |||||||
|             'you cannot perform this operation unless you are root', |             'you cannot perform this operation unless you are root', | ||||||
|             'non-root users cannot', |             'non-root users cannot', | ||||||
|             'Operation not permitted', |             'Operation not permitted', | ||||||
|  |             'root privilege', | ||||||
|             'This command has to be run under the root user.'] |             'This command has to be run under the root user.'] | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,8 @@ | |||||||
| target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''' | target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''' | ||||||
|  |  | ||||||
| source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''', | source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''', | ||||||
|                   u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟'''] |                   u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''', | ||||||
|  |                   u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''] | ||||||
|  |  | ||||||
|  |  | ||||||
| def _get_matched_layout(command): | def _get_matched_layout(command): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user