1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-04 09:02:08 +00:00

Compare commits

..

24 Commits
1.43 ... 1.44

Author SHA1 Message Date
nvbn
a5aadc6e90 Bump to 1.44 2015-05-28 21:31:10 +03:00
nvbn
18ce062300 Merge branch 'diezcami-java' 2015-05-28 18:03:37 +03:00
nvbn
73bc6c0184 Merge branch 'java' of https://github.com/diezcami/thefuck into diezcami-java
Conflicts:
	README.md
2015-05-28 18:03:24 +03:00
Vladimir Iakovlev
0296a4a46d Merge pull request #227 from Dugucloud/master
Added a sudo string of Fedora's fedup
2015-05-28 18:02:00 +03:00
Vladimir Iakovlev
54a9769c10 Merge pull request #224 from diezcami/javac
Added javac rule
2015-05-28 18:01:14 +03:00
Vladimir Iakovlev
abc7238d14 Merge pull request #219 from scorphus/fix-shell-fish
fix(shell::Fish): avoid looping when calling `fuck` twice
2015-05-28 18:00:42 +03:00
Dugucloud
710a72ee8c Added sudo string for Fedora's fedup 2015-05-28 09:46:41 +08:00
秋纫
e09c6530e5 Merge pull request #3 from nvbn/master
Sync with master
2015-05-28 09:41:18 +08:00
Cami Diez
b1da6a883a Added java rule 2015-05-27 15:50:41 +08:00
Cami Diez
a9e3b22fa4 Added javac rule 2015-05-27 15:47:34 +08:00
Pablo Santiago Blum de Aguiar
9debcdf676 fix(shells::Fish): avoid looping when calling fuck twice
Or whatever the `thefuck` function name is.

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-27 00:39:47 -03:00
Vladimir Iakovlev
718cadb85a #216 add open rule to readme 2015-05-23 18:49:20 +03:00
Vladimir Iakovlev
910e6f4759 Merge pull request #216 from diezcami/master
Addressed Issue #210
2015-05-23 18:45:52 +03:00
Cami Diez
d3146aa0ac Addressed Issue #210 2015-05-23 23:18:15 +08:00
nvbn
190e47ecdb #215 Use memoize decorator for caching 2015-05-22 17:07:01 +03:00
Vladimir Iakovlev
84a28d8c73 Merge pull request #215 from scorphus/fish-functions
Cache aliases to speed up subsequent calls and add support to Fish functions
2015-05-22 16:55:00 +03:00
Pablo Santiago Blum de Aguiar
551e35e3b6 refact(shells): add support to Fish functions
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-21 23:56:37 -03:00
Pablo Santiago Blum de Aguiar
2bebfabf8d refact(shells): cache aliases to speed up subsequent calls
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-21 23:56:28 -03:00
Vladimir Iakovlev
675317b247 Merge pull request #214 from scorphus/improve-man
refact(man): do not match if there's no argument to man
2015-05-21 15:42:26 +03:00
Pablo Santiago Blum de Aguiar
6cf430cc23 refact(man): do not match if there's no argument to man
If there's no argument to man, a call to thefuck should just give no
fuck.

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-21 00:00:22 -03:00
Dugucloud
7e55041963 Merge branch 'master' of https://github.com/nvbn/thefuck 2015-05-10 15:46:06 +08:00
Dugucloud
fc364b99b9 Revert "Added colorama in requirements.txt"
This reverts commit 742f6f9c94.
2015-04-22 23:18:11 +08:00
Dugucloud
742f6f9c94 Added colorama in requirements.txt 2015-04-22 21:48:17 +08:00
秋纫
cd1bee9cb0 Merge pull request #2 from nvbn/master
Sync with master
2015-04-22 21:36:07 +08:00
15 changed files with 203 additions and 10 deletions

View File

@@ -155,6 +155,8 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration; * `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration;
* `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration; * `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration;
* `fix_alt_space` &ndash; replaces Alt+Space with Space character; * `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `java` &ndash; removes `.java` extension when running Java programs;
* `git_add` &ndash; fix *"Did you forget to 'git add'?"*; * `git_add` &ndash; fix *"Did you forget to 'git add'?"*;
* `git_checkout` &ndash; creates the branch before checking-out; * `git_checkout` &ndash; creates the branch before checking-out;
* `git_no_command` &ndash; fixes wrong git commands like `git brnch`; * `git_no_command` &ndash; fixes wrong git commands like `git brnch`;
@@ -170,6 +172,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent; * `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`; * `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands; * `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
* `open` &ndash; prepends `http` to address passed to `open`;
* `pip_unknown_command` &ndash; fixes wrong pip commands, for example `pip instatl/pip install`; * `pip_unknown_command` &ndash; fixes wrong pip commands, for example `pip instatl/pip install`;
* `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script; * `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script;
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory; * `rm_dir` &ndash; adds `-rf` when you trying to remove directory;

View File

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

17
tests/rules/test_java.py Normal file
View File

@@ -0,0 +1,17 @@
import pytest
from thefuck.rules.java import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='java foo.java'),
Command(script='java bar.java')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('java foo.java'), 'java foo'),
(Command('java bar.java'), 'java bar')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

17
tests/rules/test_javac.py Normal file
View File

@@ -0,0 +1,17 @@
import pytest
from thefuck.rules.javac import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='javac foo'),
Command(script='javac bar')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('javac foo'), 'javac foo.java'),
(Command('javac bar'), 'javac bar.java')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -2,6 +2,7 @@ import pytest
from thefuck.rules.man import match, get_new_command from thefuck.rules.man import match, get_new_command
from tests.utils import Command from tests.utils import Command
@pytest.mark.parametrize('command', [ @pytest.mark.parametrize('command', [
Command('man read'), Command('man read'),
Command('man 2 read'), Command('man 2 read'),
@@ -14,6 +15,13 @@ def test_match(command):
assert match(command, None) assert match(command, None)
@pytest.mark.parametrize('command', [
Command('man'),
Command('man ')])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('man read'), 'man 3 read'), (Command('man read'), 'man 3 read'),
(Command('man 2 read'), 'man 3 read'), (Command('man 2 read'), 'man 3 read'),

25
tests/rules/test_open.py Normal file
View File

@@ -0,0 +1,25 @@
import pytest
from thefuck.rules.open import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='open foo.com'),
Command(script='open foo.ly'),
Command(script='open foo.org'),
Command(script='open foo.net'),
Command(script='open foo.se'),
Command(script='open foo.io')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('open foo.com'), 'open http://foo.com'),
(Command('open foo.ly'), 'open http://foo.ly'),
(Command('open foo.org'), 'open http://foo.org'),
(Command('open foo.net'), 'open http://foo.net'),
(Command('open foo.se'), 'open http://foo.se'),
(Command('open foo.io'), 'open http://foo.io')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -78,9 +78,22 @@ class TestFish(object):
def shell(self): def shell(self):
return shells.Fish() return shells.Fish()
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen')
mock.return_value.stdout.read.return_value = (
b'fish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nmath')
return mock
@pytest.mark.parametrize('before, after', [ @pytest.mark.parametrize('before, after', [
('pwd', 'pwd'), ('pwd', 'pwd'),
('ll', 'll')]) # Fish has no aliases but functions ('fuck', 'fish -ic "fuck"'),
('find', 'find'),
('funced', 'fish -ic "funced"'),
('awk', 'awk'),
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
('vim', 'vim'),
('ll', 'fish -ic "ll"')]) # Fish has no aliases but functions
def test_from_shell(self, before, after, shell): def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after assert shell.from_shell(before) == after
@@ -98,7 +111,14 @@ class TestFish(object):
assert shell.and_('foo', 'bar') == 'foo; and bar' assert shell.and_('foo', 'bar') == 'foo; and bar'
def test_get_aliases(self, shell): def test_get_aliases(self, shell):
assert shell.get_aliases() == {} assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck',
'funced': 'funced',
'funcsave': 'funcsave',
'grep': 'grep',
'history': 'history',
'll': 'll',
'math': 'math'}
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')

View File

@@ -1,6 +1,6 @@
import pytest import pytest
from mock import Mock from mock import Mock
from thefuck.utils import sudo_support, wrap_settings from thefuck.utils import sudo_support, wrap_settings, memoize
from thefuck.types import Settings from thefuck.types import Settings
from tests.utils import Command from tests.utils import Command
@@ -24,3 +24,11 @@ def test_sudo_support(return_value, command, called, result):
fn = Mock(return_value=return_value, __name__='') fn = Mock(return_value=return_value, __name__='')
assert sudo_support(fn)(Command(command), None) == result assert sudo_support(fn)(Command(command), None) == result
fn.assert_called_once_with(Command(called), None) fn.assert_called_once_with(Command(called), None)
def test_memoize():
fn = Mock(__name__='fn')
memoized = memoize(fn)
memoized()
memoized()
fn.assert_called_once_with()

13
thefuck/rules/java.py Normal file
View File

@@ -0,0 +1,13 @@
# Fixes common java command mistake
#
# Example:
# > java foo.java
# Error: Could not find or load main class foo.java
#
def match(command, settings):
return (command.script.startswith ('java ')
and command.script.endswith ('.java'))
def get_new_command(command, settings):
return command.script[:-5]

15
thefuck/rules/javac.py Normal file
View File

@@ -0,0 +1,15 @@
# Appends .java when compiling java files
#
# Example:
# > javac foo
# error: Class names, 'foo', are only accepted if annotation
# processing is explicitly requested
#
#
def match(command, settings):
return (command.script.startswith ('javac ')
and not command.script.endswith('.java'))
def get_new_command(command, settings):
return command.script + '.java'

View File

@@ -1,5 +1,5 @@
def match(command, settings): def match(command, settings):
return command.script.startswith('man') return command.script.strip().startswith('man ')
def get_new_command(command, settings): def get_new_command(command, settings):

24
thefuck/rules/open.py Normal file
View File

@@ -0,0 +1,24 @@
# Opens URL's in the default web browser
#
# Example:
# > open github.com
# The file ~/github.com does not exist.
# Perhaps you meant 'http://github.com'?
#
#
def match(command, settings):
return (command.script.startswith ('open')
and (
# Wanted to use this:
# 'http' in command.stderr
'.com' in command.script
or '.net' in command.script
or '.org' in command.script
or '.ly' in command.script
or '.io' in command.script
or '.se' in command.script
or '.edu' in command.script))
def get_new_command(command, settings):
return 'open http://' + command.script[5:]

View File

@@ -11,7 +11,8 @@ patterns = ['permission denied',
'requested operation requires superuser privilege', 'requested operation requires superuser privilege',
'must be run as root', 'must be run as root',
'must be superuser', 'must be superuser',
'Need to be root'] 'Need to be root',
'you must be root to run this program.']
def match(command, settings): def match(command, settings):

View File

@@ -8,10 +8,11 @@ from subprocess import Popen, PIPE
from time import time from time import time
import os import os
from psutil import Process from psutil import Process
from .utils import DEVNULL from .utils import DEVNULL, memoize
class Generic(object): class Generic(object):
def get_aliases(self): def get_aliases(self):
return {} return {}
@@ -61,8 +62,10 @@ class Bash(Generic):
value = value[1:-1] value = value[1:-1]
return name, value return name, value
@memoize
def get_aliases(self): def get_aliases(self):
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')
@@ -91,6 +94,25 @@ class Fish(Generic):
" end\n" " end\n"
"end") "end")
@memoize
def get_aliases(self):
proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
shell=True)
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
return {function: function for function in functions}
def _expand_aliases(self, command_script):
aliases = self.get_aliases()
binary = command_script.split(' ')[0]
if binary in aliases:
return 'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
else:
return command_script
def from_shell(self, command_script):
"""Prepares command before running in app."""
return self._expand_aliases(command_script)
def _get_history_file_name(self): def _get_history_file_name(self):
return os.path.expanduser('~/.config/fish/fish_history') return os.path.expanduser('~/.config/fish/fish_history')
@@ -111,8 +133,10 @@ class Zsh(Generic):
value = value[1:-1] value = value[1:-1]
return name, value return name, value
@memoize
def get_aliases(self): def get_aliases(self):
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')
@@ -134,8 +158,10 @@ class Tcsh(Generic):
name, value = alias.split("\t", 1) name, value = alias.split("\t", 1)
return name, value return name, value
@memoize
def get_aliases(self): def get_aliases(self):
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')

View File

@@ -1,5 +1,6 @@
from functools import wraps from functools import wraps
import os import os
import pickle
import six import six
from .types import Command from .types import Command
@@ -62,3 +63,18 @@ def sudo_support(fn):
else: else:
return result return result
return wrapper return wrapper
def memoize(fn):
"""Caches previous calls to the function."""
memo = {}
@wraps(fn)
def wrapper(*args, **kwargs):
key = pickle.dumps((args, kwargs))
if key not in memo:
memo[key] = fn(*args, **kwargs)
return memo[key]
return wrapper