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