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

Compare commits

...

275 Commits
1.37 ... 2.3

Author SHA1 Message Date
nvbn
6c534c52bc Bump to 2.3 2015-07-22 04:45:04 +03:00
nvbn
b4392ba706 #N/A Add heroku_not_command rule 2015-07-22 04:44:37 +03:00
Vladimir Iakovlev
46f918718f Merge pull request #307 from evverx/lc_all
Force LC_ALL to C
2015-07-21 17:11:16 +03:00
Vladimir Iakovlev
d71ce76ae4 Merge pull request #306 from mcarton/hub
Support GitHub's hub command
2015-07-21 16:43:24 +03:00
nvbn
355505a0a8 #N/A Make git_checkout test less dependent on get_closest 2015-07-21 16:40:45 +03:00
Evgeny Vereshchagin
3d425ce831 Force LC_ALL to C
See: http://unix.stackexchange.com/a/87763/120177
2015-07-21 13:39:34 +00:00
mcarton
98a9fb3d7d Remove now redundant checks in git_* rules 2015-07-21 15:35:39 +02:00
nvbn
c8d748e095 Bump to 2.2 2015-07-21 16:31:17 +03:00
nvbn
e0af35819d Merge branch 'master' of github.com:nvbn/thefuck 2015-07-21 16:19:11 +03:00
nvbn
9e4c250e4e #301 Fix bash support on non-eng systems 2015-07-21 16:19:01 +03:00
Vladimir Iakovlev
8c395377f8 Merge pull request #299 from evverx/dnf-history
Add `dnf history` error for the sudo rule
2015-07-21 16:15:08 +03:00
Vladimir Iakovlev
f165523247 Merge pull request #304 from mcarton/fix-git_diff_staged
Fix the `git_diff_staged` rule
2015-07-21 16:12:19 +03:00
mcarton
903abff77e Support hub as well as git in @git_support 2015-07-21 15:06:04 +02:00
Evgeny Vereshchagin
6d39b78824 Add dnf history error for the sudo rule
$ dnf history
You don't have access to the history DB.
2015-07-21 12:56:25 +00:00
mcarton
1285303363 Fix the git_diff_staged rule
The problem was:
```
% git add foo
% git diff foo
% fuck
git diff foo --staged [enter/ctrl+c]
fatal: bad flag '--staged' used after filename
```
2015-07-21 14:06:37 +02:00
nvbn
66e2ec7e3f Merge branch 'mcarton-fix-readme' 2015-07-20 22:49:31 +03:00
nvbn
92cca7b641 #296 Fix [enter/ctrl+c] case in the readme 2015-07-20 22:49:21 +03:00
mcarton
e572cab1f3 Have the README look better 2015-07-20 21:12:39 +02:00
mcarton
33b1536c28 Move misplaced rule in README 2015-07-20 21:07:56 +02:00
mcarton
d4fada8e4c Reflect the new default for require_confirmation 2015-07-20 21:06:59 +02:00
mcarton
afc089bc3c Be more consistent in README 2015-07-20 20:49:21 +02:00
nvbn
300c8f528a #N/A Mention tcsh in readme 2015-07-20 21:27:19 +03:00
nvbn
7b011a504d #N/A Fix tests in travis 2015-07-20 21:24:00 +03:00
nvbn
164103693b Bump to 2.1 2015-07-20 21:16:53 +03:00
nvbn
a21c99200e #294 Mention common shells configs in readme 2015-07-20 21:15:34 +03:00
nvbn
1b961c4b87 #294 Move entry point for alias to main 2015-07-20 21:14:43 +03:00
nvbn
a849b65352 Merge branch 'easy-install' of https://github.com/mcarton/thefuck into mcarton-easy-install 2015-07-20 21:06:21 +03:00
nvbn
dee018e792 #N/A Move get_all_executables (formerly get_all_callables) to utils 2015-07-20 21:04:49 +03:00
nvbn
c67560864a #295 Add git_push_pull rule 2015-07-20 20:51:18 +03:00
Vladimir Iakovlev
b636e9bec7 Merge pull request #295 from mcarton/new-git-rules
New git rules
2015-07-20 20:42:02 +03:00
nvbn
36450b740f #270 Add default priority in the readme 2015-07-20 20:01:45 +03:00
mcarton
0f67aad93b Update README 2015-07-20 18:58:16 +02:00
mcarton
bb7579ead5 Add the git_pull_clone rule 2015-07-20 18:58:16 +02:00
mcarton
569709388d Add a git_push_force rule 2015-07-20 18:58:11 +02:00
nvbn
baf7796295 #129 Ignore thefuck alias in switch_lang rule 2015-07-20 19:40:45 +03:00
nvbn
7b32f1df04 #N/A Fix debug output with unicode commands 2015-07-20 19:35:32 +03:00
nvbn
cd084c8ba6 #N/A Fix history rule with blank history 2015-07-20 19:30:41 +03:00
nvbn
4f5659caad #87 Add ability to fix branch names in git_checkout rule 2015-07-20 19:25:29 +03:00
mcarton
370f258b89 Change installation method in README 2015-07-20 13:40:07 +02:00
mcarton
9a069daada Make thefuck-alias generated alias a parameter 2015-07-20 13:35:22 +02:00
nvbn
ee87d1c547 #N/A Ignore history lines before fuck call in history rule 2015-07-20 01:53:32 +03:00
Vladimir Iakovlev
7e03b55729 Merge pull request #293 from mcarton/git-aliases
#292 #290 Use @git_support in all git rules
2015-07-20 01:25:25 +03:00
mcarton
db76462802 #292 #290 Use @git_support in all git rules 2015-07-20 00:08:01 +02:00
Vladimir Iakovlev
dbf20ebc73 Fix typo 2015-07-19 22:41:10 +03:00
Vladimir Iakovlev
b8a74b1425 Remove barely working coveralls badge 2015-07-19 22:40:25 +03:00
nvbn
4fb990742d Bump to 2.0 2015-07-19 22:33:56 +03:00
nvbn
cf3dca6f51 #284 Add coveralls support 2015-07-19 21:57:19 +03:00
nvbn
5187bada1b #N/A Update readme 2015-07-19 21:53:08 +03:00
nvbn
0238569b71 #N/A Require confirmation by default 2015-07-19 21:52:46 +03:00
nvbn
463b4fef2f Merge branch 'mcarton-git-aliases' 2015-07-19 21:29:39 +03:00
nvbn
f90bac10ed #290: Fix typo 2015-07-19 21:29:28 +03:00
nvbn
90014b2b05 Merge branch 'git-aliases' of https://github.com/mcarton/thefuck into mcarton-git-aliases 2015-07-19 21:27:04 +03:00
Vladimir Iakovlev
4276cacaf6 Merge pull request #292 from SimenB/delete-git-branch
Add git_branch_delete rule
2015-07-19 21:26:39 +03:00
Simen Bekkhus
b31aea3737 Add git_branch_delete rule 2015-07-19 13:45:46 +02:00
nvbn
fbfb4b5e41 Merge branch 'petr-tichy-master' 2015-07-18 17:19:57 +03:00
Petr Tichý
51c37bc5ab Fix wheel dependencies for Python 2 2015-07-17 18:51:35 +02:00
mcarton
5d0912fee8 Unquote over-quoted commands in @git_support
This allows writing rules more easily (eg. the git_branch_list rule
tests for `command.script.split() == 'git branch list'.split()`) and
looks nicer when `require_confirmation` is set.
2015-07-17 14:07:17 +02:00
mcarton
f6a4902074 Use @git_support in all git_* rules 2015-07-17 13:11:36 +02:00
mcarton
707d91200e Make the environment a setting
This would allow other rules to set the environment as needed for
`@git_support` and `GIT_TRACE`.
2015-07-17 11:37:13 +02:00
mcarton
b3e09d68df Start support for git aliases 2015-07-16 20:23:31 +02:00
nvbn
78769e4fbc Bump to 1.49 2015-07-15 07:49:18 +03:00
nvbn
3e4c043ccc #280: Add debug output 2015-07-15 07:47:54 +03:00
nvbn
934099fe9e #289: Add is a directory pattern to cp_omitting_directory rule 2015-07-15 07:12:07 +03:00
Vladimir Iakovlev
464f86eccf Merge pull request #288 from scorphus/overridden-aliases
fix(fish.get_aliases): do not include overridden aliases
2015-07-15 06:58:32 +03:00
Pablo Santiago Blum de Aguiar
891fbe7ed1 fix(fish.get_aliases): do not include overridden aliases
Fish Shell overrides some shell commands, such as `cd` and `ls` and
therefore some rules fail to match. The following aliases are excluded
by default:

 * cd
 * grep
 * ls
 * man
 * open

To change them, one can use the `TF_OVERRIDDEN_ALIASES` environment
variable such as:

```
set TF_OVERRIDDEN_ALIASES 'cd,grep,ls'
```

Fix #262
2015-07-13 22:53:15 -03:00
nvbn
5abab8bd1e Merge branch 'master' of github.com:nvbn/thefuck 2015-07-10 17:58:53 +03:00
nvbn
7ebc8a38af #N/A Add history rule 2015-07-10 17:58:41 +03:00
nvbn
f40b63f44b #N/A Add ability to disable memoization in tests 2015-07-10 17:06:05 +03:00
nvbn
4b4e7acc0f #N/A Add ability to get shell history 2015-07-10 16:42:21 +03:00
Vladimir Iakovlev
a8587d3871 Merge pull request #285 from mcarton/tmux
Use `get_closest` in the tmux rule
2015-07-10 15:54:14 +03:00
mcarton
370c58e679 Use get_closest in the tmux rule 2015-07-10 09:49:49 +02:00
Vladimir Iakovlev
328e65179e Merge pull request #283 from mcarton/mercurial
Some fixes in REAME.md
2015-07-09 20:13:38 +03:00
Vladimir Iakovlev
63bb4da8e1 Merge pull request #282 from mcarton/sudo
Add systemd's kind of error for the sudo rule
2015-07-09 20:13:17 +03:00
mcarton
0b5a7a8e2d Fix rule name in README 2015-07-09 18:35:33 +02:00
mcarton
5693bd49f7 #281 Add the mercurial rule to README.md 2015-07-09 18:01:44 +02:00
mcarton
12f8d017b9 Add systemd's kind of error for the sudo rule
A complete error would be:

```
% systemctl daemon-reload
==== AUTHENTICATING FOR org.freedesktop.systemd1.reload-daemon ===
Authentication is required to reload the systemd state.
Authenticating as: martin
Password:
```
2015-07-09 17:24:45 +02:00
nvbn
c7071763a3 Bump to 1.48 2015-07-08 21:34:39 +03:00
nvbn
27b5b9de6a #229 Use closest git command 2015-07-08 21:33:30 +03:00
nvbn
c0eae8b85c #N/A Add get_closest utility function 2015-07-08 21:30:24 +03:00
nvbn
fbf7b91005 Bump to 1.47 2015-07-07 16:39:40 +03:00
nvbn
3d842fe6eb #N/A Fix setup.py 2015-07-07 16:39:21 +03:00
Vladimir Iakovlev
17d359b43f Merge pull request #281 from scorphus/mercurial
improve(rules): add mercurial (hg) support
2015-07-07 16:36:06 +03:00
nvbn
22503cdb94 #279 Fix merge 2015-07-07 16:34:30 +03:00
nvbn
a8de919300 Merge branch 'master' of https://github.com/mkreder/thefuck into mkreder-master 2015-07-07 16:33:58 +03:00
nvbn
fac18de242 #267 Compare version_info with a tuple 2015-07-07 16:33:41 +03:00
Vladimir Iakovlev
26fc18dfe4 Merge pull request #278 from mcarton/sed
Add a sed_unterminated_s rule
2015-07-07 16:30:42 +03:00
Vladimir Iakovlev
0fb5c9a228 Merge pull request #277 from mcarton/fix-sudo
Fix the pacman rule with `sudo`
2015-07-07 16:30:02 +03:00
Vladimir Iakovlev
04a342bbc7 Merge pull request #276 from mcarton/tmux
Add a tmux rule
2015-07-07 16:29:15 +03:00
Vladimir Iakovlev
669fbff6ce Merge pull request #272 from scorphus/issue-271-ls-lah
fix(rules.ls_lah): make sure script starts with ls
2015-07-07 16:28:49 +03:00
Vladimir Iakovlev
e2542915e4 Merge pull request #267 from SanketDG/version_check
add python version testing in setup.py
2015-07-07 16:28:28 +03:00
Vladimir Iakovlev
1b08a7dcb6 Merge pull request #265 from mcarton/open
Open
2015-07-07 16:27:33 +03:00
Pablo Santiago Blum de Aguiar
8d77a2d528 improve(rules): add mercurial (hg) support
Fix #269
2015-07-06 21:37:31 -03:00
Matias Kreder
18ea4272ab change deps to install_requires #2 2015-07-04 12:26:56 -03:00
Matias Kreder
a75c99eb12 change deps to install_requires 2015-07-04 12:24:09 -03:00
mcarton
f3cdfbdbdb Add a sed_unterminated_s rule 2015-07-04 17:10:11 +02:00
Matias Kreder
30082bc9a9 fixed dependency problem, on python 3.4.0 pathlib is included in the python distribution so it must not be included in the requirements for thefuck otherwise it wont run at startup 2015-07-04 11:55:28 -03:00
mcarton
3822f62d90 Add a tmux rule 2015-07-04 14:17:33 +02:00
Pablo Santiago Blum de Aguiar
25cc98a21a fix(rules.ls_lah): make sure script starts with ls
Fix #271
2015-07-03 14:24:45 -03:00
SanketDG
409d839e92 add python version testing in setup.py 2015-07-03 00:29:07 +05:30
mcarton
51b5dd0460 Fix the pacman rule with sudo
Does not use @sudo_support as this does not place 'sudo' at the right
position.
2015-06-28 22:10:34 +02:00
Vladimir Iakovlev
cb33c912e5 Merge pull request #264 from mcarton/cleanup
Cleanup
2015-06-26 17:30:14 +03:00
Vladimir Iakovlev
470d66eeb2 Merge pull request #263 from mcarton/test.py
Add a test.py rule
2015-06-26 17:28:24 +03:00
mcarton
5552fd3dc9 s/compile/execute when talking about Python
The word 'compile' is just misleading here.
2015-06-26 14:54:33 +02:00
mcarton
ab55c1cccb Add other flavors of open command 2015-06-26 14:52:34 +02:00
mcarton
7173e0dbad Use spaces instead of tabs
The is more common in python and follows other rules usage.
2015-06-26 14:50:01 +02:00
mcarton
369ea7ff46 Add a test.py rule 2015-06-26 14:05:18 +02:00
mcarton
40fe604adc Replace use of '&&' by shells.and_ 2015-06-26 13:58:50 +02:00
mcarton
ef504b6436 Add a few other common patterns for the open rule 2015-06-26 13:45:23 +02:00
mcarton
b59e83cca9 Fix punctuation in README.md 2015-06-26 12:02:05 +02:00
mcarton
6d718a38dc :sort rules in README 2015-06-26 12:00:16 +02:00
mcarton
330f91f5dc Cleanup the systemctl rule 2015-06-26 11:55:10 +02:00
mcarton
e3cc9c52e6 Remove redundant patterns in sudo rule 2015-06-26 11:41:55 +02:00
mcarton
01ce65047a Use spaces instead of tabs
This is more common in python and follows other rules usage.
2015-06-26 11:27:04 +02:00
Vladimir Iakovlev
3203d57b36 Merge pull request #261 from maciekmm/rule-systemctl
Added systemctl rule
2015-06-24 13:50:36 +03:00
Maciej Mionskowski
28ab6c62f8 Added systemctl rule to README.md 2015-06-24 11:07:03 +02:00
Maciej Mionskowski
360e4673eb Added systemctl rule 2015-06-24 09:36:09 +02:00
Vladimir Iakovlev
c6aead735b Merge pull request #259 from diezcami/master
Added Python Compile Rule
2015-06-22 19:19:41 +03:00
Cami Diez
af3e1a555f Edited python compile rule 2015-06-21 09:27:03 +08:00
Cami Diez
897b847975 Added rule 2015-06-21 09:25:20 +08:00
Cami Diez
a0949b1102 Added Python Compile Rule 2015-06-21 09:24:27 +08:00
nvbn
2f1460120e Merge branch 'master' of github.com:nvbn/thefuck 2015-06-16 13:53:24 +03:00
nvbn
43ec397190 #252 Fix bash and zsh aliases 2015-06-16 13:52:41 +03:00
nvbn
eb537bef81 Merge branch 'issue-221-tf-alias' of https://github.com/scorphus/thefuck into scorphus-issue-221-tf-alias 2015-06-16 13:49:17 +03:00
Vladimir Iakovlev
b033d3893b Merge pull request #256 from scorphus/psutil-children
fix(main.wait_output): use Process’ children() instead of get_children()
2015-06-16 13:47:30 +03:00
nvbn
633c4f8415 #257 sudo patterns are case insensitive 2015-06-16 13:46:18 +03:00
TJ Horner
5bfe0ac997 One more trigger word 2015-06-15 17:13:21 -07:00
TJ Horner
e8a55220ad Forgot to commit the actual rule 👎 2015-06-15 17:11:54 -07:00
TJ Horner
ea306038f9 Fix sudo rule 2015-06-15 17:08:08 -07:00
Pablo Santiago Blum de Aguiar
6a88cc47b6 fix(main.wait_output): use Process’ children() instead of get_children()
Since psutil 2.0.0 `get_children()` has become deprecated and the use of
`children()` instead of it has been encouraged. In version 3.0.0, just
released, `get_children()` has been dropped. Check:

https://github.com/giampaolo/psutil/blob/master/HISTORY.rst

Fix #255
2015-06-15 09:59:58 -03:00
Pablo Santiago Blum de Aguiar
96fe1e77b3 refact(rules.no_command): do not add TF_ALIAS to the “callables” list
Fix #234, #245 and #251

Ref #221
2015-06-12 00:49:55 -03:00
Pablo Santiago Blum de Aguiar
c08d9125e4 refact(shells): use an env var TF_ALIAS to keep the name of the alias
This environment variable may be used by any rule to decide whether it
matches or not.
2015-06-10 20:50:49 -03:00
Pablo Santiago Blum de Aguiar
be682170e5 test(shells): add fuck alias to collection of aliases 2015-06-10 20:49:28 -03:00
nvbn
5e981d9b01 Bump to 1.46 2015-06-07 02:24:08 +03:00
nvbn
add499af7f #250 #247 Fix UnicodeDecodeError with fish 2015-06-07 02:23:48 +03:00
nvbn
9c711734aa Merge branch 'encode_fix' of https://github.com/SanketDG/thefuck into SanketDG-encode_fix 2015-06-07 02:13:48 +03:00
Vladimir Iakovlev
ff7a433f39 Merge pull request #249 from mcarton/cargo
Add two cargo related rules
2015-06-07 02:09:11 +03:00
SanketDG
6bb7d79ddc change encoding of return statement to utf8 2015-06-07 00:03:00 +05:30
mcarton
f6c013d033 Add a cargo_no_command rule 2015-06-06 17:22:14 +02:00
mcarton
01cf199866 Add a cargo rule 2015-06-06 17:05:51 +02:00
Vladimir Iakovlev
3d41a3fb7c Merge pull request #246 from cubuspl42/patch-1
Added mount rule
2015-06-04 20:58:24 +03:00
cubuspl42
f55fa35ebf Added mount rule
$ mount /dev/sda2 /mnt/var
mount: only root can do that
2015-06-04 18:38:09 +02:00
nvbn
ce922758a4 Bump to 1.45 2015-06-02 08:47:53 +03:00
Vladimir Iakovlev
c47968a180 Merge pull request #240 from diezcami/brew-upgrade
Added brew_upgrade rule
2015-06-02 08:46:57 +03:00
Vladimir Iakovlev
581c97ec4b Merge pull request #239 from diezcami/quotation-marks
Added quotation_marks rule
2015-06-02 08:46:06 +03:00
Vladimir Iakovlev
0a53966f9b Merge pull request #238 from diezcami/go-run
Added go_run rule
2015-06-02 08:44:56 +03:00
Camille Diez
ed4e7946d7 Updated brew_upgrade description 2015-06-02 13:27:03 +08:00
Cami Diez
2ed96b1d51 Added brew_upgrade rule 2015-06-02 13:23:34 +08:00
Cami Diez
79d94e2651 Added quotation marks rule 2015-06-02 13:18:13 +08:00
Camille Diez
c08182509d Update README.md 2015-06-02 12:08:28 +08:00
Cami Diez
1d2d907c60 Added go_run rule 2015-06-02 12:05:47 +08:00
Vladimir Iakovlev
13996261be Update README.md 2015-06-02 06:11:29 +03:00
Vladimir Iakovlev
afcd7fc67e Merge pull request #237 from waldyrious/patch-1
+how to make the command available right away
2015-06-02 06:10:13 +03:00
Vladimir Iakovlev
c0c7397057 Update README.md 2015-06-02 06:09:45 +03:00
Vladimir Iakovlev
707743e7a7 Merge pull request #236 from bugaevc/git-branch-list
Git branch list
2015-06-02 06:08:09 +03:00
Waldir Pimenta
d8779dc4a6 +how to make the command available right away 2015-06-02 00:57:00 +01:00
Sergey Bugaev
ba9214f7fc Add a test for git_branch_list rule 2015-06-02 00:17:57 +03:00
Sergey Bugaev
660422806c Add git_branch_list rule 2015-06-01 23:52:41 +03:00
Vladimir Iakovlev
3c8978784b Merge pull request #230 from scorphus/git-diff-staged-rule
add(rule): add the new git_diff_staged rule
2015-06-01 08:10:06 +03:00
Vladimir Iakovlev
995b373347 Merge pull request #232 from bugaevc/fix-sudo
Wrap apt-get rule in sudo_support
2015-05-31 01:20:15 +03:00
Sergey Bugaev
dbe1a94c7d Wrap apt-get rule in sudo_support
Fixes sudo_support not working for no_command rule.
2015-05-30 19:40:01 +03:00
Pablo Santiago Blum de Aguiar
15e13d7c1a add(rule): add the new git_diff_staged rule 2015-05-29 18:41:53 -03:00
Vladimir Iakovlev
3194913965 Merge pull request #228 from mcarton/fix-cd-space
Fix cd command
2015-05-29 04:04:02 +03:00
mcarton
237f43ebdb Fix cd command
Fix #226
2015-05-28 23:29:38 +02:00
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
nvbn
0fc7c00e8d Bump to 1.40 2015-05-11 14:16:59 +02:00
nvbn
64318c09b7 #161 support different psutils versions 2015-05-11 14:16:23 +02:00
Dugucloud
7e55041963 Merge branch 'master' of https://github.com/nvbn/thefuck 2015-05-10 15:46:06 +08:00
nvbn
5b6e17b5f1 Merge branch 'master' of github.com:nvbn/thefuck 2015-05-10 09:35:44 +02:00
nvbn
6cdc2c27fb #179 /c++1/cpp11/s 2015-05-10 09:35:02 +02:00
nvbn
62c605d0ac Merge branch 'C++11' of git://github.com/mcarton/thefuck into mcarton-C++11 2015-05-10 09:33:49 +02:00
mcarton
8930d01601 Update README.md to add the C++11 rule 2015-05-09 20:42:18 +02:00
mcarton
c749615ad6 Add a C++11 rule 2015-05-09 20:37:13 +02:00
Vladimir Iakovlev
f03d8c54b1 Merge pull request #177 from ja5h/master
Fixed grammar in README.txt
2015-05-09 19:30:20 +02:00
archilius777
20f1c76d27 Fixed grammar in README.txt 2015-05-09 22:56:35 +05:30
nvbn
f477cd69c2 Bump to 1.39 2015-05-09 18:53:49 +02:00
nvbn
690729d5a1 #176 Fix fails with wrong aliases 2015-05-09 18:53:36 +02:00
nvbn
f082ba829f Bump to 1.38 2015-05-08 15:27:33 +02:00
Vladimir Iakovlev
112e20d7c5 Merge pull request #171 from mcarton/dry
Add a don't repeat yourself rule
2015-05-08 12:11:41 +02:00
mcarton
95007220fb Add a test for the DRY rule 2015-05-08 11:42:00 +02:00
mcarton
56f636f3d8 Remove unnecessary space in the DRY rule 2015-05-08 11:41:26 +02:00
mcarton
932a7c5db5 Add a don't repeat yourself rule 2015-05-08 01:49:47 +02:00
Vladimir Iakovlev
1bed4d4e8d Merge pull request #170 from SanketDG/manfix
add rule for having no spaces in man commands.
2015-05-08 01:11:48 +02:00
Vladimir Iakovlev
e0bba379ff Merge pull request #169 from mcarton/git-checkout
Add the git_checkout rule
2015-05-08 01:11:09 +02:00
SanketDG
045959ec47 add man_no_space 2015-05-08 00:16:50 +05:30
SanketDG
65aeea857e add tests for man_no_space 2015-05-08 00:15:57 +05:30
SanketDG
793e883073 add man_no_space command 2015-05-08 00:15:32 +05:30
mcarton
a395ac568c Add the git_checkout rule
It creates a branch before checking-out to it if the branch does not
exist.
2015-05-07 20:32:04 +02: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
106 changed files with 2563 additions and 208 deletions

View File

@@ -4,6 +4,11 @@ python:
- "3.3"
- "2.7"
install:
- python setup.py develop
- pip install -r requirements.txt
script: py.test
- python setup.py develop
- pip install coveralls
- rm -rf build
script:
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
- coverage run --source=thefuck,tests -m py.test -v
after_success: coveralls

137
README.md
View File

@@ -1,12 +1,12 @@
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck)
**Aliases changed in 1.34.**
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck)
Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
Few examples:
![gif with examples](https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif)
Few more examples:
```bash
➜ apt-get install vim
@@ -14,7 +14,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
➜ fuck
sudo apt-get install vim
sudo apt-get install vim [enter/ctrl+c]
[sudo] password for nvbn:
Reading package lists... Done
...
@@ -29,7 +29,7 @@ To push the current branch and set the remote as upstream, use
➜ fuck
git push --set-upstream origin master
git push --set-upstream origin master [enter/ctrl+c]
Counting objects: 9, done.
...
```
@@ -42,7 +42,7 @@ No command 'puthon' found, did you mean:
zsh: command not found: puthon
➜ fuck
python
python [enter/ctrl+c]
Python 3.4.2 (default, Oct 8 2014, 13:08:17)
...
```
@@ -55,7 +55,7 @@ Did you mean this?
branch
➜ fuck
git branch
git branch [enter/ctrl+c]
* master
```
@@ -67,13 +67,13 @@ Did you mean this?
repl
➜ fuck
lein repl
lein repl [enter/ctrl+c]
nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848
REPL-y 0.3.1
...
```
If you are scared to blindly run changed command, there's `require_confirmation`
If you are not scared to blindly run the changed command, there is a `require_confirmation`
[settings](#settings) option:
```bash
@@ -82,7 +82,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
➜ fuck
sudo apt-get install vim [Enter/Ctrl+C]
sudo apt-get install vim
[sudo] password for nvbn:
Reading package lists... Done
...
@@ -104,29 +104,18 @@ sudo pip install thefuck
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
And add to `.bashrc` or `.bash_profile`(for OSX):
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
```bash
alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
eval "$(thefuck-alias)"
# You can use whatever you want as an alias, like for Mondays:
alias FUCK='fuck'
eval "$(thefuck-alias FUCK)"
```
Or in your `.zshrc`:
```bash
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
```
Alternatively, you can redirect the output of `thefuck-alias`:
```bash
thefuck-alias >> ~/.bashrc
```
[Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
Changes will be available only in a new shell session.
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
## Update
@@ -135,53 +124,96 @@ Changes will be available only in a new shell session.
sudo pip install thefuck --upgrade
```
**Aliases changed in 1.34.**
## How it works
The Fuck tries to match rule for the previous command, create new command
using matched rule and run it. Rules enabled by default:
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:
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`;
* `cd_correction` &ndash; spellchecks and correct failed cd commands;
* `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;
* `cpp11` &ndash; adds missing `-std=c++11` to `g++` or `clang++`;
* `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;
* `dry` &ndash; fixes repetitions like "git git push";
* `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `git_add` &ndash; fix *"Did you forget to 'git add'?"*;
* `git_no_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_add` &ndash; fixes *"Did you forget to 'git add'?"*;
* `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_push_pull` &ndash; runs `git pull` when `push` was rejected;
* `git_stash` &ndash; stashes you local modifications before rebasing or switching branch;
* `go_run` &ndash; appends `.go` extension when compiling/running Go programs
* `grep_recursive` &ndash; adds `-r` when you trying to grep directory;
* `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_no_command` &ndash; fixes wrong heroku commands like `heroku log`;
* `history` &ndash; tries to replace command with most similar command from history;
* `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `ls_lah` &ndash; adds -lah to ls;
* `man` &ndash; changes manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mercurial` &ndash; fixes wrong `hg` commands;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed;
* `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`;
* `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `python_execute` &ndash; appends missing `.py` when executing Python files;
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args'
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` &ndash; switches command from your local layout to en;
* `systemctl` &ndash; correctly orders parameters of confusing systemctl;
* `test.py` &ndash; runs `py.test` instead of `test.py`;
* `tmux` &ndash; fixes tmux commands;
* `whois` &ndash; fixes `whois` command.
Enabled by default only on specific platforms:
* `apt_get` &ndash; installs app from apt if it not installed;
* `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`;
* `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed.
Bundled, but not enabled by default:
* `ls_lah` &ndash; adds -lah to ls;
* `git_push_force` &ndash; adds `--force` to a `git push` (may conflict with `git_push_pull`);
* `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.
## Creating your own rules
For adding your own rule you should create `your-rule-name.py`
in `~/.thefuck/rules`. Rule should contain two functions:
`match(command: Command, settings: Settings) -> bool`
and `get_new_command(command: Command, settings: Settings) -> str`.
Also the rule can contain optional function
`side_effect(command: Command, settings: Settings) -> None` and
optional boolean `enabled_by_default`
in `~/.thefuck/rules`. The rule should contain two functions:
```python
match(command: Command, settings: Settings) -> bool
get_new_command(command: Command, settings: Settings) -> str
```
Also the rule can contain an optional function
`side_effect(command: Command, settings: Settings) -> None` and an
optional boolean `enabled_by_default`.
`Command` has three attributes: `script`, `stdout` and `stderr`.
`Settings` is a special object filled with `~/.thefuck/settings.py` and values from env, [more](#settings).
`Settings` is a special object filled with `~/.thefuck/settings.py` and values from env ([see more below](#settings)).
Simple example of the rule for running script with `sudo`:
@@ -193,14 +225,14 @@ def match(command, settings):
def get_new_command(command, settings):
return 'sudo {}'.format(command.script)
# Optional:
enabled_by_default = True
def side_effect(command, settings):
subprocess.call('chmod 777 .', shell=True)
priority = 1000 # Lower first
priority = 1000 # Lower first, default is 1000
```
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
@@ -208,13 +240,14 @@ priority = 1000 # Lower first
## Settings
The Fuck has a few settings parameters, they can be changed in `~/.thefuck/settings.py`:
The Fuck has a few settings parameters which can be changed in `~/.thefuck/settings.py`:
* `rules` &ndash; list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` &ndash; require confirmation before running new command, by default `False`;
* `require_confirmation` &ndash; requires confirmation before running new command, by default `True`;
* `wait_command` &ndash; max amount of time in seconds for getting previous command output;
* `no_colors` &ndash; disable colored output;
* `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first.
* `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first;
* `debug` &ndash; enables debug output, by default `False`.
Example of `settings.py`:
@@ -224,6 +257,7 @@ require_confirmation = True
wait_command = 10
no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
debug = False
```
Or via environment variables:
@@ -233,7 +267,8 @@ Or via environment variables:
* `THEFUCK_WAIT_COMMAND` &ndash; max amount of time in seconds for getting previous command output;
* `THEFUCK_NO_COLORS` &ndash; disable colored output, `true/false`;
* `THEFUCK_PRIORITY` &ndash; priority of the rules, like `no_command=9999:apt_get=100`,
rule with lower `priority` will be matched first.
rule with lower `priority` will be matched first;
* `THEFUCK_DEBUG` &ndash; enables debug output, `true/false`.
For example:

BIN
example.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

View File

@@ -2,3 +2,4 @@ pytest
mock
pytest-mock
wheel
setuptools>=17.1

19
setup.py Normal file → Executable file
View File

@@ -1,8 +1,20 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
import sys
if sys.version_info < (2, 7):
print('thefuck requires Python version 2.7 or later' +
' ({}.{} detected).'.format(*sys.version_info[:2]))
sys.exit(-1)
elif (3, 0) < sys.version_info < (3, 3):
print('thefuck requires Python version 3.3 or later' +
' ({}.{} detected).'.format(*sys.version_info[:2]))
sys.exit(-1)
VERSION = '1.37'
VERSION = '2.3'
install_requires = ['psutil', 'colorama', 'six']
extras_require = {':python_version<"3.4"': ['pathlib']}
setup(name='thefuck',
version=VERSION,
@@ -15,7 +27,8 @@ setup(name='thefuck',
'tests', 'release']),
include_package_data=True,
zip_safe=False,
install_requires=['pathlib', 'psutil', 'colorama', 'six'],
install_requires=install_requires,
extras_require=extras_require,
entry_points={'console_scripts': [
'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.shells:app_alias']})
'thefuck-alias = thefuck.main:print_alias']})

6
tests/conftest.py Normal file
View File

@@ -0,0 +1,6 @@
import pytest
@pytest.fixture
def no_memoize(monkeypatch):
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)

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,15 @@
import pytest
from thefuck.rules.brew_upgrade import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='brew upgrade')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('brew upgrade'), 'brew upgrade --all')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,21 @@
import pytest
from thefuck.rules.cargo_no_command import match, get_new_command
from tests.utils import Command
no_such_subcommand = """No such subcommand
Did you mean `build`?
"""
@pytest.mark.parametrize('command', [
Command(script='cargo buid', stderr=no_such_subcommand)])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('cargo buid', stderr=no_such_subcommand), 'cargo build')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -1,14 +1,22 @@
from mock import Mock
import pytest
from thefuck.rules.cp_omitting_directory import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Mock(script='cp dir', stderr="cp: omitting directory 'dir'"),
None)
assert not match(Mock(script='some dir',
stderr="cp: omitting directory 'dir'"), None)
assert not match(Mock(script='cp dir', stderr=""), None)
@pytest.mark.parametrize('script, stderr', [
('cp dir', 'cp: dor: is a directory'),
('cp dir', "cp: omitting directory 'dir'")])
def test_match(script, stderr):
assert match(Command(script, stderr=stderr), None)
@pytest.mark.parametrize('script, stderr', [
('some dir', 'cp: dor: is a directory'),
('some dir', "cp: omitting directory 'dir'"),
('cp dir', '')])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr), None)
def test_get_new_command():
assert get_new_command(Mock(script='cp dir'), None) == 'cp -a dir'
assert get_new_command(Command(script='cp dir'), None) == 'cp -a dir'

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_dry.py Normal file
View File

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

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,22 @@
import pytest
from thefuck.rules.git_branch_delete import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: The branch 'branch' is not fully merged.
If you are sure you want to delete it, run 'git branch -D branch'.
'''
def test_match(stderr):
assert match(Command('git branch -d branch', stderr=stderr), None)
assert not match(Command('git branch -d branch'), None)
assert not match(Command('ls', stderr=stderr), None)
def test_get_new_command(stderr):
assert get_new_command(Command('git branch -d branch', stderr=stderr), None)\
== "git branch -D branch"

View File

@@ -0,0 +1,18 @@
from thefuck import shells
from thefuck.rules.git_branch_list import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('git branch list'), None)
def test_not_match():
assert not match(Command(), None)
assert not match(Command('git commit'), None)
assert not match(Command('git branch'), None)
assert not match(Command('git stash list'), None)
def test_get_new_command():
assert (get_new_command(Command('git branch list'), None) ==
shells.and_('git branch --delete list', 'git branch'))

View File

@@ -0,0 +1,51 @@
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.fixture
def get_branches(mocker):
return mocker.patch('thefuck.rules.git_checkout')
@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('branches, 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'),
(['master'],
Command(script='git checkout mster', stderr=did_not_match('mster')),
'git checkout master'),
(['master'],
Command(script='git commit mster', stderr=did_not_match('mster')),
'git commit master')])
def test_get_new_command(branches, command, new_command, get_branches):
get_branches.return_value = branches
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,26 @@
import pytest
from thefuck.rules.git_diff_staged import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='git diff foo'),
Command(script='git diff')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git diff --staged'),
Command(script='git tag'),
Command(script='git branch'),
Command(script='git log')])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('git diff'), 'git diff --staged'),
(Command('git diff foo'), 'git diff --staged foo')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -25,6 +25,16 @@ stats
"""
@pytest.fixture
def git_not_command_closest():
return '''git: 'tags' is not a git command. See 'git --help'.
Did you mean one of these?
stage
tag
'''
@pytest.fixture
def git_command():
return "* master"
@@ -37,8 +47,11 @@ def test_match(git_not_command, git_command, git_not_command_one_of_this):
assert not match(Command('git branch', stderr=git_command), None)
def test_get_new_command(git_not_command, git_not_command_one_of_this):
assert get_new_command(Command('git brnch', stderr=git_not_command), None)\
== 'git branch'
def test_get_new_command(git_not_command, git_not_command_one_of_this,
git_not_command_closest):
assert get_new_command(Command('git brnch', stderr=git_not_command), None) \
== 'git branch'
assert get_new_command(Command('git st', stderr=git_not_command_one_of_this),
None) == 'git status'
assert get_new_command(Command('git tags', stderr=git_not_command_closest),
None) == 'git tag'

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,21 @@
import pytest
from thefuck.rules.git_pull_clone import match, get_new_command
from tests.utils import Command
git_err = '''
fatal: Not a git repository (or any parent up to mount point /home)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).
'''
@pytest.mark.parametrize('command', [
Command(script='git pull git@github.com:mcarton/thefuck.git', stderr=git_err)])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, output', [
(Command(script='git pull git@github.com:mcarton/thefuck.git', stderr=git_err), 'git clone git@github.com:mcarton/thefuck.git')])
def test_get_new_command(command, output):
assert get_new_command(command, None) == output

View File

@@ -20,5 +20,5 @@ def test_match(stderr):
def test_get_new_command(stderr):
assert get_new_command(Command(stderr=stderr), None)\
assert get_new_command(Command('git push', stderr=stderr), None)\
== "git push --set-upstream origin master"

View File

@@ -0,0 +1,52 @@
import pytest
from thefuck.rules.git_push_force import match, get_new_command
from tests.utils import Command
git_err = '''
To /tmp/foo
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to '/tmp/bar'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
'''
git_uptodate = 'Everything up-to-date'
git_ok = '''
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /tmp/bar
514eed3..f269c79 master -> master
'''
@pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_err),
Command(script='git push nvbn', stderr=git_err),
Command(script='git push nvbn master', stderr=git_err)])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_ok),
Command(script='git push', stderr=git_uptodate),
Command(script='git push nvbn', stderr=git_ok),
Command(script='git push nvbn master', stderr=git_uptodate),
Command(script='git push nvbn', stderr=git_ok),
Command(script='git push nvbn master', stderr=git_uptodate)])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, output', [
(Command(script='git push', stderr=git_err), 'git push --force'),
(Command(script='git push nvbn', stderr=git_err), 'git push --force nvbn'),
(Command(script='git push nvbn master', stderr=git_err), 'git push --force nvbn master')])
def test_get_new_command(command, output):
assert get_new_command(command, None) == output

View File

@@ -0,0 +1,54 @@
import pytest
from thefuck.rules.git_push_pull import match, get_new_command
from tests.utils import Command
git_err = '''
To /tmp/foo
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to '/tmp/bar'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
'''
git_uptodate = 'Everything up-to-date'
git_ok = '''
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To /tmp/bar
514eed3..f269c79 master -> master
'''
@pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_err),
Command(script='git push nvbn', stderr=git_err),
Command(script='git push nvbn master', stderr=git_err)])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='git push', stderr=git_ok),
Command(script='git push', stderr=git_uptodate),
Command(script='git push nvbn', stderr=git_ok),
Command(script='git push nvbn master', stderr=git_uptodate),
Command(script='git push nvbn', stderr=git_ok),
Command(script='git push nvbn master', stderr=git_uptodate)])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, output', [
(Command(script='git push', stderr=git_err), 'git pull && git push'),
(Command(script='git push nvbn', stderr=git_err),
'git pull nvbn && git push nvbn'),
(Command(script='git push nvbn master', stderr=git_err),
'git pull nvbn master && git push nvbn master')])
def test_get_new_command(command, output):
assert get_new_command(command, None) == output

View File

@@ -0,0 +1,37 @@
import pytest
from thefuck.rules.git_stash import match, get_new_command
from tests.utils import Command
cherry_pick_error = (
'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')
rebase_error = (
'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,17 @@
import pytest
from thefuck.rules.go_run import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='go run foo'),
Command(script='go run bar')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('go run foo'), 'go run foo.go'),
(Command('go run bar'), 'go run bar.go')])
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 .'

View File

@@ -0,0 +1,34 @@
import pytest
from tests.utils import Command
from thefuck.rules.heroku_not_command import match, get_new_command
def suggest_stderr(cmd):
return ''' ! `{}` is not a heroku command.
! Perhaps you meant `logs`, `pg`.
! See `heroku help` for a list of available commands.'''.format(cmd)
no_suggest_stderr = ''' ! `aaaaa` is not a heroku command.
! See `heroku help` for a list of available commands.'''
@pytest.mark.parametrize('cmd', ['log', 'pge'])
def test_match(cmd):
assert match(
Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)), None)
@pytest.mark.parametrize('script, stderr', [
('cat log', suggest_stderr('log')),
('heroku aaa', no_suggest_stderr)])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr), None)
@pytest.mark.parametrize('cmd, result', [
('log', 'heroku logs'),
('pge', 'heroku pg')])
def test_get_new_command(cmd, result):
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))
assert get_new_command(command, None) == result

View File

@@ -0,0 +1,42 @@
import pytest
from thefuck.rules.history import match, get_new_command
from tests.utils import Command
@pytest.fixture
def history(mocker):
return mocker.patch('thefuck.rules.history.get_history',
return_value=['le cat', 'fuck', 'ls cat',
'diff x', 'nocommand x'])
@pytest.fixture
def alias(mocker):
return mocker.patch('thefuck.rules.history.thefuck_alias',
return_value='fuck')
@pytest.fixture
def callables(mocker):
return mocker.patch('thefuck.rules.history.get_all_executables',
return_value=['diff', 'ls'])
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
@pytest.mark.parametrize('script', ['ls cet', 'daff x'])
def test_match(script):
assert match(Command(script=script), None)
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
@pytest.mark.parametrize('script', ['apt-get', 'nocommand y'])
def test_not_match(script):
assert not match(Command(script=script), None)
@pytest.mark.usefixtures('history', 'callables', 'no_memoize', 'alias')
@pytest.mark.parametrize('script, result', [
('ls cet', 'ls cat'),
('daff x', 'diff x')])
def test_get_new_command(script, result):
assert get_new_command(Command(script), None) == result

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

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

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

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

View File

@@ -3,9 +3,12 @@ from thefuck.rules.ls_lah import match, get_new_command
def test_match():
assert match(Mock(script='ls'), None)
assert match(Mock(script='ls file.py'), None)
assert match(Mock(script='ls /opt'), None)
assert not match(Mock(script='ls -lah /opt'), None)
assert not match(Mock(script='pacman -S binutils'), None)
assert not match(Mock(script='lsof'), None)
def test_get_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

@@ -0,0 +1,12 @@
from thefuck.rules.man_no_space import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('mandiff', stderr='mandiff: command not found'), None)
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(
Command('mandiff'), None) == 'man diff'

View File

@@ -0,0 +1,134 @@
import pytest
from tests.utils import Command
from thefuck.rules.mercurial import (
extract_possisiblities, match, get_new_command
)
@pytest.mark.parametrize('command', [
Command('hg base', stderr=(
"hg: unknown command 'base'"
'\n(did you mean one of blame, phase, rebase?)'
)),
Command('hg branchch', stderr=(
"hg: unknown command 'branchch'"
'\n(did you mean one of branch, branches?)'
)),
Command('hg vert', stderr=(
"hg: unknown command 'vert'"
'\n(did you mean one of revert?)'
)),
Command('hg lgo -r tip', stderr=(
"hg: command 're' is ambiguous:"
'\n(did you mean one of log?)'
)),
Command('hg rerere', stderr=(
"hg: unknown command 'rerere'"
'\n(did you mean one of revert?)'
)),
Command('hg re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)),
Command('hg re re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)),
])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command('hg', stderr=(
'\nMercurial Distributed SCM\n\nbasic commands:'
)),
Command('hg asdf', stderr=(
"hg: unknown command 'asdf'"
'\nMercurial Distributed SCM\n\nbasic commands:'
)),
Command('hg qwer', stderr=(
"hg: unknown command 'qwer'"
'\nMercurial Distributed SCM\n\nbasic commands:'
)),
Command('hg me', stderr=(
"\nabort: no repository found in './thefuck' (.hg not found)!"
)),
Command('hg reb', stderr=(
"\nabort: no repository found in './thefuck' (.hg not found)!"
)),
Command('hg co', stderr=(
"\nabort: no repository found in './thefuck' (.hg not found)!"
)),
])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, possibilities', [
(Command('hg base', stderr=(
"hg: unknown command 'base'"
'\n(did you mean one of blame, phase, rebase?)'
)), ['blame', 'phase', 'rebase']),
(Command('hg branchch', stderr=(
"hg: unknown command 'branchch'"
'\n(did you mean one of branch, branches?)'
)), ['branch', 'branches']),
(Command('hg vert', stderr=(
"hg: unknown command 'vert'"
'\n(did you mean one of revert?)'
)), ['revert']),
(Command('hg lgo -r tip', stderr=(
"hg: command 're' is ambiguous:"
'\n(did you mean one of log?)'
)), ['log']),
(Command('hg rerere', stderr=(
"hg: unknown command 'rerere'"
'\n(did you mean one of revert?)'
)), ['revert']),
(Command('hg re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)), ['rebase', 'recover', 'remove', 'rename', 'resolve', 'revert']),
(Command('hg re re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)), ['rebase', 'recover', 'remove', 'rename', 'resolve', 'revert']),
])
def test_extract_possisiblities(command, possibilities):
assert extract_possisiblities(command) == possibilities
@pytest.mark.parametrize('command, new_command', [
(Command('hg base', stderr=(
"hg: unknown command 'base'"
'\n(did you mean one of blame, phase, rebase?)'
)), 'hg rebase'),
(Command('hg branchch', stderr=(
"hg: unknown command 'branchch'"
'\n(did you mean one of branch, branches?)'
)), 'hg branch'),
(Command('hg vert', stderr=(
"hg: unknown command 'vert'"
'\n(did you mean one of revert?)'
)), 'hg revert'),
(Command('hg lgo -r tip', stderr=(
"hg: command 're' is ambiguous:"
'\n(did you mean one of log?)'
)), 'hg log -r tip'),
(Command('hg rerere', stderr=(
"hg: unknown command 'rerere'"
'\n(did you mean one of revert?)'
)), 'hg revert'),
(Command('hg re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)), 'hg rebase'),
(Command('hg re re', stderr=(
"hg: command 're' is ambiguous:"
'\n rebase recover remove rename resolve revert'
)), 'hg rebase re'),
])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -1,19 +1,29 @@
from mock import patch, Mock
import pytest
from thefuck.rules.no_command import match, get_new_command
from tests.utils import Command
@pytest.fixture(autouse=True)
def get_all_executables(mocker):
mocker.patch('thefuck.rules.no_command.get_all_executables',
return_value=['vim', 'apt-get', 'fsck'])
@pytest.mark.usefixtures('no_memoize')
def test_match():
with patch('thefuck.rules.no_command._get_all_bins',
return_value=['vim', 'apt-get']):
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='some text', script='vom file.py'), None)
assert match(Command(stderr='vom: not found', script='vom file.py'), None)
assert match(Command(stderr='fucck: not found', script='fucck'), None)
assert not match(Command(stderr='qweqwe: not found', script='qweqwe'), None)
assert not match(Command(stderr='some text', script='vom file.py'), None)
@pytest.mark.usefixtures('no_memoize')
def test_get_new_command():
with patch('thefuck.rules.no_command._get_all_bins',
return_value=['vim', 'apt-get']):
assert get_new_command(
Mock(stderr='vom: not found',
script='vom file.py'),
None) == 'vim file.py'
assert get_new_command(
Command(stderr='vom: not found',
script='vom file.py'),
None) == 'vim file.py'
assert get_new_command(
Command(stderr='fucck: not found',
script='fucck'),
Command) == 'fsck'

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,69 @@
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'),
Command(script='sudo vim', stderr='sudo: 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),
(Command(script='sudo vim', stderr='sudo: 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)
@pytest.mark.parametrize('command', [
Command(script='vim', stderr=''), Command(),
Command(script='sudo 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('sudo vim'), '{} -S extra/gvim && sudo vim'.format(pacman_cmd)),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd)),
(Command('sudo convert'), '{} -S extra/imagemagick && sudo 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('sudo vim'), '{} -S extra/gvim && sudo vim'.format(pacman_cmd), PKGFILE_OUTPUT_VIM),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd), PKGFILE_OUTPUT_CONVERT),
(Command('sudo convert'), '{} -S extra/imagemagick && sudo 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

View File

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

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.quotation_marks import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script="git commit -m \'My Message\""),
Command(script="git commit -am \"Mismatched Quotation Marks\'"),
Command(script="echo \"hello\'")])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command("git commit -m \'My Message\""), "git commit -m \"My Message\""),
(Command("git commit -am \"Mismatched Quotation Marks\'"), "git commit -am \"Mismatched Quotation Marks\""),
(Command("echo \"hello\'"), "echo \"hello\"")])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.rules.sed_unterminated_s import match, get_new_command
from tests.utils import Command
@pytest.fixture
def sed_unterminated_s():
return "sed: -e expression #1, char 9: unterminated `s' command"
def test_match(sed_unterminated_s):
assert match(Command('sed -e s/foo/bar', stderr=sed_unterminated_s), None)
assert match(Command('sed -es/foo/bar', stderr=sed_unterminated_s), None)
assert match(Command('sed -e s/foo/bar -e s/baz/quz', stderr=sed_unterminated_s), None)
assert not match(Command('sed -e s/foo/bar'), None)
assert not match(Command('sed -es/foo/bar'), None)
assert not match(Command('sed -e s/foo/bar -e s/baz/quz'), None)
def test_get_new_command(sed_unterminated_s):
assert get_new_command(Command('sed -e s/foo/bar', stderr=sed_unterminated_s), None) \
== 'sed -e s/foo/bar/'
assert get_new_command(Command('sed -es/foo/bar', stderr=sed_unterminated_s), None) \
== 'sed -es/foo/bar/'
assert get_new_command(Command(r"sed -e 's/\/foo/bar'", stderr=sed_unterminated_s), None) \
== r"sed -e 's/\/foo/bar/'"
assert get_new_command(Command(r"sed -e s/foo/bar -es/baz/quz", stderr=sed_unterminated_s), None) \
== r"sed -e s/foo/bar/ -es/baz/quz/"

View File

@@ -8,6 +8,10 @@ from tests.utils import Command
('permission denied', ''),
("npm ERR! Error: EACCES, unlink", ''),
('requested operation requires superuser privilege', ''),
('need to be root', ''),
('need root', ''),
('must be root', ''),
('You don\'t have access to the history DB.', ''),
('', "error: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/ipaddr.py'")])
def test_match(stderr, stdout):
assert match(Command(stderr=stderr, stdout=stdout), None)

View File

@@ -15,6 +15,7 @@ def test_match(command):
@pytest.mark.parametrize('command', [
Command(stderr='command not found: pat-get', script=u'pat-get'),
Command(stderr='command not found: ls', script=u'ls'),
Command(stderr='command not found: агсл', script=u'агсл'),
Command(stderr='some info', script=u'фзе-пуе')])
def test_not_match(command):
assert not switch_lang.match(command, None)

View File

@@ -0,0 +1,18 @@
import pytest
from thefuck.rules.systemctl import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('systemctl nginx start', stderr='Unknown operation \'nginx\'.'), None)
assert match(Command('sudo systemctl nginx start', stderr='Unknown operation \'nginx\'.'), None)
assert not match(Command('systemctl start nginx'), None)
assert not match(Command('systemctl start nginx'), None)
assert not match(Command('sudo systemctl nginx', stderr='Unknown operation \'nginx\'.'), None)
assert not match(Command('systemctl nginx', stderr='Unknown operation \'nginx\'.'), None)
assert not match(Command('systemctl start wtf', stderr='Failed to start wtf.service: Unit wtf.service failed to load: No such file or directory.'), None)
def test_get_new_command():
assert get_new_command(Command('systemctl nginx start'), None) == "systemctl start nginx"
assert get_new_command(Command('sudo systemctl nginx start'), None) == "sudo systemctl start nginx"

19
tests/rules/test_tmux.py Normal file
View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.tmux import match, get_new_command
from tests.utils import Command
@pytest.fixture
def tmux_ambiguous():
return "ambiguous command: list, could be: " \
"list-buffers, list-clients, list-commands, list-keys, " \
"list-panes, list-sessions, list-windows"
def test_match(tmux_ambiguous):
assert match(Command('tmux list', stderr=tmux_ambiguous), None)
def test_get_new_command(tmux_ambiguous):
assert get_new_command(Command('tmux list', stderr=tmux_ambiguous), None)\
== 'tmux list-keys'

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

@@ -5,3 +5,10 @@ from thefuck import logs
def test_color():
assert logs.color('red', Mock(no_colors=False)) == 'red'
assert logs.color('red', Mock(no_colors=True)) == ''
def test_debug(capsys):
logs.debug('test', Mock(no_colors=True, debug=True))
assert capsys.readouterr() == ('', 'DEBUG: test\n')
logs.debug('test', Mock(no_colors=True, debug=False))
assert capsys.readouterr() == ('', '')

View File

@@ -77,23 +77,23 @@ class TestGetCommand(object):
monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
def test_get_command_calls(self, Popen):
assert main.get_command(Mock(),
assert main.get_command(Mock(env={}),
['thefuck', 'apt-get', 'search', 'vim']) \
== Command('apt-get search vim', 'stdout', 'stderr')
Popen.assert_called_once_with('apt-get search vim',
shell=True,
stdout=PIPE,
stderr=PIPE,
env={'LANG': 'C'})
env={})
@pytest.mark.parametrize('args, result', [
(['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')])
def test_get_command_script(self, args, result):
if result:
assert main.get_command(Mock(), args).script == result
assert main.get_command(Mock(env={}), args).script == result
else:
assert main.get_command(Mock(), args) is None
assert main.get_command(Mock(env={}), args) is None
class TestGetMatchedRule(object):
@@ -110,7 +110,7 @@ class TestGetMatchedRule(object):
def test_when_rule_failed(self, capsys):
main.get_matched_rule(
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
Mock(no_colors=True))
Mock(no_colors=True, debug=False))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
@@ -126,7 +126,7 @@ class TestRunRule(object):
def test_run_rule_with_side_effect(self, capsys):
side_effect = Mock()
settings = Mock()
settings = Mock(debug=False)
command = Command()
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
side_effect=side_effect),

View File

@@ -12,24 +12,61 @@ def isfile(mocker):
return mocker.patch('os.path.isfile', return_value=True)
@pytest.fixture
@pytest.mark.usefixtures('isfile')
def history_lines(mocker):
def aux(lines):
mock = mocker.patch('io.open')
mock.return_value.__enter__\
.return_value.__iter__.return_value = lines
return aux
class TestGeneric(object):
def test_from_shell(self):
assert shells.Generic().from_shell('pwd') == 'pwd'
@pytest.fixture
def shell(self):
return shells.Generic()
def test_to_shell(self):
assert shells.Generic().to_shell('pwd') == 'pwd'
def test_from_shell(self, shell):
assert shell.from_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open):
assert shells.Generic().put_to_history('ls') is None
def test_to_shell(self, shell):
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
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {}
def test_app_alias(self, shell):
assert 'alias fuck' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_ALIAS' in shell.app_alias('fuck')
def test_get_history(self, history_lines, shell):
history_lines(['ls', 'rm'])
# We don't know what to do in generic shell with history lines,
# so just ignore them:
assert list(shell.get_history()) == []
@pytest.mark.usefixtures('isfile')
class TestBash(object):
@pytest.fixture
def shell(self):
return shells.Bash()
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen')
mock.return_value.stdout.read.return_value = (
b'alias fuck=\'eval $(thefuck $(fc -ln -1))\'\n'
b'alias l=\'ls -CF\'\n'
b'alias la=\'ls -A\'\n'
b'alias ll=\'ls -alF\'')
@@ -37,42 +74,162 @@ class TestBash(object):
@pytest.mark.parametrize('before, after', [
('pwd', 'pwd'),
('fuck', 'eval $(thefuck $(fc -ln -1))'),
('awk', 'awk'),
('ll', 'ls -alF')])
def test_from_shell(self, before, after):
assert shells.Bash().from_shell(before) == after
def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after
def test_to_shell(self):
assert shells.Bash().to_shell('pwd') == 'pwd'
def test_to_shell(self, shell):
assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open):
shells.Bash().put_to_history('ls')
def test_put_to_history(self, builtins_open, shell):
shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
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() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}
def test_app_alias(self, shell):
assert 'alias fuck' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_ALIAS' in shell.app_alias('fuck')
def test_get_history(self, history_lines, shell):
history_lines(['ls', 'rm'])
assert list(shell.get_history()) == ['ls', 'rm']
@pytest.mark.usefixtures('isfile')
class TestZsh(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'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
b'man\nmath\npopd\npushd\nruby')
return mock
@pytest.fixture
def environ(self, monkeypatch):
data = {'TF_OVERRIDDEN_ALIASES': 'cd, ls, man, open'}
monkeypatch.setattr('thefuck.shells.os.environ', data)
return data
@pytest.mark.usefixture('environ')
def test_get_overridden_aliases(self, shell, environ):
assert shell._get_overridden_aliases() == ['cd', 'ls', 'man', 'open']
@pytest.mark.parametrize('before, after', [
('cd', 'cd'),
('pwd', 'pwd'),
('fuck', 'fish -ic "fuck"'),
('find', 'find'),
('funced', 'fish -ic "funced"'),
('grep', 'grep'),
('awk', 'awk'),
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
('man', 'man'),
('open', 'open'),
('vim', 'vim'),
('ll', 'fish -ic "ll"'),
('ls', 'ls')]) # 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',
'history': 'history',
'll': 'll',
'math': 'math',
'popd': 'popd',
'pushd': 'pushd',
'ruby': 'ruby'}
def test_app_alias(self, shell):
assert 'function fuck' in shell.app_alias('fuck')
assert 'function FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_ALIAS' in shell.app_alias('fuck')
@pytest.mark.usefixtures('isfile')
class TestZsh(object):
@pytest.fixture
def shell(self):
return shells.Zsh()
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen')
mock.return_value.stdout.read.return_value = (
b'fuck=\'eval $(thefuck $(fc -ln -1 | tail -n 1))\'\n'
b'l=\'ls -CF\'\n'
b'la=\'ls -A\'\n'
b'll=\'ls -alF\'')
return mock
@pytest.mark.parametrize('before, after', [
('fuck', 'eval $(thefuck $(fc -ln -1 | tail -n 1))'),
('pwd', 'pwd'),
('ll', 'ls -alF')])
def test_from_shell(self, before, after):
assert shells.Zsh().from_shell(before) == after
def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after
def test_to_shell(self):
assert shells.Zsh().to_shell('pwd') == 'pwd'
def test_to_shell(self, shell):
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',
return_value=1430707243.3517463)
shells.Zsh().put_to_history('ls')
shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
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() == {
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}
def test_app_alias(self, shell):
assert 'alias fuck' in shell.app_alias('fuck')
assert 'alias FUCK' in shell.app_alias('FUCK')
assert 'thefuck' in shell.app_alias('fuck')
assert 'TF_ALIAS' in shell.app_alias('fuck')
def test_get_history(self, history_lines, shell):
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
assert list(shell.get_history()) == ['ls', 'rm']

View File

@@ -1,6 +1,7 @@
import pytest
from mock import Mock
from thefuck.utils import sudo_support, wrap_settings
from thefuck.utils import git_support, sudo_support, wrap_settings,\
memoize, get_closest, get_all_executables
from thefuck.types import Settings
from tests.utils import Command
@@ -24,3 +25,70 @@ def test_sudo_support(return_value, command, called, result):
fn = Mock(return_value=return_value, __name__='')
assert sudo_support(fn)(Command(command), None) == result
fn.assert_called_once_with(Command(called), None)
@pytest.mark.parametrize('called, command, stderr', [
('git co', 'git checkout', "19:22:36.299340 git.c:282 trace: alias expansion: co => 'checkout'"),
('git com file', 'git commit --verbose file', "19:23:25.470911 git.c:282 trace: alias expansion: com => 'commit' '--verbose'")])
def test_git_support(called, command, stderr):
@git_support
def fn(command, settings): return command.script
assert fn(Command(script=called, stderr=stderr), None) == command
@pytest.mark.parametrize('command, is_git', [
('git pull', True),
('hub pull', True),
('git push --set-upstream origin foo', True),
('hub push --set-upstream origin foo', True),
('ls', False),
('cat git', False),
('cat hub', False)])
def test_git_support_match(command, is_git):
@git_support
def fn(command, settings): return True
assert fn(Command(script=command), None) == is_git
def test_memoize():
fn = Mock(__name__='fn')
memoized = memoize(fn)
memoized()
memoized()
fn.assert_called_once_with()
@pytest.mark.usefixtures('no_memoize')
def test_no_memoize():
fn = Mock(__name__='fn')
memoized = memoize(fn)
memoized()
memoized()
assert fn.call_count == 2
class TestGetClosest(object):
def test_when_can_match(self):
assert 'branch' == get_closest('brnch', ['branch', 'status'])
def test_when_cant_match(self):
assert 'status' == get_closest('st', ['status', 'reset'])
def test_without_fallback(self):
assert get_closest('st', ['status', 'reset'],
fallback_to_first=False) is None
@pytest.fixture
def get_aliases(mocker):
mocker.patch('thefuck.shells.get_aliases',
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
@pytest.mark.usefixtures('no_memoize', 'get_aliases')
def test_get_all_callables():
all_callables = get_all_executables()
assert 'vim' in all_callables
assert 'fsck' in all_callables
assert 'fuck' not in all_callables

View File

@@ -27,15 +27,18 @@ DEFAULT_PRIORITY = 1000
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_command': 3,
'require_confirmation': False,
'require_confirmation': True,
'no_colors': False,
'priority': {}}
'debug': False,
'priority': {},
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_COMMAND': 'wait_command',
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
'THEFUCK_NO_COLORS': 'no_colors',
'THEFUCK_PRIORITY': 'priority'}
'THEFUCK_PRIORITY': 'priority',
'THEFUCK_DEBUG': 'debug'}
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
@@ -87,7 +90,7 @@ def _val_from_env(env, attr):
return dict(_priority_from_env(val))
elif attr == 'wait_command':
return int(val)
elif attr in ('require_confirmation', 'no_colors'):
elif attr in ('require_confirmation', 'no_colors', 'debug'):
return val.lower() == 'true'
else:
return val

View File

@@ -1,3 +1,4 @@
from pprint import pformat
import sys
from traceback import format_exception
import colorama
@@ -52,3 +53,12 @@ def failed(msg, settings):
msg=msg,
red=color(colorama.Fore.RED, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
def debug(msg, settings):
if settings.debug:
sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
msg=msg,
reset=color(colorama.Style.RESET_ALL, settings),
blue=color(colorama.Fore.BLUE, settings),
bold=color(colorama.Style.BRIGHT, settings)))

View File

@@ -1,6 +1,7 @@
from imp import load_source
from pathlib import Path
from os.path import expanduser
from pprint import pformat
from subprocess import Popen, PIPE
import os
import sys
@@ -62,7 +63,7 @@ def wait_output(settings, popen):
proc.wait(settings.wait_command)
return True
except TimeoutExpired:
for child in proc.get_children(recursive=True):
for child in proc.children(recursive=True):
child.kill()
proc.kill()
return False
@@ -79,8 +80,13 @@ def get_command(settings, args):
return
script = shells.from_shell(script)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
env=dict(os.environ, LANG='C'))
logs.debug(u'Call: {}'.format(script), settings)
env = dict(os.environ)
env.update(settings.env)
logs.debug(u'Executing with env: {}'.format(env), settings)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE, env=env)
if wait_output(settings, result):
return types.Command(script, result.stdout.read().decode('utf-8'),
result.stderr.read().decode('utf-8'))
@@ -90,6 +96,7 @@ def get_matched_rule(command, rules, settings):
"""Returns first matched rule for command."""
for rule in rules:
try:
logs.debug(u'Trying rule: {}'.format(rule.name), settings)
if rule.match(command, settings):
return rule
except Exception:
@@ -121,17 +128,35 @@ def run_rule(rule, command, settings):
print(new_command)
# Entry points:
def main():
colorama.init()
user_dir = setup_user_dir()
settings = conf.get_settings(user_dir)
logs.debug(u'Run with settings: {}'.format(pformat(settings)), settings)
command = get_command(settings, sys.argv)
if command:
logs.debug(u'Received stdout: {}'.format(command.stdout), settings)
logs.debug(u'Received stderr: {}'.format(command.stderr), settings)
rules = get_rules(user_dir, settings)
logs.debug(
u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
settings)
matched_rule = get_matched_rule(command, rules, settings)
if matched_rule:
logs.debug(u'Matched rule: {}'.format(matched_rule.name), settings)
run_rule(matched_rule, command, settings)
return
logs.failed('No fuck given', settings)
def print_alias():
alias = shells.thefuck_alias()
if len(sys.argv) > 1:
alias = sys.argv[1]
print(shells.app_alias(alias))

View File

@@ -1,9 +1,12 @@
from thefuck import shells
from thefuck.utils import sudo_support
try:
import CommandNotFound
except ImportError:
enabled_by_default = False
@sudo_support
def match(command, settings):
if 'not found' in command.stderr:
try:
@@ -15,9 +18,10 @@ def match(command, settings):
# IndexError is thrown when no matching package is found
return False
@sudo_support
def get_new_command(command, settings):
c = CommandNotFound.CommandNotFound()
pkgs = c.getPackages(command.script.split(" ")[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

@@ -1,14 +1,14 @@
import difflib
import os
import re
from subprocess import check_output
import thefuck.logs
from thefuck.utils import get_closest
# Formulars are base on each local system's status
brew_formulas = []
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'
for file_name in os.listdir(brew_formula_path):
@@ -18,8 +18,8 @@ except:
pass
def _get_similar_formulars(formula_name):
return difflib.get_close_matches(formula_name, brew_formulas, 1, 0.85)
def _get_similar_formula(formula_name):
return get_closest(formula_name, brew_formulas, 1, 0.85)
def match(command, settings):
@@ -30,7 +30,7 @@ def match(command, settings):
if is_proper_command:
formula = re.findall(r'Error: No available formula for ([a-z]+)',
command.stderr)[0]
has_possible_formulas = len(_get_similar_formulars(formula)) > 0
has_possible_formulas = bool(_get_similar_formula(formula))
return has_possible_formulas
@@ -38,6 +38,6 @@ def match(command, settings):
def get_new_command(command, settings):
not_exist_formula = re.findall(r'Error: No available formula for ([a-z]+)',
command.stderr)[0]
exist_formula = _get_similar_formulars(not_exist_formula)[0]
exist_formula = _get_similar_formula(not_exist_formula)
return command.script.replace(not_exist_formula, exist_formula, 1)

View File

@@ -1,8 +1,7 @@
import difflib
import os
import re
import subprocess
from thefuck.utils import get_closest
BREW_CMD_PATH = '/Library/Homebrew/cmd'
TAP_PATH = '/Library/Taps'
@@ -12,7 +11,8 @@ TAP_CMD_PATH = '/%s/%s/cmd'
def _get_brew_path_prefix():
"""To get brew path"""
try:
return subprocess.check_output(['brew', '--prefix']).strip()
return subprocess.check_output(['brew', '--prefix'],
universal_newlines=True).strip()
except:
return None
@@ -77,8 +77,8 @@ if brew_path_prefix:
pass
def _get_similar_commands(command):
return difflib.get_close_matches(command, brew_commands)
def _get_similar_command(command):
return get_closest(command, brew_commands)
def match(command, settings):
@@ -89,7 +89,7 @@ def match(command, settings):
if is_proper_command:
broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
command.stderr)[0]
has_possible_commands = len(_get_similar_commands(broken_cmd)) > 0
has_possible_commands = bool(_get_similar_command(broken_cmd))
return has_possible_commands
@@ -97,6 +97,6 @@ def match(command, settings):
def get_new_command(command, settings):
broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
command.stderr)[0]
new_cmd = _get_similar_commands(broken_cmd)[0]
new_cmd = _get_similar_command(broken_cmd)
return command.script.replace(broken_cmd, new_cmd, 1)

View File

@@ -0,0 +1,14 @@
# Appends --all to the brew upgrade command
#
# Example:
# > brew upgrade
# Warning: brew upgrade with no arguments will change behaviour soon!
# It currently upgrades all formula but this will soon change to require '--all'.
#
#
def match(command, settings):
return (command.script == 'brew upgrade')
def get_new_command(command, settings):
return command.script + ' --all'

6
thefuck/rules/cargo.py Normal file
View File

@@ -0,0 +1,6 @@
def match(command, settings):
return command.script == 'cargo'
def get_new_command(command, settings):
return 'cargo build'

View File

@@ -0,0 +1,14 @@
import re
def match(command, settings):
return ('cargo' in command.script
and 'No such subcommand' in command.stderr
and 'Did you mean' in command.stderr)
def get_new_command(command, settings):
broken = command.script.split()[1]
fix = re.findall(r'Did you mean `([^`]*)`', command.stderr)[0]
return command.script.replace(broken, fix, 1)

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
from thefuck import shells
from thefuck.utils import sudo_support
@@ -11,4 +12,5 @@ def match(command, settings):
@sudo_support
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

@@ -4,8 +4,9 @@ from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
stderr = command.stderr.lower()
return command.script.startswith('cp ') \
and 'cp: omitting directory' in command.stderr.lower()
and ('omitting directory' in stderr or 'is a directory' in stderr)
@sudo_support

9
thefuck/rules/cpp11.py Normal file
View File

@@ -0,0 +1,9 @@
def match(command, settings):
return (('g++' in command.script or 'clang++' in command.script) and
('This file requires compiler and library support for the '
'ISO C++ 2011 standard.' in command.stderr or
'-Wc++11-extensions' in command.stderr))
def get_new_command(command, settings):
return command.script + ' -std=c++11'

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)

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

@@ -0,0 +1,12 @@
def match(command, settings):
split_command = command.script.split()
return len(split_command) >= 2 and split_command[0] == split_command[1]
def get_new_command(command, settings):
return command.script[command.script.find(' ')+1:]
# it should be rare enough to actually have to type twice the same word, so
# this rule can have a higher priority to come before things like "cd cd foo"
priority = 900

View File

@@ -1,15 +1,18 @@
import re
from thefuck import utils, shells
@utils.git_support
def match(command, settings):
return ('git' in command.script
and 'did not match any file(s) known to git.' in command.stderr
return ('did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" in command.stderr)
@utils.git_support
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"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

@@ -0,0 +1,12 @@
from thefuck import utils
@utils.git_support
def match(command, settings):
return ('branch -d' in command.script
and 'If you are sure you want to delete it' in command.stderr)
@utils.git_support
def get_new_command(command, settings):
return command.script.replace('-d', '-D')

View File

@@ -0,0 +1,12 @@
from thefuck import utils, shells
@utils.git_support
def match(command, settings):
# catches "git branch list" in place of "git branch"
return command.script.split()[1:] == 'branch list'.split()
@utils.git_support
def get_new_command(command, settings):
return shells.and_('git branch --delete list', 'git branch')

View File

@@ -0,0 +1,36 @@
import re
import subprocess
from thefuck import shells, utils
@utils.git_support
def match(command, settings):
return ('did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" not in command.stderr)
def get_branches():
proc = subprocess.Popen(
['git', 'branch', '-a', '--no-color', '--no-column'],
stdout=subprocess.PIPE)
for line in proc.stdout.readlines():
line = line.decode('utf-8')
if line.startswith('*'):
line = line.split(' ')[1]
if '/' in line:
line = line.split('/')[-1]
yield line.strip()
@utils.git_support
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
closest_branch = utils.get_closest(missing_file, get_branches(),
fallback_to_first=False)
if closest_branch:
return command.script.replace(missing_file, closest_branch, 1)
else:
return shells.and_('git branch {}', '{}').format(
missing_file, command.script)

View File

@@ -0,0 +1,12 @@
from thefuck import utils
@utils.git_support
def match(command, settings):
return ('diff' in command.script and
'--staged' not in command.script)
@utils.git_support
def get_new_command(command, settings):
return command.script.replace(' diff', ' diff --staged')

View File

@@ -1,16 +1,27 @@
import re
from thefuck.utils import get_closest, git_support
@git_support
def match(command, settings):
return ('git' in command.script
and " is not a git command. See 'git --help'." in command.stderr
return (" is not a git command. See 'git --help'." in command.stderr
and 'Did you mean' in command.stderr)
def _get_all_git_matched_commands(stderr):
should_yield = False
for line in stderr.split('\n'):
if 'Did you mean' in line:
should_yield = True
elif should_yield and line:
yield line.strip()
@git_support
def get_new_command(command, settings):
broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
command.stderr)[0]
new_cmd = re.findall(r'Did you mean[^\n]*\n\s*([^\n]*)',
command.stderr)[0]
new_cmd = get_closest(broken_cmd,
_get_all_git_matched_commands(command.stderr))
return command.script.replace(broken_cmd, new_cmd, 1)

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

@@ -0,0 +1,16 @@
from thefuck import shells, utils
@utils.git_support
def match(command, settings):
return ('pull' in command.script
and 'set-upstream' in command.stderr)
@utils.git_support
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 shells.and_(set_upstream, command.script)

View File

@@ -0,0 +1,12 @@
from thefuck import utils
@utils.git_support
def match(command, settings):
return ('fatal: Not a git repository' in command.stderr
and "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)." in command.stderr)
@utils.git_support
def get_new_command(command, settings):
return command.script.replace(' pull ', ' clone ')

View File

@@ -1,8 +1,12 @@
from thefuck import utils
@utils.git_support
def match(command, settings):
return ('git' in command.script
and 'push' in command.script
return ('push' in command.script
and 'set-upstream' in command.stderr)
@utils.git_support
def get_new_command(command, settings):
return command.stderr.split('\n')[-3].strip()

View File

@@ -0,0 +1,17 @@
from thefuck import utils
@utils.git_support
def match(command, settings):
return ('push' in command.script
and '! [rejected]' in command.stderr
and 'failed to push some refs to' in command.stderr
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
@utils.git_support
def get_new_command(command, settings):
return command.script.replace('push', 'push --force')
enabled_by_default = False

View File

@@ -0,0 +1,16 @@
from thefuck import utils
from thefuck.shells import and_
@utils.git_support
def match(command, settings):
return ('push' in command.script
and '! [rejected]' in command.stderr
and 'failed to push some refs to' in command.stderr
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
@utils.git_support
def get_new_command(command, settings):
return and_(command.script.replace('push', 'pull'),
command.script)

View File

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

14
thefuck/rules/go_run.py Normal file
View File

@@ -0,0 +1,14 @@
# Appends .go when compiling go files
#
# Example:
# > go run foo
# error: go run: no go files listed
#
#
def match(command, settings):
return (command.script.startswith ('go run ')
and not command.script.endswith('.go'))
def get_new_command(command, settings):
return command.script + '.go'

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

View File

@@ -0,0 +1,20 @@
import re
from thefuck.utils import get_closest
def match(command, settings):
return command.script.startswith('heroku') and \
'is not a heroku command' in command.stderr and \
'Perhaps you meant' in command.stderr
def _get_suggests(stderr):
for line in stderr.split('\n'):
if 'Perhaps you meant' in line:
return re.findall(r'`([^`]+)`', line)
def get_new_command(command, settings):
wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0]
correct = get_closest(wrong, _get_suggests(command.stderr))
return command.script.replace(' {}'.format(wrong), ' {}'.format(correct), 1)

36
thefuck/rules/history.py Normal file
View File

@@ -0,0 +1,36 @@
from difflib import get_close_matches
from thefuck.shells import get_history, thefuck_alias
from thefuck.utils import get_closest, memoize, get_all_executables
def _not_corrected(history, tf_alias):
"""Returns all lines from history except that comes before `fuck`."""
previous = None
for line in history:
if previous is not None and line != tf_alias:
yield previous
previous = line
if history:
yield history[-1]
@memoize
def _history_of_exists_without_current(command):
history = get_history()
tf_alias = thefuck_alias()
executables = get_all_executables()
return [line for line in _not_corrected(history, tf_alias)
if not line.startswith(tf_alias) and not line == command.script
and line.split(' ')[0] in executables]
def match(command, settings):
return len(get_close_matches(command.script,
_history_of_exists_without_current(command)))
def get_new_command(command, settings):
return get_closest(command.script,
_history_of_exists_without_current(command))
priority = 9999

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

@@ -0,0 +1,14 @@
# 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,8 +1,7 @@
enabled_by_default = False
def match(command, settings):
return 'ls' in command.script and not ('ls -' in command.script)
return (command.script == 'ls'
or command.script.startswith('ls ')
and not ('ls -' in command.script))
def get_new_command(command, settings):

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

@@ -0,0 +1,9 @@
def match(command, settings):
return (command.script.startswith(u'man')
and u'command not found' in command.stderr.lower())
def get_new_command(command, settings):
return u'man {}'.format(command.script[3:])
priority = 2000

View File

@@ -0,0 +1,29 @@
import re
from thefuck.utils import get_closest
def extract_possisiblities(command):
possib = re.findall(r'\n\(did you mean one of ([^\?]+)\?\)', command.stderr)
if possib:
return possib[0].split(', ')
possib = re.findall(r'\n ([^$]+)$', command.stderr)
if possib:
return possib[0].split(' ')
return possib
def match(command, settings):
return (command.script.startswith('hg ')
and ('hg: unknown command' in command.stderr
and '(did you mean one of ' in command.stderr
or "hg: command '" in command.stderr
and "' is ambiguous:" in command.stderr
)
)
def get_new_command(command, settings):
script = command.script.split(' ')
possisiblities = extract_possisiblities(command)
script[1] = get_closest(script[1], possisiblities)
return ' '.join(script)

View File

@@ -1,35 +1,19 @@
from difflib import get_close_matches
import os
from pathlib import Path
from thefuck.utils import sudo_support
def _safe(fn, fallback):
try:
return fn()
except OSError:
return fallback
def _get_all_bins():
return [exe.name
for path in os.environ.get('PATH', '').split(':')
for exe in _safe(lambda: list(Path(path).iterdir()), [])
if not _safe(exe.is_dir, True)]
from thefuck.utils import sudo_support, get_all_executables
@sudo_support
def match(command, settings):
return 'not found' in command.stderr and \
bool(get_close_matches(command.script.split(' ')[0],
_get_all_bins()))
get_all_executables()))
@sudo_support
def get_new_command(command, settings):
old_command = command.script.split(' ')[0]
new_command = get_close_matches(old_command,
_get_all_bins())[0]
get_all_executables())[0]
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)

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

@@ -0,0 +1,26 @@
# 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', 'xdg-open', 'gnome-open', 'kde-open'))
and (
'.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
or '.info' in command.script
or '.me' in command.script
or 'www.' in command.script))
def get_new_command(command, settings):
return 'open http://' + command.script[5:]

View File

@@ -1,23 +1,22 @@
import subprocess
from thefuck.utils import DEVNULL
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
from thefuck.utils import DEVNULL, which
from thefuck import shells
from thefuck.utils import memoize
@memoize
def __get_pkgfile(command):
try:
command = command.script
if command.startswith('sudo'):
command = command[5:]
command = command.split(" ")[0]
return subprocess.check_output(
['pkgfile', '-b', '-v', command.script.split(" ")[0]],
universal_newlines=True, stderr=subprocess.DEVNULL
['pkgfile', '-b', '-v', command],
universal_newlines=True, stderr=DEVNULL
).split()
except subprocess.CalledProcessError:
return None
@@ -30,14 +29,15 @@ def match(command, settings):
def get_new_command(command, settings):
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
elif __command_available('yaourt'):
elif which('yaourt'):
pacman = 'yaourt'
elif __command_available('pacman'):
elif which('pacman'):
pacman = 'sudo pacman'
else:
enabled_by_default = False

View File

@@ -0,0 +1,14 @@
# Appends .py when executing python files
#
# Example:
# > python foo
# error: python: can't open file 'foo': [Errno 2] No such file or directory
def match(command, settings):
return (command.script.startswith('python ')
and not command.script.endswith('.py'))
def get_new_command(command, settings):
return command.script + '.py'

View File

@@ -0,0 +1,14 @@
# Fixes careless " and ' usage
#
# Example:
# > git commit -m 'My Message"
#
#
#
def match(command, settings):
return ('\'' in command.script
and '\"' in command.script)
def get_new_command(command, settings):
return command.script.replace ('\'', '\"')

View File

@@ -0,0 +1,17 @@
import shlex
from thefuck.utils import quote
def match(command, settings):
return ('sed' in command.script
and "unterminated `s' command" in command.stderr)
def get_new_command(command, settings):
script = shlex.split(command.script)
for (i, e) in enumerate(script):
if e.startswith(('s/', '-es/')) and e[-1] != '/':
script[i] += '/'
return ' '.join(map(quote, script))

View File

@@ -7,11 +7,15 @@ patterns = ['permission denied',
'root privilege',
'This command has to be run under the root user.',
'This operation requires root.',
'You need to be root to perform this command.',
'requested operation requires superuser privilege',
'must be run as root',
'must be superuser',
'Need to be root']
'must be root',
'need to be root',
'need root',
'only root can do that',
'You don\'t have access to the history DB.',
'authentication is required']
def match(command, settings):

View File

@@ -1,4 +1,6 @@
# -*- encoding: utf-8 -*-
from thefuck.shells import thefuck_alias
from thefuck.utils import memoize
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
@@ -7,6 +9,7 @@ source_layouts = [u'''йцукенгшщзхъфывапролджэячсмит
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
@memoize
def _get_matched_layout(command):
for source_layout in source_layouts:
if all([ch in source_layout or ch in '-_'
@@ -14,10 +17,6 @@ def _get_matched_layout(command):
return source_layout
def match(command, settings):
return 'not found' in command.stderr and _get_matched_layout(command)
def _switch(ch, layout):
if ch in layout:
return target_layout[layout.index(ch)]
@@ -25,7 +24,18 @@ def _switch(ch, layout):
return ch
def _switch_command(command, layout):
return ''.join(_switch(ch, layout) for ch in command.script)
def match(command, settings):
if 'not found' not in command.stderr:
return False
matched_layout = _get_matched_layout(command)
return matched_layout and \
_switch_command(command, matched_layout) != thefuck_alias()
def get_new_command(command, settings):
matched_layout = _get_matched_layout(command)
return ''.join(_switch(ch, matched_layout) for ch in command.script)
return _switch_command(command, matched_layout)

Some files were not shown because too many files have changed in this diff Show More