1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-07 10:32:01 +00:00

Compare commits

...

36 Commits
1.42 ... 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
nvbn
d088dac0f4 Bump to 1.43 2015-05-21 01:07:41 +03:00
nvbn
c65fdd0f81 Add rule for django south inconsistent migrations 2015-05-21 00:55:23 +03:00
nvbn
e7d7b80c09 Add rule for django south ghost migrations 2015-05-21 00:49:56 +03:00
Vladimir Iakovlev
f986df23d5 Merge pull request #212 from scorphus/fix-whois
fix(whois): check if there's at least one argument to `whois`
2015-05-21 00:33:22 +03:00
Vladimir Iakovlev
cf5f18bf23 Merge pull request #211 from mcarton/man
Add a rule to change man section
2015-05-21 00:32:23 +03:00
Pablo Santiago Blum de Aguiar
44c06c483e fix(whois): check if there's at least one argument to whois
This avoids thefuck failing when there's no arguments. It fails with:

```
  ...
  File "thefuck/rules/whois.py", line 26, in get_new_command
    url = command.script.split()[1]
IndexError: list index out of range
```

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-20 13:54:33 -03:00
mcarton
1f48d5e12a Add a rule to change man section 2015-05-20 18:08:15 +02:00
nvbn
2c3df1ad47 #209 add support of aliases to no_command 2015-05-20 16:58:05 +03:00
nvbn
5319871326 #209 add get_aliases to shells 2015-05-20 16:56:42 +03:00
nvbn
d6ce5e1e62 #208 .name isn't callable in specific psutil 2015-05-20 16:41:11 +03:00
Vladimir Iakovlev
c42898dde3 Merge pull request #208 from tevino/get-shell-fix
better way to get shell
2015-05-20 16:39:08 +03:00
Tevin Zhang
17b9104939 better way to get shell 2015-05-20 15:30:20 +08: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
23 changed files with 448 additions and 51 deletions

View File

@@ -152,7 +152,11 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `cp_omitting_directory` &ndash; adds `-a` when you `cp` directory; * `cp_omitting_directory` &ndash; adds `-a` when you `cp` directory;
* `cpp11` &ndash; add missing `-std=c++11` to `g++` or `clang++`; * `cpp11` &ndash; add missing `-std=c++11` to `g++` or `clang++`;
* `dry` &ndash; fix repetitions like "git git push"; * `dry` &ndash; fix repetitions like "git git push";
* `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;
* `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`;
@@ -163,10 +167,12 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `has_exists_script` &ndash; prepends `./` when script/binary exists; * `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`; * `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `ls_lah` &ndash; adds -lah to ls; * `ls_lah` &ndash; adds -lah to ls;
* `man` &ndash; change manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`; * `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `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.42' VERSION = '1.44'
setup(name='thefuck', setup(name='thefuck',

View File

@@ -0,0 +1,53 @@
import pytest
from thefuck.rules.django_south_ghost import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''Traceback (most recent call last):
File "/home/nvbn/work/.../bin/python", line 42, in <module>
exec(compile(__file__f.read(), __file__, "exec"))
File "/home/nvbn/work/.../app/manage.py", line 34, in <module>
execute_from_command_line(sys.argv)
File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 443, in execute_from_command_line
utility.execute()
File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 382, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/nvbn/work/.../lib/django/core/management/base.py", line 196, in run_from_argv
self.execute(*args, **options.__dict__)
File "/home/nvbn/work/.../lib/django/core/management/base.py", line 232, in execute
output = self.handle(*args, **options)
File "/home/nvbn/work/.../app/lib/south/management/commands/migrate.py", line 108, in handle
ignore_ghosts = ignore_ghosts,
File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 193, in migrate_app
applied_all = check_migration_histories(applied_all, delete_ghosts, ignore_ghosts)
File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 88, in check_migration_histories
raise exceptions.GhostMigrations(ghosts)
south.exceptions.GhostMigrations:
! These migrations are in the database but not on disk:
<app1: 0033_auto__...>
<app1: 0034_fill_...>
<app1: 0035_rename_...>
<app2: 0003_add_...>
<app2: 0004_denormalize_...>
<app1: 0033_auto....>
<app1: 0034_fill...>
! I'm not trusting myself; either fix this yourself by fiddling
! with the south_migrationhistory table, or pass --delete-ghost-migrations
! to South to have it delete ALL of these records (this may not be good).
'''
def test_match(stderr):
assert match(Command('./manage.py migrate', stderr=stderr), None)
assert match(Command('python manage.py migrate', stderr=stderr), None)
assert not match(Command('./manage.py migrate'), None)
assert not match(Command('app migrate', stderr=stderr), None)
assert not match(Command('./manage.py test', stderr=stderr), None)
def test_get_new_command():
assert get_new_command(Command('./manage.py migrate auth'), None)\
== './manage.py migrate auth --delete-ghost-migrations'

View File

@@ -0,0 +1,43 @@
import pytest
from thefuck.rules.django_south_merge import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''Running migrations for app:
! Migration app:0003_auto... should not have been applied before app:0002_auto__add_field_query_due_date_ but was.
Traceback (most recent call last):
File "/home/nvbn/work/.../bin/python", line 42, in <module>
exec(compile(__file__f.read(), __file__, "exec"))
File "/home/nvbn/work/.../app/manage.py", line 34, in <module>
execute_from_command_line(sys.argv)
File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 443, in execute_from_command_line
utility.execute()
File "/home/nvbn/work/.../lib/django/core/management/__init__.py", line 382, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/nvbn/work/.../lib/django/core/management/base.py", line 196, in run_from_argv
self.execute(*args, **options.__dict__)
File "/home/nvbn/work/.../lib/django/core/management/base.py", line 232, in execute
output = self.handle(*args, **options)
File "/home/nvbn/work/.../app/lib/south/management/commands/migrate.py", line 108, in handle
ignore_ghosts = ignore_ghosts,
File "/home/nvbn/work/.../app/lib/south/migration/__init__.py", line 207, in migrate_app
raise exceptions.InconsistentMigrationHistory(problems)
south.exceptions.InconsistentMigrationHistory: Inconsistent migration history
The following options are available:
--merge: will just attempt the migration ignoring any potential dependency conflicts.
'''
def test_match(stderr):
assert match(Command('./manage.py migrate', stderr=stderr), None)
assert match(Command('python manage.py migrate', stderr=stderr), None)
assert not match(Command('./manage.py migrate'), None)
assert not match(Command('app migrate', stderr=stderr), None)
assert not match(Command('./manage.py test', stderr=stderr), None)
def test_get_new_command():
assert get_new_command(Command('./manage.py migrate auth'), None) \
== './manage.py migrate auth --merge'

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

34
tests/rules/test_man.py Normal file
View File

@@ -0,0 +1,34 @@
import pytest
from thefuck.rules.man import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command('man read'),
Command('man 2 read'),
Command('man 3 read'),
Command('man -s2 read'),
Command('man -s3 read'),
Command('man -s 2 read'),
Command('man -s 3 read')])
def test_match(command):
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', [
(Command('man read'), 'man 3 read'),
(Command('man 2 read'), 'man 3 read'),
(Command('man 3 read'), 'man 2 read'),
(Command('man -s2 read'), 'man -s3 read'),
(Command('man -s3 read'), 'man -s2 read'),
(Command('man -s 2 read'), 'man -s 3 read'),
(Command('man -s 3 read'), 'man -s 2 read')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -3,7 +3,7 @@ from thefuck.rules.no_command import match, get_new_command
def test_match(): def test_match():
with patch('thefuck.rules.no_command._get_all_bins', with patch('thefuck.rules.no_command._get_all_callables',
return_value=['vim', 'apt-get']): return_value=['vim', 'apt-get']):
assert match(Mock(stderr='vom: not found', script='vom file.py'), None) assert match(Mock(stderr='vom: not found', script='vom file.py'), None)
assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None) assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None)
@@ -11,7 +11,7 @@ def test_match():
def test_get_new_command(): def test_get_new_command():
with patch('thefuck.rules.no_command._get_all_bins', with patch('thefuck.rules.no_command._get_all_callables',
return_value=['vim', 'apt-get']): return_value=['vim', 'apt-get']):
assert get_new_command( assert get_new_command(
Mock(stderr='vom: not found', Mock(stderr='vom: not found',

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

@@ -11,6 +11,10 @@ def test_match(command):
assert match(command, None) assert match(command, None)
def test_not_match():
assert not match(Command(script='whois'), None)
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'), (Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'),
(Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'), (Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'),

View File

@@ -13,19 +13,33 @@ def isfile(mocker):
class TestGeneric(object): class TestGeneric(object):
def test_from_shell(self): @pytest.fixture
assert shells.Generic().from_shell('pwd') == 'pwd' def shell(self):
return shells.Generic()
def test_to_shell(self): def test_from_shell(self, shell):
assert shells.Generic().to_shell('pwd') == 'pwd' assert shell.from_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open): def test_to_shell(self, shell):
assert shells.Generic().put_to_history('ls') is None assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, shell):
assert shell.put_to_history('ls') is None
assert builtins_open.call_count == 0 assert builtins_open.call_count == 0
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {}
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestBash(object): class TestBash(object):
@pytest.fixture
def shell(self):
return shells.Bash()
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, mocker): def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen') mock = mocker.patch('thefuck.shells.Popen')
@@ -38,42 +52,81 @@ class TestBash(object):
@pytest.mark.parametrize('before, after', [ @pytest.mark.parametrize('before, after', [
('pwd', 'pwd'), ('pwd', 'pwd'),
('ll', 'ls -alF')]) ('ll', 'ls -alF')])
def test_from_shell(self, before, after): def test_from_shell(self, before, after, shell):
assert shells.Bash().from_shell(before) == after assert shell.from_shell(before) == after
def test_to_shell(self): def test_to_shell(self, shell):
assert shells.Bash().to_shell('pwd') == 'pwd' assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open): def test_put_to_history(self, builtins_open, shell):
shells.Bash().put_to_history('ls') shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('ls\n') write.assert_called_once_with('ls\n')
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestFish(object): class TestFish(object):
@pytest.fixture
def shell(self):
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"'),
def test_from_shell(self, before, after): ('find', 'find'),
assert shells.Fish().from_shell(before) == after ('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):
assert shell.from_shell(before) == after
def test_to_shell(self): def test_to_shell(self, shell):
assert shells.Fish().to_shell('pwd') == 'pwd' assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, mocker): def test_put_to_history(self, builtins_open, mocker, shell):
mocker.patch('thefuck.shells.time', mocker.patch('thefuck.shells.time',
return_value=1430707243.3517463) return_value=1430707243.3517463)
shells.Fish().put_to_history('ls') shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('- cmd: ls\n when: 1430707243\n') write.assert_called_once_with('- cmd: ls\n when: 1430707243\n')
def test_and_(self): def test_and_(self, shell):
assert shells.Fish().and_('foo', 'bar') == 'foo; and bar' assert shell.and_('foo', 'bar') == 'foo; and bar'
def test_get_aliases(self, shell):
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')
class TestZsh(object): class TestZsh(object):
@pytest.fixture
def shell(self):
return shells.Zsh()
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, mocker): def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen') mock = mocker.patch('thefuck.shells.Popen')
@@ -86,15 +139,23 @@ class TestZsh(object):
@pytest.mark.parametrize('before, after', [ @pytest.mark.parametrize('before, after', [
('pwd', 'pwd'), ('pwd', 'pwd'),
('ll', 'ls -alF')]) ('ll', 'ls -alF')])
def test_from_shell(self, before, after): def test_from_shell(self, before, after, shell):
assert shells.Zsh().from_shell(before) == after assert shell.from_shell(before) == after
def test_to_shell(self): def test_to_shell(self, shell):
assert shells.Zsh().to_shell('pwd') == 'pwd' assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, mocker): def test_put_to_history(self, builtins_open, mocker, shell):
mocker.patch('thefuck.shells.time', mocker.patch('thefuck.shells.time',
return_value=1430707243.3517463) return_value=1430707243.3517463)
shells.Zsh().put_to_history('ls') shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with(': 1430707243:0;ls\n') write.assert_called_once_with(': 1430707243:0;ls\n')
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}

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()

View File

@@ -0,0 +1,8 @@
def match(command, settings):
return 'manage.py' in command.script and \
'migrate' in command.script \
and 'or pass --delete-ghost-migrations' in command.stderr
def get_new_command(command, settings):
return u'{} --delete-ghost-migrations'.format(command.script)

View File

@@ -0,0 +1,8 @@
def match(command, settings):
return 'manage.py' in command.script and \
'migrate' in command.script \
and '--merge: will just attempt the migration' in command.stderr
def get_new_command(command, settings):
return u'{} --merge'.format(command.script)

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'

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

@@ -0,0 +1,13 @@
def match(command, settings):
return command.script.strip().startswith('man ')
def get_new_command(command, settings):
if '3' in command.script:
return command.script.replace("3", "2")
if '2' in command.script:
return command.script.replace("2", "3")
split_cmd = command.script.split()
split_cmd.insert(1, ' 3 ')
return "".join(split_cmd)

View File

@@ -2,6 +2,7 @@ from difflib import get_close_matches
import os import os
from pathlib import Path from pathlib import Path
from thefuck.utils import sudo_support from thefuck.utils import sudo_support
from thefuck.shells import get_aliases
def _safe(fn, fallback): def _safe(fn, fallback):
@@ -11,25 +12,25 @@ def _safe(fn, fallback):
return fallback return fallback
def _get_all_bins(): def _get_all_callables():
return [exe.name return [exe.name
for path in os.environ.get('PATH', '').split(':') for path in os.environ.get('PATH', '').split(':')
for exe in _safe(lambda: list(Path(path).iterdir()), []) for exe in _safe(lambda: list(Path(path).iterdir()), [])
if not _safe(exe.is_dir, True)] if not _safe(exe.is_dir, True)] + get_aliases()
@sudo_support @sudo_support
def match(command, settings): def match(command, settings):
return 'not found' in command.stderr and \ return 'not found' in command.stderr and \
bool(get_close_matches(command.script.split(' ')[0], bool(get_close_matches(command.script.split(' ')[0],
_get_all_bins())) _get_all_callables()))
@sudo_support @sudo_support
def get_new_command(command, settings): def get_new_command(command, settings):
old_command = command.script.split(' ')[0] old_command = command.script.split(' ')[0]
new_command = get_close_matches(old_command, new_command = get_close_matches(old_command,
_get_all_bins())[0] _get_all_callables())[0]
return ' '.join([new_command] + command.script.split(' ')[1:]) return ' '.join([new_command] + command.script.split(' ')[1:])

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

@@ -19,7 +19,7 @@ def match(command, settings):
- www.google.fr → subdomain: www, domain: 'google.fr'; - www.google.fr → subdomain: www, domain: 'google.fr';
- google.co.uk → subdomain: None, domain; 'google.co.uk'. - google.co.uk → subdomain: None, domain; 'google.co.uk'.
""" """
return 'whois' in command.script return 'whois' in command.script and len(command.script.split()) > 1
def get_new_command(command, settings): def get_new_command(command, settings):

View File

@@ -1,5 +1,5 @@
"""Module with shell specific actions, each shell class should """Module with shell specific actions, each shell class should
implement `from_shell`, `to_shell`, `app_alias` and `put_to_history` implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and `get_aliases`
methods. methods.
""" """
@@ -8,15 +8,16 @@ 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 {}
def _expand_aliases(self, command_script): def _expand_aliases(self, command_script):
aliases = self._get_aliases() aliases = self.get_aliases()
binary = command_script.split(' ')[0] binary = command_script.split(' ')[0]
if binary in aliases: if binary in aliases:
return command_script.replace(binary, aliases[binary], 1) return command_script.replace(binary, aliases[binary], 1)
@@ -61,8 +62,10 @@ class Bash(Generic):
value = value[1:-1] value = value[1:-1]
return name, value return name, value
def _get_aliases(self): @memoize
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) def get_aliases(self):
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
def _get_aliases(self): @memoize
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) def get_aliases(self):
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
def _get_aliases(self): @memoize
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) def get_aliases(self):
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')
@@ -153,16 +179,16 @@ shells = defaultdict(lambda: Generic(), {
'bash': Bash(), 'bash': Bash(),
'fish': Fish(), 'fish': Fish(),
'zsh': Zsh(), 'zsh': Zsh(),
'-csh': Tcsh(), 'csh': Tcsh(),
'tcsh': Tcsh()}) 'tcsh': Tcsh()})
def _get_shell(): def _get_shell():
try: try:
shell = Process(os.getpid()).parent().cmdline()[0] shell = Process(os.getpid()).parent().name()
except TypeError: except TypeError:
shell = Process(os.getpid()).parent.cmdline[0] shell = Process(os.getpid()).parent.name
return shells[os.path.basename(shell)] return shells[shell]
def from_shell(command): def from_shell(command):
@@ -183,3 +209,7 @@ def put_to_history(command):
def and_(*commands): def and_(*commands):
return _get_shell().and_(*commands) return _get_shell().and_(*commands)
def get_aliases():
return list(_get_shell().get_aliases().keys())

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