1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-12 21:06:01 +00:00

Compare commits

...

90 Commits
1.40 ... 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
nvbn
02d9613618 Bump to 1.42 2015-05-20 02:50:43 +03:00
nvbn
b63ce26853 Reorganize list of rules in readme 2015-05-20 02:50:08 +03:00
nvbn
ce6855fd97 Add git_pull rule 2015-05-20 02:40:36 +03:00
nvbn
051f5fcb89 Bump to 1.41 2015-05-19 15:48:17 +03:00
nvbn
6590da623f #205 Fix import in cd_correction 2015-05-19 15:46:23 +03:00
Vladimir Iakovlev
dc53f58b2a Merge pull request #206 from scorphus/fish-shell
Add support to Fish shell
2015-05-19 12:53:02 +03:00
Vladimir Iakovlev
961d4d5293 Merge pull request #205 from mmussomele/master
Adding cd correction rule to the default rules
2015-05-19 12:52:16 +03:00
Vladimir Iakovlev
1ffc9624ed Merge pull request #202 from mcarton/pacman
Cleanup `pacman` rule
2015-05-19 12:51:26 +03:00
mcarton
afcee5844b Fix pacman tests on Arch Linux 2015-05-18 09:41:49 +02:00
mcarton
881967f4c5 Merge branch 'master' of github.com:nvbn/thefuck into pacman 2015-05-17 20:24:13 +02:00
mmussomele
3c673e0972 fixed extra check 2015-05-17 09:52:42 -07:00
mmussomele
8fdcff776a reimplemented using native string matching 2015-05-17 09:03:19 -07:00
Pablo Santiago Blum de Aguiar
1b5c935f30 feat(shells): add specific actions for the Fish shell
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-17 12:57:45 -03:00
Pablo Santiago Blum de Aguiar
8d256390a1 refact(shells): use os.path.basename to get the name of the shell
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-17 12:57:45 -03:00
Vladimir Iakovlev
51800afca8 Merge pull request #201 from mcarton/whois
Add test and complete README for the `whois` rule
2015-05-17 16:26:23 +03:00
Vladimir Iakovlev
07831666db Merge pull request #200 from scorphus/improve-shells
Improve shells
2015-05-17 16:25:45 +03:00
mmussomele
252859e63a fixed accidentally correcting to some directories with short name length 2015-05-16 23:53:08 -07:00
mmussomele
a54c97f624 added newline to end of cd_correction.py 2015-05-16 21:47:15 -07:00
mmussomele
9ef346468c added cd_correction.py 2015-05-16 21:42:21 -07:00
mcarton
f04c4396eb Fix Python 2.7 support 2015-05-16 23:17:53 +02:00
Pablo Santiago Blum de Aguiar
9ade21bf0a refact(travis): enable verbose mode for tests on travis
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-16 11:53:16 -03:00
Pablo Santiago Blum de Aguiar
179839c32f test(rules): test other rules involving shells.and_()
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-16 11:53:01 -03:00
Pablo Santiago Blum de Aguiar
3d0d4be4a9 refact(shells): add and_ method to assemble expressions involving AND
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-16 11:52:50 -03:00
Pablo Santiago Blum de Aguiar
d854320acc refact(shells): add specific app_alias methods for Bash and Zsh
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-16 11:40:32 -03:00
mcarton
bb4b42d2f1 Add the whois rule in README.md 2015-05-16 15:37:00 +02:00
mcarton
6539c853b4 Add tests for the whois rule 2015-05-16 15:36:27 +02:00
mcarton
5f2b2433b1 Cleanup pacman rule 2015-05-16 15:25:32 +02:00
Vladimir Iakovlev
d41b1d48d2 Merge pull request #199 from igorsantos07/master
Adding rule for forgotten '-r' when grepping folders
2015-05-16 11:51:07 +02:00
Vladimir Iakovlev
bbdac1884a Merge pull request #198 from scorphus/pacman-py2
fix(pacman): make the entire rule py2-compatible
2015-05-16 11:50:36 +02:00
Igor Santos
d5bd57fb49 Adding rule for forgotten '-r' when grepping folders 2015-05-15 19:09:14 -03:00
Pablo Santiago Blum de Aguiar
fc8f1b1136 fix(pacman): make the entire rule py2-compatible
One reference to subprocess.DEVNULL remained.

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-15 15:53:37 -03:00
Vladimir Iakovlev
d7c67ad09d Merge pull request #197 from mcarton/whois
Add a `whois` rule
2015-05-15 19:13:22 +02:00
Vladimir Iakovlev
73939836d4 Merge pull request #196 from mcarton/no-such-file
Add a `no_such_file` rule
2015-05-15 19:12:43 +02:00
mcarton
744f17d905 Add a whois rule 2015-05-15 18:41:55 +02:00
mcarton
08a2065119 Add missing cases for the no_such_file rule 2015-05-15 18:08:43 +02:00
mcarton
5504aa44a1 Add tests for the no_such_file rule 2015-05-15 18:03:33 +02:00
mcarton
3c4f9d50a9 Add a no_such_file rule 2015-05-15 18:03:17 +02:00
Vladimir Iakovlev
371a4b0ad3 Merge pull request #192 from scorphus/fix-brew-install-py3
Fix brew_install rule on Python 3
2015-05-13 22:54:28 +02:00
Pablo Santiago Blum de Aguiar
9cf41f8e43 fix(brew_install): make subprocess.check_output return str
This fix makes the `brew_install` rule work on Python 3.

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-13 15:53:30 -03:00
Pablo Santiago Blum de Aguiar
d2e511fa2c refact(brew_install): remove an unused import
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-13 15:53:30 -03:00
Vladimir Iakovlev
a1437db40a Merge pull request #190 from HughMacdonald/master
Add TCSH support
2015-05-13 17:04:17 +02:00
Hugh Macdonald
239f91b670 Update readme 2015-05-13 15:56:50 +01:00
Hugh Macdonald
7b29b54ac7 Add initial tcsh support. Still require better history support 2015-05-13 15:55:33 +01:00
Vladimir Iakovlev
a83d75991b Merge pull request #188 from mcarton/git-stash
Improve the git_stash rule
2015-05-13 11:44:16 +02:00
mcarton
14d14c5ac6 Update README 2015-05-13 09:49:45 +02:00
mcarton
65c624ad52 Improve the git_stash rule 2015-05-13 09:47:31 +02:00
Vladimir Iakovlev
a77db59da5 Merge pull request #187 from mcarton/git-stash
Add a git_stash rule
2015-05-12 20:40:59 +02:00
mcarton
8ac4dafe6d Add a git_stash rule 2015-05-12 19:44:52 +02:00
nvbn
779e29906e Merge branch 'SanketDG-alias_fix' 2015-05-12 14:22:31 +02:00
nvbn
e8de4ee7e8 #185 Fix python 3 2015-05-12 14:22:20 +02:00
nvbn
47a1faa881 Merge branch 'alias_fix' of git://github.com/SanketDG/thefuck into SanketDG-alias_fix 2015-05-12 14:21:35 +02:00
Vladimir Iakovlev
ab97b94faf Merge pull request #183 from scorphus/fix-type-error-py34
fix(brew_unknown_command): make subprocess.check_output return str
2015-05-12 14:20:06 +02:00
SanketDG
7489040f8f fix thefuck-alias 2015-05-12 14:29:00 +05:30
Pablo Santiago Blum de Aguiar
484a53e314 fix(brew_unknown_command): make subprocess.check_output return str
Fix `TypeError: can't concat bytes to str` error on Python 3.4.

Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-05-11 23:58:53 -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
47 changed files with 1040 additions and 76 deletions

View File

@@ -6,4 +6,4 @@ python:
install: install:
- python setup.py develop - python setup.py develop
- pip install -r requirements.txt - pip install -r requirements.txt
script: py.test script: py.test -v

View File

@@ -118,6 +118,11 @@ Or in your `.zshrc`:
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R' alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
``` ```
If you are using `tcsh`:
```tcsh
alias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'
```
Alternatively, you can redirect the output of `thefuck-alias`: Alternatively, you can redirect the output of `thefuck-alias`:
```bash ```bash
@@ -140,37 +145,52 @@ sudo pip install thefuck --upgrade
The Fuck tries to match a rule for the previous command, creates a new command The Fuck tries to match a rule for the previous command, creates a new command
using the matched rule and runs it. Rules enabled by default are as follows: using the matched rule and runs it. Rules enabled by default are as follows:
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`; * `cd_correction` &ndash; spellchecks and correct failed cd commands;
* `cpp11` &ndash; add missing `-std=c++11` to `g++` or `clang++`;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
* `cd_mkdir` &ndash; creates directories before cd'ing into them; * `cd_mkdir` &ndash; creates directories before cd'ing into them;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
* `composer_not_command` &ndash; fixes composer command name;
* `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++`;
* `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`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`; * `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_stash` &ndash; stashes you local modifications before rebasing or switching branch;
* `grep_recursive` &ndash; adds `-r` when you trying to grep directory;
* `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;
* `man` &ndash; change manual section;
* `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`;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`; * `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed; * `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;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory; * `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning; * `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions; * `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` &ndash; switches command from your local layout to en; * `switch_layout` &ndash; switches command from your local layout to en;
* `whois` &ndash; fixes `whois` command.
Enabled by default only on specific platforms:
* `apt_get` &ndash; installs app from apt if it not installed; * `apt_get` &ndash; installs app from apt if it not installed;
* `brew_install` &ndash; fixes formula name for `brew install`; * `brew_install` &ndash; fixes formula name for `brew install`;
* `composer_not_command` &ndash; fixes composer command name. * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed.
Bundled, but not enabled by default: Bundled, but not enabled by default:
* `ls_lah` &ndash; adds -lah to ls;
* `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command. * `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.
## Creating your own rules ## Creating your own rules

View File

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

0
tests/rules/__init__.py Normal file
View File

6
tests/rules/conftest.py Normal file
View File

@@ -0,0 +1,6 @@
import pytest
@pytest.fixture(autouse=True)
def generic_shell(monkeypatch):
monkeypatch.setattr('thefuck.shells.and_', lambda *x: ' && '.join(x))

View File

@@ -0,0 +1,59 @@
import pytest
from mock import Mock, patch
from thefuck.rules import apt_get
from thefuck.rules.apt_get import match, get_new_command
from tests.utils import Command
# python-commandnotfound is available in ubuntu 14.04+
@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True),
reason='Skip if python-commandnotfound is not available')
@pytest.mark.parametrize('command', [
Command(script='vim', stderr='vim: command not found')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, return_value', [
(Command(script='vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')])])
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
@patch.multiple(apt_get, create=True, apt_get='apt_get')
def test_match_mocked(cmdnf_mock, command, return_value):
get_packages = Mock(return_value=return_value)
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
assert match(command, None)
assert cmdnf_mock.CommandNotFound.called
assert get_packages.called
@pytest.mark.parametrize('command', [
Command(script='vim', stderr=''), Command()])
def test_not_match(command):
assert not match(command, None)
# python-commandnotfound is available in ubuntu 14.04+
@pytest.mark.skipif(not getattr(apt_get, 'enabled_by_default', True),
reason='Skip if python-commandnotfound is not available')
@pytest.mark.parametrize('command, new_command', [
(Command('vim'), 'sudo apt-get install vim && vim'),
(Command('convert'), 'sudo apt-get install imagemagick && convert')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command
@pytest.mark.parametrize('command, new_command, return_value', [
(Command('vim'), 'sudo apt-get install vim && vim',
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command('convert'), 'sudo apt-get install imagemagick && convert',
[('imagemagick', 'main'),
('graphicsmagick-imagemagick-compat', 'universe')])])
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
@patch.multiple(apt_get, create=True, apt_get='apt_get')
def test_get_new_command_mocked(cmdnf_mock, command, new_command, return_value):
get_packages = Mock(return_value=return_value)
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
assert get_new_command(command, None) == new_command
assert cmdnf_mock.CommandNotFound.called
assert get_packages.called

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'

View File

@@ -0,0 +1,39 @@
import pytest
from thefuck.rules.git_add import match, get_new_command
from tests.utils import Command
@pytest.fixture
def did_not_match(target, did_you_forget=True):
error = ("error: pathspec '{}' did not match any "
"file(s) known to git.".format(target))
if did_you_forget:
error = ("{}\nDid you forget to 'git add'?'".format(error))
return error
@pytest.mark.parametrize('command', [
Command(script='git submodule update unknown',
stderr=did_not_match('unknown')),
Command(script='git commit unknown',
stderr=did_not_match('unknown'))]) # Older versions of Git
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git submodule update known', stderr=('')),
Command(script='git commit known', stderr=('')),
Command(script='git commit unknown', # Newer versions of Git
stderr=did_not_match('unknown', False))])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('git submodule update unknown', stderr=did_not_match('unknown')),
'git add -- unknown && git submodule update unknown'),
(Command('git commit unknown', stderr=did_not_match('unknown')), # Old Git
'git add -- unknown && git commit unknown')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,37 @@
import pytest
from thefuck.rules.git_checkout import match, get_new_command
from tests.utils import Command
@pytest.fixture
def did_not_match(target, did_you_forget=False):
error = ("error: pathspec '{}' did not match any "
"file(s) known to git.".format(target))
if did_you_forget:
error = ("{}\nDid you forget to 'git add'?'".format(error))
return error
@pytest.mark.parametrize('command', [
Command(script='git checkout unknown', stderr=did_not_match('unknown')),
Command(script='git commit unknown', stderr=did_not_match('unknown'))])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git submodule update unknown',
stderr=did_not_match('unknown', True)),
Command(script='git checkout known', stderr=('')),
Command(script='git commit known', stderr=(''))])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='git checkout unknown', stderr=did_not_match('unknown')),
'git branch unknown && git checkout unknown'),
(Command('git commit unknown', stderr=did_not_match('unknown')),
'git branch unknown && git commit unknown')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,29 @@
import pytest
from thefuck.rules.git_pull import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream-to=<remote>/<branch> master
'''
def test_match(stderr):
assert match(Command('git pull', stderr=stderr), None)
assert not match(Command('git pull'), None)
assert not match(Command('ls', stderr=stderr), None)
def test_get_new_command(stderr):
assert get_new_command(Command('git pull', stderr=stderr), None) \
== "git branch --set-upstream-to=origin/master master && git pull"

View File

@@ -0,0 +1,39 @@
import pytest
from thefuck.rules.git_stash import match, get_new_command
from tests.utils import Command
@pytest.fixture
def cherry_pick_error():
return ('error: Your local changes would be overwritten by cherry-pick.\n'
'hint: Commit your changes or stash them to proceed.\n'
'fatal: cherry-pick failed')
@pytest.fixture
def rebase_error():
return ('Cannot rebase: Your index contains uncommitted changes.\n'
'Please commit or stash them.')
@pytest.mark.parametrize('command', [
Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error()),
Command(script='git rebase -i HEAD~7', stderr=rebase_error())])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git cherry-pick a1b2c3d', stderr=('')),
Command(script='git rebase -i HEAD~7', stderr=(''))])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='git cherry-pick a1b2c3d', stderr=cherry_pick_error),
'git stash && git cherry-pick a1b2c3d'),
(Command('git rebase -i HEAD~7', stderr=rebase_error),
'git stash && git rebase -i HEAD~7')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,12 @@
from thefuck.rules.grep_recursive import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('grep blah .', stderr='grep: .: Is a directory'), None)
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(
Command('grep blah .'), None) == 'grep -r blah .'

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',

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.no_such_file import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"),
Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"),
])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='mv foo bar/foo', stderr="mv: cannot move 'foo' to 'bar/foo': No such file or directory"), 'mkdir -p bar && mv foo bar/foo'),
(Command(script='mv foo bar/', stderr="mv: cannot move 'foo' to 'bar/': No such file or directory"), 'mkdir -p bar && mv foo bar/'),
])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

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

@@ -0,0 +1,66 @@
import pytest
from mock import patch
from thefuck.rules import pacman
from thefuck.rules.pacman import match, get_new_command
from tests.utils import Command
pacman_cmd = getattr(pacman, 'pacman', 'pacman')
PKGFILE_OUTPUT_CONVERT = '''
extra/imagemagick 6.9.1.0-1\t/usr/bin/convert
'''
PKGFILE_OUTPUT_VIM = '''
extra/gvim 7.4.712-1 \t/usr/bin/vim
extra/gvim-python3 7.4.712-1\t/usr/bin/vim
extra/vim 7.4.712-1 \t/usr/bin/vim
extra/vim-minimal 7.4.712-1 \t/usr/bin/vim
extra/vim-python3 7.4.712-1 \t/usr/bin/vim
'''
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
reason='Skip if pacman is not available')
@pytest.mark.parametrize('command', [
Command(script='vim', stderr='vim: command not found')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, return_value', [
(Command(script='vim', stderr='vim: command not found'), PKGFILE_OUTPUT_VIM)])
@patch('thefuck.rules.pacman.subprocess')
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
def test_match_mocked(subp_mock, command, return_value):
subp_mock.check_output.return_value = return_value
assert match(command, None)
assert subp_mock.check_output.called
@pytest.mark.parametrize('command', [
Command(script='vim', stderr=''), Command()])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
reason='Skip if pacman is not available')
@pytest.mark.parametrize('command, new_command', [
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd)),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd))])
def test_get_new_command(command, new_command, mocker):
assert get_new_command(command, None) == new_command
@pytest.mark.parametrize('command, new_command, return_value', [
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd),
PKGFILE_OUTPUT_VIM),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd),
PKGFILE_OUTPUT_CONVERT)])
@patch('thefuck.rules.pacman.subprocess')
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
def test_get_new_command_mocked(subp_mock, command, new_command, return_value):
subp_mock.check_output.return_value = return_value
assert get_new_command(command, None) == new_command
assert subp_mock.check_output.called

23
tests/rules/test_whois.py Normal file
View File

@@ -0,0 +1,23 @@
import pytest
from thefuck.rules.whois import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='whois https://en.wikipedia.org/wiki/Main_Page'),
Command(script='whois https://en.wikipedia.org/'),
Command(script='whois en.wikipedia.org')])
def test_match(command):
assert match(command, None)
def test_not_match():
assert not match(Command(script='whois'), None)
@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/'), 'whois en.wikipedia.org'),
(Command('whois en.wikipedia.org'), 'whois wikipedia.org')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

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,20 +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')
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', [
('pwd', 'pwd'),
('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):
assert shell.from_shell(before) == after
def test_to_shell(self, shell):
assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, mocker, shell):
mocker.patch('thefuck.shells.time',
return_value=1430707243.3517463)
shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('- cmd: ls\n when: 1430707243\n')
def test_and_(self, shell):
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')
@@ -64,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

@@ -1,3 +1,5 @@
from thefuck import shells
try: try:
import CommandNotFound import CommandNotFound
except ImportError: except ImportError:
@@ -20,4 +22,5 @@ def get_new_command(command, settings):
c = CommandNotFound.CommandNotFound() c = CommandNotFound.CommandNotFound()
pkgs = c.getPackages(command.script.split(" ")[0]) pkgs = c.getPackages(command.script.split(" ")[0])
name, _ = pkgs[0] name, _ = pkgs[0]
return "sudo apt-get install {} && {}".format(name, command.script) formatme = shells.and_('sudo apt-get install {}', '{}')
return formatme.format(name, command.script)

View File

@@ -3,12 +3,11 @@ import os
import re import re
from subprocess import check_output from subprocess import check_output
import thefuck.logs
# Formulars are base on each local system's status # Formulars are base on each local system's status
brew_formulas = [] brew_formulas = []
try: try:
brew_path_prefix = check_output(['brew', '--prefix']).strip() brew_path_prefix = check_output(['brew', '--prefix'],
universal_newlines=True).strip()
brew_formula_path = brew_path_prefix + '/Library/Formula' brew_formula_path = brew_path_prefix + '/Library/Formula'
for file_name in os.listdir(brew_formula_path): for file_name in os.listdir(brew_formula_path):

View File

@@ -12,7 +12,8 @@ TAP_CMD_PATH = '/%s/%s/cmd'
def _get_brew_path_prefix(): def _get_brew_path_prefix():
"""To get brew path""" """To get brew path"""
try: try:
return subprocess.check_output(['brew', '--prefix']).strip() return subprocess.check_output(['brew', '--prefix'],
universal_newlines=True).strip()
except: except:
return None return None

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
__author__ = "mmussomele"
"""Attempts to spellcheck and correct failed cd commands"""
import os
from difflib import get_close_matches
from thefuck.utils import sudo_support
from thefuck.rules import cd_mkdir
MAX_ALLOWED_DIFF = 0.6
def _get_sub_dirs(parent):
"""Returns a list of the child directories of the given parent directory"""
return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))]
@sudo_support
def match(command, settings):
"""Match function copied from cd_mkdir.py"""
return (command.script.startswith('cd ')
and ('no such file or directory' in command.stderr.lower()
or 'cd: can\'t cd to' in command.stderr.lower()))
@sudo_support
def get_new_command(command, settings):
"""
Attempt to rebuild the path string by spellchecking the directories.
If it fails (i.e. no directories are a close enough match), then it
defaults to the rules of cd_mkdir.
Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6
"""
dest = command.script.split()[1].split(os.sep)
if dest[-1] == '':
dest = dest[:-1]
cwd = os.getcwd()
for directory in dest:
if directory == ".":
continue
elif directory == "..":
cwd = os.path.split(cwd)[0]
continue
best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF)
if best_matches:
cwd = os.path.join(cwd, best_matches[0])
else:
return cd_mkdir.get_new_command(command, settings)
return "cd {0}".format(cwd)
enabled_by_default = True

View File

@@ -1,4 +1,5 @@
import re import re
from thefuck import shells
from thefuck.utils import sudo_support from thefuck.utils import sudo_support
@@ -11,4 +12,5 @@ def match(command, settings):
@sudo_support @sudo_support
def get_new_command(command, settings): def get_new_command(command, settings):
return re.sub(r'^cd (.*)', 'mkdir -p \\1 && cd \\1', command.script) repl = shells.and_('mkdir -p \\1', 'cd \\1')
return re.sub(r'^cd (.*)', repl, command.script)

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)

View File

@@ -1,4 +1,5 @@
import re import re
from thefuck import shells
def match(command, settings): def match(command, settings):
@@ -12,4 +13,5 @@ def get_new_command(command, settings):
r"error: pathspec '([^']*)' " r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0] "did not match any file\(s\) known to git.", command.stderr)[0]
return 'git add -- {} && {}'.format(missing_file, command.script) formatme = shells.and_('git add -- {}', '{}')
return formatme.format(missing_file, command.script)

View File

@@ -1,4 +1,5 @@
import re import re
from thefuck import shells
def match(command, settings): def match(command, settings):
@@ -12,4 +13,5 @@ def get_new_command(command, settings):
r"error: pathspec '([^']*)' " r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0] "did not match any file\(s\) known to git.", command.stderr)[0]
return 'git branch {} && {}'.format(missing_file, command.script) formatme = shells.and_('git branch {}', '{}')
return formatme.format(missing_file, command.script)

12
thefuck/rules/git_pull.py Normal file
View File

@@ -0,0 +1,12 @@
def match(command, settings):
return ('git' in command.script
and 'pull' in command.script
and 'set-upstream' in command.stderr)
def get_new_command(command, settings):
line = command.stderr.split('\n')[-3].strip()
branch = line.split(' ')[-1]
set_upstream = line.replace('<remote>', 'origin')\
.replace('<branch>', branch)
return u'{} && {}'.format(set_upstream, command.script)

View File

@@ -0,0 +1,12 @@
from thefuck import shells
def match(command, settings):
# catches "Please commit or stash them" and "Please, commit your changes or
# stash them before you can switch branches."
return 'git' in command.script and 'or stash them' in command.stderr
def get_new_command(command, settings):
formatme = shells.and_('git stash', '{}')
return formatme.format(command.script)

View File

@@ -0,0 +1,7 @@
def match(command, settings):
return (command.script.startswith('grep')
and 'is a directory' in command.stderr.lower())
def get_new_command(command, settings):
return 'grep -r {}'.format(command.script[5:])

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,6 +1,3 @@
enabled_by_default = False
def match(command, settings): def match(command, settings):
return 'ls' in command.script and not ('ls -' in command.script) return 'ls' in command.script and not ('ls -' in command.script)

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:])

View File

@@ -0,0 +1,30 @@
import re
from thefuck import shells
patterns = (
r"mv: cannot move '[^']*' to '([^']*)': No such file or directory",
r"mv: cannot move '[^']*' to '([^']*)': Not a directory",
r"cp: cannot create regular file '([^']*)': No such file or directory",
r"cp: cannot create regular file '([^']*)': Not a directory",
)
def match(command, settings):
for pattern in patterns:
if re.search(pattern, command.stderr):
return True
return False
def get_new_command(command, settings):
for pattern in patterns:
file = re.findall(pattern, command.stderr)
if file:
file = file[0]
dir = file[0:file.rfind('/')]
formatme = shells.and_('mkdir -p {}', '{}')
return formatme.format(dir, command.script)

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

@@ -1,23 +1,13 @@
import subprocess import subprocess
from thefuck.utils import DEVNULL from thefuck.utils import DEVNULL, which
from thefuck import shells
def __command_available(command):
try:
subprocess.check_output([command], stderr=DEVNULL)
return True
except subprocess.CalledProcessError:
# command exists but is not happy to be called without any argument
return True
except OSError:
return False
def __get_pkgfile(command): def __get_pkgfile(command):
try: try:
return subprocess.check_output( return subprocess.check_output(
['pkgfile', '-b', '-v', command.script.split(" ")[0]], ['pkgfile', '-b', '-v', command.script.split(" ")[0]],
universal_newlines=True, stderr=subprocess.DEVNULL universal_newlines=True, stderr=DEVNULL
).split() ).split()
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return None return None
@@ -30,14 +20,15 @@ def match(command, settings):
def get_new_command(command, settings): def get_new_command(command, settings):
package = __get_pkgfile(command)[0] package = __get_pkgfile(command)[0]
return '{} -S {} && {}'.format(pacman, package, command.script) formatme = shells.and_('{} -S {}', '{}')
return formatme.format(pacman, package, command.script)
if not __command_available('pkgfile'): if not which('pkgfile'):
enabled_by_default = False enabled_by_default = False
elif __command_available('yaourt'): elif which('yaourt'):
pacman = 'yaourt' pacman = 'yaourt'
elif __command_available('pacman'): elif which('pacman'):
pacman = 'sudo pacman' pacman = 'sudo pacman'
else: else:
enabled_by_default = False enabled_by_default = False

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

31
thefuck/rules/whois.py Normal file
View File

@@ -0,0 +1,31 @@
# -*- encoding: utf-8 -*-
from six.moves.urllib.parse import urlparse
def match(command, settings):
"""
What the `whois` command returns depends on the 'Whois server' it contacted
and is not consistent through different servers. But there can be only two
types of errors I can think of with `whois`:
- `whois https://en.wikipedia.org/` → `whois en.wikipedia.org`;
- `whois en.wikipedia.org` → `whois wikipedia.org`.
So we match any `whois` command and then:
- if there is a slash: keep only the FQDN;
- if there is no slash but there is a point: removes the left-most
subdomain.
We cannot either remove all subdomains because we cannot know which part is
the subdomains and which is the domain, consider:
- www.google.fr → subdomain: www, domain: 'google.fr';
- google.co.uk → subdomain: None, domain; 'google.co.uk'.
"""
return 'whois' in command.script and len(command.script.split()) > 1
def get_new_command(command, settings):
url = command.script.split()[1]
if '/' in command.script:
return 'whois ' + urlparse(url).netloc
elif '.' in command.script:
return 'whois ' + '.'.join(urlparse(url).path.split('.')[1:])

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)
@@ -47,16 +48,24 @@ class Generic(object):
with open(history_file_name, 'a') as history: with open(history_file_name, 'a') as history:
history.write(self._get_history_line(command_script)) history.write(self._get_history_line(command_script))
def and_(self, *commands):
return ' && '.join(commands)
class Bash(Generic): class Bash(Generic):
def app_alias(self):
return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n"
def _parse_alias(self, alias): def _parse_alias(self, alias):
name, value = alias.replace('alias ', '', 1).split('=', 1) name, value = alias.replace('alias ', '', 1).split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
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')
@@ -70,15 +79,64 @@ class Bash(Generic):
return u'{}\n'.format(command_script) return u'{}\n'.format(command_script)
class Fish(Generic):
def app_alias(self):
return ("function fuck -d 'Correct your previous console command'\n"
" set -l exit_code $status\n"
" set -l eval_script"
" (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
" set -l fucked_up_commandd $history[1]\n"
" thefuck $fucked_up_commandd > $eval_script\n"
" . $eval_script\n"
" rm $eval_script\n"
" if test $exit_code -ne 0\n"
" history --delete $fucked_up_commandd\n"
" end\n"
"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):
return os.path.expanduser('~/.config/fish/fish_history')
def _get_history_line(self, command_script):
return u'- cmd: {}\n when: {}\n'.format(command_script, int(time()))
def and_(self, *commands):
return '; and '.join(commands)
class Zsh(Generic): class Zsh(Generic):
def app_alias(self):
return "\nalias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'\n"
def _parse_alias(self, alias): def _parse_alias(self, alias):
name, value = alias.split('=', 1) name, value = alias.split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
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')
@@ -92,16 +150,44 @@ class Zsh(Generic):
return u': {}:0;{}\n'.format(int(time()), command_script) return u': {}:0;{}\n'.format(int(time()), command_script)
class Tcsh(Generic):
def app_alias(self):
return "\nalias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'\n"
def _parse_alias(self, alias):
name, value = alias.split("\t", 1)
return name, value
@memoize
def get_aliases(self):
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias and '\t' in alias)
def _get_history_file_name(self):
return os.environ.get("HISTFILE",
os.path.expanduser('~/.history'))
def _get_history_line(self, command_script):
return u'#+{}\n{}\n'.format(int(time()), command_script)
shells = defaultdict(lambda: Generic(), { shells = defaultdict(lambda: Generic(), {
'bash': Bash(), 'bash': Bash(),
'zsh': Zsh()}) 'fish': Fish(),
'zsh': Zsh(),
'csh': 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[shell] return shells[shell]
@@ -114,8 +200,16 @@ def to_shell(command):
def app_alias(): def app_alias():
return _get_shell().app_alias() print(_get_shell().app_alias())
def put_to_history(command): def put_to_history(command):
return _get_shell().put_to_history(command) return _get_shell().put_to_history(command)
def 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