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

Compare commits

...

326 Commits
1.26 ... 1.47

Author SHA1 Message Date
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
nvbn
29e70e14a0 Bump to 1.37 2015-05-07 14:16:17 +02:00
nvbn
0cdd23edcf Use wheel 2015-05-07 14:16:07 +02:00
nvbn
36d80859a4 Add tox config 2015-05-07 13:51:27 +02:00
nvbn
2b12b4bfce Improve tests with mocker 2015-05-07 13:42:52 +02:00
nvbn
91c1fe414a Update thefuck-alias entry point 2015-05-07 13:32:23 +02:00
nvbn
f3d377114e Bump to 1.36 2015-05-07 13:12:25 +02:00
nvbn
05f594b918 #154 Add ability to override priority in settings 2015-05-07 13:11:45 +02:00
nvbn
5bf1424613 #164 Decrease priority of no_command 2015-05-07 12:57:43 +02:00
nvbn
fc3fcf028a #154 Add priority to rules 2015-05-06 13:57:09 +02:00
nvbn
5864faadef #165 fix python 2 support 2015-05-06 13:17:14 +02:00
Vladimir Iakovlev
608d48e408 Merge pull request #166 from mcarton/git-add
Add a git_add rule
2015-05-06 13:10:23 +02:00
mcarton
9380eb1f56 Add a git_add rule 2015-05-06 11:31:31 +02:00
Vladimir Iakovlev
fb069b74d7 Merge pull request #159 from mcarton/master
Add a rule for pacman
2015-05-05 13:30:03 +02:00
mcarton
6624ecb3b8 Add a rule for pacman 2015-05-05 11:13:29 +02:00
nvbn
a8ff2375c0 Bump to 1.35 2015-05-04 05:01:56 +02:00
Vladimir Iakovlev
80bfbec422 Update README.md 2015-05-04 05:00:11 +02:00
nvbn
3f2fe0d275 #89 #152 Use shell history 2015-05-04 04:44:16 +02:00
nvbn
72ac9650f9 Bump to 1.34 2015-05-03 13:25:01 +02:00
nvbn
93c90d5758 #157 Don't fail if can't get brew commands 2015-05-03 13:24:33 +02:00
nvbn
3ce8c1187c Make thefuck-alias depends on current shell 2015-05-03 13:04:33 +02:00
nvbn
bcd3154121 Bump to 1.33 2015-05-03 12:59:37 +02:00
nvbn
fcc2a1a40a #128 #69 add support of shell specific actions, add alias expansion for bash and zsh 2015-05-03 12:46:01 +02:00
nvbn
938f1df035 Remove not used fixture 2015-05-02 04:56:23 +02:00
nvbn
2acfea3350 #1 s/last_script/last_command/, s/last_fixed_script/last_fixed_command/ 2015-05-02 04:32:07 +02:00
nvbn
dd1861955c Refine tests 2015-05-02 04:29:55 +02:00
nvbn
ba601644d6 #1 Add history of last commands, allow fuck more than once 2015-05-01 08:38:38 +02:00
nvbn
fb7376f5a5 Bump to 1.32 2015-05-01 04:47:25 +02:00
nvbn
ee5c40d427 Update rules list in readme 2015-05-01 04:46:58 +02:00
nvbn
9a43ba6e24 #102 Update readme 2015-05-01 04:43:55 +02:00
nvbn
5eeb9d704c #102 Use side_effect in ssh_known_host rule 2015-05-01 04:41:33 +02:00
nvbn
b985dfbffc #102 Add support of rules with side effects 2015-05-01 04:39:37 +02:00
Vladimir Iakovlev
b928a59672 Merge pull request #150 from SanketDG/add-alias
Add thefuck-alias for outputting the alias command.
2015-04-30 20:53:22 +02:00
SanketDG
32fd929e48 add instructions to use thefuck-alias 2015-05-01 00:13:08 +05:30
SanketDG
8a49b40f6a add entry point 2015-05-01 00:12:43 +05:30
SanketDG
4276e1b991 add alias function 2015-05-01 00:12:30 +05:30
nvbn
6372674351 Merge branch 'SanketDG-sudo-shutdown' 2015-04-30 19:57:01 +02:00
nvbn
9f9c5369ec Merge branch 'sudo-shutdown' of https://github.com/SanketDG/thefuck into SanketDG-sudo-shutdown
Conflicts:
	thefuck/rules/sudo.py
2015-04-30 19:56:45 +02:00
Vladimir Iakovlev
50ab7429d9 Merge pull request #148 from danybony/patch-1
Add more patterns to sudo.py
2015-04-30 19:50:59 +02:00
SanketDG
55cfdda203 add rule for shutdown command 2015-04-30 19:50:37 +05:30
Daniele
be9446635b Add more patterns to sudo.py
These patterns cover commands like
`reboot`
or
`dpkg-reconfigure something`
2015-04-30 13:54:02 +01:00
Vladimir Iakovlev
b4cbcd7a99 Merge pull request #146 from kimtree/brew-improve
Improve a logic to get recommended command based on local environment
2015-04-29 08:48:20 +02:00
Namwoo Kim
9bf910a2dd Improve a logic to get recommended command based on local environment 2015-04-29 15:18:48 +09:00
Vladimir Iakovlev
7e76ab1dc6 Fix typo 2015-04-29 05:06:30 +02:00
nvbn
b80f3ea6e4 Bump to 1.31 2015-04-29 05:02:32 +02:00
nvbn
99d9c9aff7 #132 Merge pip_install_sudo rule with sudo rule 2015-04-29 05:01:30 +02:00
nvbn
4be60c78fa Merge branch 'pip-needs-sudo' of https://github.com/kimtree/thefuck into kimtree-pip-needs-sudo 2015-04-29 04:52:09 +02:00
Vladimir Iakovlev
847b9e2cec Merge pull request #143 from SanketDG/readme-fix
add version info to requirements
2015-04-29 04:48:48 +02:00
Vladimir Iakovlev
c463fea8a0 #145 add config examples 2015-04-29 04:47:15 +02:00
SanketDG
7652884df6 change requirements 2015-04-29 00:14:23 +05:30
Vladimir Iakovlev
e1ca120eb8 Merge pull request #137 from Sclarki/patch-1
Added dpkg rule
2015-04-28 20:32:41 +02:00
Trace
d3295e6a4e Hoping this fixes the issue of it not working.
Still new to this project. My problem of it not working (I admit to being at fault) was that I had not added the same rule to the test case.
2015-04-28 11:59:15 -05:00
Namwoo Kim
bc2c87e8fe Add a support for pip install with sudo
- Fixes #136
2015-04-28 15:52:09 +09:00
Trace
a2ac15da56 Added dpkg rule
For example, when using ```dpkg -i some-pkg.deb```
2015-04-27 17:45:59 -05:00
Vladimir Iakovlev
4cf631fa47 Fix links to wiki 2015-04-27 07:56:17 +02:00
Vladimir Iakovlev
cbf6507e1e Move aliases for specific shells to wiki 2015-04-27 07:54:40 +02:00
Vladimir Iakovlev
49ed98c8a4 Move os specific installation to wiki 2015-04-27 07:46:15 +02:00
Vladimir Iakovlev
52bf3907a1 Merge pull request #134 from firstdoit/patch-2
Fix typo: unoffical -> unofficial
2015-04-27 07:30:14 +02:00
Guilherme Rodrigues
077de17d6c Fix typo: unoffical -> unofficial 2015-04-26 20:35:04 -03:00
nvbn
419878f526 #118 Make ls_lah disabled by default 2015-04-25 03:42:36 +02:00
nvbn
f610cf2256 Merge branch 'ls_lah' of git://github.com/crimsonknave/thefuck into crimsonknave-ls_lah 2015-04-25 03:35:53 +02:00
nvbn
2f04a953ba Fix tests with python 2 2015-04-25 03:13:57 +02:00
nvbn
698451f65d Use parametrized tests where it possible 2015-04-25 02:54:39 +02:00
nvbn
b7cb407637 Add useful constructors for Rule and Command for tests 2015-04-25 02:35:26 +02:00
Vladimir Iakovlev
bb6b9a638c #124 Add note that brew package isn't offical 2015-04-25 02:11:07 +02:00
nvbn
0009fb0588 Bump to 1.30 2015-04-25 02:04:38 +02:00
Vladimir Iakovlev
a9d3456e29 Merge pull request #123 from nvbn/revert-117-master
Revert "Fixing fish shell example in README.md"
2015-04-24 18:25:05 +02:00
Vladimir Iakovlev
1e28671934 Revert "Fixing fish shell example in README.md" 2015-04-24 18:24:46 +02:00
Vladimir Iakovlev
3134a60e27 Merge pull request #120 from nwinkler/cd_mkdir
Added cd_mkdir rule
2015-04-24 18:23:22 +02:00
Vladimir Iakovlev
03dd7eda04 Merge pull request #119 from scorphus/initialize-settings
conf: initialize a settings file if it doesn't exist (fix #111)
2015-04-24 18:22:53 +02:00
Nils Winkler
d12a8bcdd8 Added cd_mkdir rule
This fixes #50 and #98.

```bash
$ cd foo/bar/baz
cd: foo: No such file or directory
$ fuck
mkdir -p foo/bar/baz && cd foo/bar/baz
```

Added matchers for both Bash and sh error messages. Depending on your
default shell, the messages might be slightly different.
2015-04-24 08:52:39 +02:00
Pablo Santiago Blum de Aguiar
58069f0a3e conf: initialize a settings file if it doesn't exist (fix #111)
Signed-off-by: Pablo Santiago Blum de Aguiar <scorphus@gmail.com>
2015-04-24 00:38:59 -03:00
nvbn
d0e02bc20c Merge branch 'master' of github.com:nvbn/thefuck 2015-04-24 05:23:04 +02:00
nvbn
e554238996 #78 Disable when can't import CommandNotFound 2015-04-24 05:22:19 +02:00
nvbn
fa465620ba Merge branch 'master' of git://github.com/luv/thefuck 2015-04-24 05:13:37 +02:00
Vladimir Iakovlev
294ba07ce1 Merge pull request #117 from J3RN/master
Fixing fish shell example in README.md
2015-04-24 04:37:27 +02:00
Joseph Henrich
e21befbcc4 add ls_lah to readme 2015-04-23 20:33:38 -04:00
Joseph Henrich
a454438939 Fuck, I wanted ls -lah 2015-04-23 20:28:19 -04:00
Jonathan Arnett
1fa7827f1a Fixing fish shell example in README.md
For me, `$history[1]` is the currently running command, so for the last one you want `$history[2]`
2015-04-23 18:35:18 -04:00
nvbn
48ec853436 Bump to 1.29 2015-04-23 21:50:38 +02:00
nvbn
5a8d889dc0 Merge branch 'master' of github.com:nvbn/thefuck 2015-04-23 21:48:05 +02:00
nvbn
1f96faef2c #116 Fix tests 2015-04-23 21:47:46 +02:00
nvbn
0235c0654d Merge branch 'master' of git://github.com/neomede/thefuck into neomede-master 2015-04-23 21:45:26 +02:00
Vladimir Iakovlev
f286033f82 Merge pull request #114 from nwinkler/fix-alt-space
Added rule for fixing Alt+Space character
2015-04-23 18:07:44 +02:00
Rubén Simón Andreo
473f5e6a33 Add composer rule 2015-04-23 17:34:34 +02:00
Nils Winkler
f1cce413b3 Added rule for fixing Alt+Space character
Happens on the Mac a lot when typing a pipe character (Alt+7), and
keeping the Alt key pressed down for a bit too long, so instead of
Space, you're typing Alt+Space. This rule replaces the Alt+Space with a
simple Space character.

$ ps -ef | grep foo
-bash:  grep: command not found
$ fuck
ps -ef | grep foo
2015-04-23 15:19:30 +02:00
Vladimir Iakovlev
ee2b208adf Merge pull request #112 from nwinkler/eval-alias
Using eval for Bash alias
2015-04-23 15:06:23 +02:00
Vladimir Iakovlev
a20bf6fa23 Merge pull request #110 from kimtree/support-brew
Support brew unknown command
2015-04-23 15:04:18 +02:00
Vladimir Iakovlev
da050f0db3 Merge pull request #109 from bethrezen/patch-1
MacOSX specific message
2015-04-23 15:03:20 +02:00
Vladimir Iakovlev
f5e9124327 Merge pull request #107 from kimtree/support-pip
Add a support for pip unknown commands
2015-04-23 15:02:56 +02:00
Vladimir Iakovlev
1f38e0a932 Merge pull request #106 from Brobin/master
New rule: sl -> ls
2015-04-23 15:01:57 +02:00
Nils Winkler
380827d1d9 Using eval for Bash alias
This fixes #108.
2015-04-23 11:26:19 +02:00
Namwoo Kim
54b5cd6122 Add a support for brew unavailable formulas 2015-04-23 18:16:36 +09:00
Namwoo Kim
9611264210 Update README.md 2015-04-23 17:06:36 +09:00
Namwoo Kim
24ce459f2c Add a support for unknown brew commands - #83 2015-04-23 17:06:05 +09:00
Alexander Kozhevnikov
07b9aba0d0 MacOSX specific message
Patch for understanding macosx message.
Example case:
```
[10:24:48][bethrezen@bethrezen-mac ~]$ apachectl graceful
This operation requires root.
[10:24:54][bethrezen@bethrezen-mac ~]$ fuck
No fuck given
```
2015-04-23 10:29:34 +03:00
Namwoo Kim
bb42780ca5 Update README.md and remove whitespaces 2015-04-23 16:05:02 +09:00
Namwoo Kim
af2bfe7c58 Add a support for pip unknown commands 2015-04-23 15:25:12 +09:00
Brobin
157e3e95fc added sl_ls test :shipit: 2015-04-22 20:51:18 -05:00
Brobin
776ff4e3db updated readme for sl_ls 2015-04-22 20:45:12 -05:00
Brobin
5de020bccd unf*ck sl -> ls 2015-04-22 20:41:56 -05:00
nvbn
0272e8a801 Bump to 1.28 2015-04-22 23:37:02 +02:00
nvbn
2e652112ff Merge branch 'master' of github.com:nvbn/thefuck 2015-04-22 23:36:43 +02:00
Vladimir Iakovlev
12eab10028 Update README.md 2015-04-22 23:08:10 +02:00
Vladimir Iakovlev
61eab83789 Merge pull request #101 from scott-abernethy/brew-install
Brew installation note in README
2015-04-22 23:05:25 +02:00
nvbn
d3d1f99232 Move special data types to types 2015-04-22 23:04:22 +02:00
Scott Abernethy
ca67080bd9 Brew installation note in README 2015-04-23 09:00:18 +12:00
nvbn
54c408a6b5 Rename DEFAULT to DEFAULT_RULES 2015-04-22 22:37:11 +02:00
nvbn
20b6c4c160 Inherit RulesNamesList from list 2015-04-22 22:36:18 +02:00
nvbn
0553d57ec1 Don't mess with inheritance for filling settings 2015-04-22 22:29:23 +02:00
Vladimir Iakovlev
e046d55de8 Merge pull request #99 from timofurrer/master
fix rm dir rule to make it case insensitive
2015-04-22 20:20:00 +02:00
nvbn
69a9516477 Add ability to change settings via environment variables 2015-04-22 20:18:53 +02:00
Timo Furrer
c788dfbc14 fix rm dir rule to make it case insensitive
In bash the output for the command `rm -f foo/` is:

    rm: cannot remove ‘foo/’: Is a directory

And not:

    rm: cannot remove ‘foo/’: is a directory
2015-04-22 19:04:52 +02:00
Dugucloud
fc364b99b9 Revert "Added colorama in requirements.txt"
This reverts commit 742f6f9c94.
2015-04-22 23:18:11 +08:00
nvbn
b4b599df80 Update readme 2015-04-22 16:52:09 +02:00
nvbn
69ddd82bae Bump to 1.27 2015-04-22 16:46:06 +02:00
nvbn
e7b78205f4 Add transparent sudo support for rules where it required 2015-04-22 16:45:38 +02:00
nvbn
7010b3a7f6 #43 Add test for rm_root 2015-04-22 16:22:10 +02:00
nvbn
3a9c2cc204 Merge branch 'SpyCheese-patch-1' 2015-04-22 16:09:09 +02:00
nvbn
fa4e4522b7 #43 Add rm_root as disabled by default rule 2015-04-22 16:08:54 +02:00
nvbn
14ef5c7d1c Merge branch 'patch-1' of git://github.com/SpyCheese/thefuck into SpyCheese-patch-1 2015-04-22 16:03:20 +02:00
nvbn
957209bdb6 Add ability to bundle disabled by default rules 2015-04-22 15:59:44 +02: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
nvbn
8376fed459 Merge branch 'master' of github.com:nvbn/thefuck 2015-04-22 06:03:34 +02:00
nvbn
5d424dad88 Use colorama for colored output 2015-04-22 06:03:06 +02:00
nvbn
126194ec2e Put errors in stderr instead of "echo ..." in stdout 2015-04-22 05:29:44 +02:00
Vladimir Iakovlev
6b54a3a072 Merge pull request #88 from Dugucloud/master
Added sudo rule for Fedora yum's output.
2015-04-22 05:15:24 +02:00
Dugucloud
79fb7c987c Added sudo rule for Fedora yum's output. 2015-04-22 09:26:45 +08:00
秋纫
d2356c570e Merge pull request #1 from nvbn/master
Synchronize with nvbn's repo.
2015-04-22 09:23:20 +08:00
Lukas Vacek
93b6a623e1 adding rule to run "sudo apt-get install" 2015-04-21 17:59:44 +02:00
SpyCheese
ceeccf1cd7 Update rm_root.py
Okay, there was an incorrect match function.
2015-04-19 10:21:46 +05:00
SpyCheese
f113bae59d Update rm_root.py 2015-04-19 09:12:19 +05:00
SpyCheese
2a79a5e413 Create rm_root.py 2015-04-19 09:03:34 +05:00
121 changed files with 3310 additions and 277 deletions

View File

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

155
README.md
View File

@@ -1,5 +1,7 @@
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck)
**Aliases changed in 1.34.**
Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
@@ -71,7 +73,7 @@ REPL-y 0.3.1
...
```
If you are scared to blindly run changed command, there's `require_confirmation`
If you are scared to blindly run the changed command, there is a `require_confirmation`
[settings](#settings) option:
```bash
@@ -88,8 +90,8 @@ Reading package lists... Done
## Requirements
- python (2.7+ or 3.3+)
- pip
- python
- python-dev
## Installation
@@ -100,41 +102,37 @@ Install `The Fuck` with `pip`:
sudo pip install thefuck
```
If it fails try to use `easy_install`:
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
And add to the `.bashrc` or `.bash_profile`(for OSX):
```bash
sudo easy_install thefuck
```
And add to `.bashrc` or `.zshrc` or `.bash_profile`(for OSX):
```bash
alias fuck='$(thefuck $(fc -ln -1))'
# You can use whatever you want as an alias, like for mondays:
alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
# You can use whatever you want as an alias, like for Mondays:
alias FUCK='fuck'
```
Or in `config.fish`:
Or in your `.zshrc`:
```fish
function fuck
eval (thefuck $history[1])
end
```bash
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
```
Or in your Powershell `$PROFILE` on Windows:
```powershell
function fuck {
$fuck = $(thefuck (get-history -count 1).commandline)
if($fuck.startswith("echo")) {
$fuck.substring(5)
}
else { iex "$fuck" }
}
If you are using `tcsh`:
```tcsh
alias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'
```
Alternatively, you can redirect the output of `thefuck-alias`:
```bash
thefuck-alias >> ~/.bashrc
```
[Or in your shell config (Bash, Zsh, Fish, Powershell).](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
@@ -145,21 +143,68 @@ sudo pip install thefuck --upgrade
## 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:
* `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; add 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; fix 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_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; creates the branch before checking-out;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_no_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_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;
* `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; change manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `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;
* `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` &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.
* `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`;
* `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:
* `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.
## Creating your own rules
@@ -167,10 +212,13 @@ 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`.
`Command` has three attributes: `script`, `stdout` and `stderr`.
`Settings` is `~/.thefuck/settings.py`.
`Settings` is a special object filled with `~/.thefuck/settings.py` and values from env, [more](#settings).
Simple example of the rule for running script with `sudo`:
@@ -182,6 +230,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
```
[More examples of rules](https://github.com/nvbn/thefuck/tree/master/thefuck/rules),
@@ -189,11 +245,42 @@ def get_new_command(command, settings):
## 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 all;
* `require_confirmation` &ndash; require confirmation before running new command, by default `False`;
* `wait_command` &ndash; max amount of time in seconds for getting previous command output.
* `rules` &ndash; list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` &ndash; requires confirmation before running new command, by default `False`;
* `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.
Example of `settings.py`:
```python
rules = ['sudo', 'no_command']
require_confirmation = True
wait_command = 10
no_colors = False
priority = {'sudo': 100, 'no_command': 9999}
```
Or via environment variables:
* `THEFUCK_RULES` &ndash; list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
* `THEFUCK_REQUIRE_CONFIRMATION` &ndash; require confirmation before running new command, `true/false`;
* `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.
For example:
```bash
export THEFUCK_RULES='sudo:no_command'
export THEFUCK_REQUIRE_CONFIRMATION='true'
export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
```
## Developing

View File

@@ -28,4 +28,4 @@ call('git commit -am "Bump to {}"'.format(version), shell=True)
call('git tag {}'.format(version), shell=True)
call('git push', shell=True)
call('git push --tags', shell=True)
call('python setup.py sdist upload', shell=True)
call('python setup.py sdist bdist_wheel upload', shell=True)

View File

@@ -1,2 +1,4 @@
pytest
mock
pytest-mock
wheel

2
setup.cfg Normal file
View File

@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 1

21
setup.py Normal file → Executable file
View File

@@ -1,8 +1,22 @@
#!/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.26'
VERSION = '1.47'
install_requires = ['psutil', 'colorama', 'six']
if sys.version_info < (3, 4):
install_requires.append('pathlib')
setup(name='thefuck',
version=VERSION,
@@ -15,6 +29,7 @@ setup(name='thefuck',
'tests', 'release']),
include_package_data=True,
zip_safe=False,
install_requires=['pathlib', 'psutil'],
install_requires=install_requires,
entry_points={'console_scripts': [
'thefuck = thefuck.main:main']})
'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.shells:app_alias']})

0
tests/__init__.py Normal file
View File

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,49 @@
import pytest
from thefuck.rules.brew_install import match, get_new_command
from thefuck.rules.brew_install import brew_formulas
from tests.utils import Command
@pytest.fixture
def brew_no_available_formula():
return '''Error: No available formula for elsticsearch '''
@pytest.fixture
def brew_install_no_argument():
return '''This command requires a formula argument'''
@pytest.fixture
def brew_already_installed():
return '''Warning: git-2.3.5 already installed'''
def _is_not_okay_to_test():
if 'elasticsearch' not in brew_formulas:
return True
return False
@pytest.mark.skipif(_is_not_okay_to_test(),
reason='No need to run if there\'s no formula')
def test_match(brew_no_available_formula, brew_already_installed,
brew_install_no_argument):
assert match(Command('brew install elsticsearch',
stderr=brew_no_available_formula), None)
assert not match(Command('brew install git',
stderr=brew_already_installed), None)
assert not match(Command('brew install', stderr=brew_install_no_argument),
None)
@pytest.mark.skipif(_is_not_okay_to_test(),
reason='No need to run if there\'s no formula')
def test_get_new_command(brew_no_available_formula):
assert get_new_command(Command('brew install elsticsearch',
stderr=brew_no_available_formula), None)\
== 'brew install elasticsearch'
assert get_new_command(Command('brew install aa',
stderr=brew_no_available_formula),
None) != 'brew install aha'

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.rules.brew_unknown_command import match, get_new_command
from thefuck.rules.brew_unknown_command import brew_commands
from tests.utils import Command
@pytest.fixture
def brew_unknown_cmd():
return '''Error: Unknown command: inst'''
@pytest.fixture
def brew_unknown_cmd2():
return '''Error: Unknown command: instaa'''
def test_match(brew_unknown_cmd):
assert match(Command('brew inst', stderr=brew_unknown_cmd), None)
for command in brew_commands:
assert not match(Command('brew ' + command), None)
def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2):
assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd),
None) == 'brew list'
assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2),
None) == 'brew install'

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

@@ -0,0 +1,25 @@
import pytest
from thefuck.rules.cd_mkdir import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='cd foo', stderr='cd: foo: No such file or directory'),
Command(script='cd foo/bar/baz',
stderr='cd: foo: No such file or directory'),
Command(script='cd foo/bar/baz', stderr='cd: can\'t cd to foo/bar/baz')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='cd foo', stderr=''), Command()])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('cd foo'), 'mkdir -p foo && cd foo'),
(Command('cd foo/bar/baz'), 'mkdir -p foo/bar/baz && cd foo/bar/baz')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -1,12 +1,12 @@
from thefuck.main import Command
from thefuck.rules.cd_parent import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('cd..', '', 'cd..: command not found'), None)
assert not match(Command('', '', ''), None)
assert match(Command('cd..', stderr='cd..: command not found'), None)
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(
Command('cd..', '', ''), None) == 'cd ..'
Command('cd..'), None) == 'cd ..'

View File

@@ -0,0 +1,53 @@
import pytest
from thefuck.rules.composer_not_command import match, get_new_command
from tests.utils import Command
@pytest.fixture
def composer_not_command():
return """
[InvalidArgumentException]
Command "udpate" is not defined.
Did you mean this?
update
"""
@pytest.fixture
def composer_not_command_one_of_this():
return """
[InvalidArgumentException]
Command "pdate" is not defined.
Did you mean one of these?
selfupdate
self-update
update
"""
def test_match(composer_not_command, composer_not_command_one_of_this):
assert match(Command('composer udpate',
stderr=composer_not_command), None)
assert match(Command('composer pdate',
stderr=composer_not_command_one_of_this), None)
assert not match(Command('ls update', stderr=composer_not_command),
None)
def test_get_new_command(composer_not_command, composer_not_command_one_of_this):
assert get_new_command(Command('composer udpate',
stderr=composer_not_command), None) \
== 'composer update'
assert get_new_command(
Command('composer pdate', stderr=composer_not_command_one_of_this),
None) == 'composer selfupdate'

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,22 @@
# -*- encoding: utf-8 -*-
from thefuck.rules.fix_alt_space import match, get_new_command
from tests.utils import Command
def test_match():
"""The character before 'grep' is Alt+Space, which happens frequently
on the Mac when typing the pipe character (Alt+7), and holding the Alt
key pressed for longer than necessary.
"""
assert match(Command(u'ps -ef | grep foo',
stderr=u'-bash:  grep: command not found'), None)
assert not match(Command('ps -ef | grep foo'), None)
assert not match(Command(), None)
def test_get_new_command():
""" Replace the Alt+Space character by a simple space """
assert get_new_command(Command(u'ps -ef | grep foo'), None)\
== 'ps -ef | grep foo'

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

View File

@@ -0,0 +1,27 @@
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'),
Command(script='git df'),
Command(script='git ds')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
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 df'), 'git df --staged'),
(Command('git ds'), 'git ds --staged')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -1,6 +1,6 @@
import pytest
from thefuck.main import Command
from thefuck.rules.git_not_command import match, get_new_command
from tests.utils import Command
@pytest.fixture
@@ -31,14 +31,14 @@ def git_command():
def test_match(git_not_command, git_command, git_not_command_one_of_this):
assert match(Command('git brnch', '', git_not_command), None)
assert match(Command('git st', '', git_not_command_one_of_this), None)
assert not match(Command('ls brnch', '', git_not_command), None)
assert not match(Command('git branch', '', git_command), None)
assert match(Command('git brnch', stderr=git_not_command), None)
assert match(Command('git st', stderr=git_not_command_one_of_this), None)
assert not match(Command('ls brnch', stderr=git_not_command), None)
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', '', git_not_command), None)\
assert get_new_command(Command('git brnch', stderr=git_not_command), None)\
== 'git branch'
assert get_new_command(
Command('git st', '', git_not_command_one_of_this), None) == 'git status'
assert get_new_command(Command('git st', stderr=git_not_command_one_of_this),
None) == 'git status'

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

@@ -1,6 +1,6 @@
import pytest
from thefuck.main import Command
from thefuck.rules.git_push import match, get_new_command
from tests.utils import Command
@pytest.fixture
@@ -14,11 +14,11 @@ To push the current branch and set the remote as upstream, use
def test_match(stderr):
assert match(Command('git push master', '', stderr), None)
assert not match(Command('git push master', '', ''), None)
assert not match(Command('ls', '', stderr), None)
assert match(Command('git push master', stderr=stderr), None)
assert not match(Command('git push master'), None)
assert not match(Command('ls', stderr=stderr), None)
def test_get_new_command(stderr):
assert get_new_command(Command('', '', stderr), None)\
assert get_new_command(Command(stderr=stderr), None)\
== "git push --set-upstream origin master"

View File

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

View File

@@ -0,0 +1,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

@@ -1,5 +1,5 @@
from mock import Mock, patch
from thefuck.rules. has_exists_script import match, get_new_command
from thefuck.rules.has_exists_script import match, get_new_command
def test_match():

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

@@ -0,0 +1,16 @@
from mock import patch, Mock
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():
assert get_new_command(Mock(script='ls file.py'), None) == 'ls -lah file.py'
assert get_new_command(Mock(script='ls'), None) == 'ls -lah'

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,13 +1,22 @@
from thefuck.main import Command
import pytest
from thefuck.rules.mkdir_p import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('mkdir foo/bar/baz', '', 'mkdir: foo/bar: No such file or directory'), None)
assert not match(Command('mkdir foo/bar/baz', '', ''), None)
assert not match(Command('mkdir foo/bar/baz', '', 'foo bar baz'), None)
assert not match(Command('', '', ''), None)
assert match(Command('mkdir foo/bar/baz',
stderr='mkdir: foo/bar: No such file or directory'),
None)
@pytest.mark.parametrize('command', [
Command('mkdir foo/bar/baz'),
Command('mkdir foo/bar/baz', stderr='foo bar baz'),
Command()])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():
assert get_new_command(Command('mkdir foo/bar/baz', '', ''), None) == 'mkdir -p foo/bar/baz'
assert get_new_command(Command('mkdir foo/bar/baz'), None)\
== 'mkdir -p foo/bar/baz'

View File

@@ -1,19 +1,36 @@
from mock import patch, Mock
from thefuck.rules.no_command import match, get_new_command
from thefuck.rules.no_command import match, get_new_command, _get_all_callables
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)
@patch('thefuck.rules.no_command._safe', return_value=[])
@patch('thefuck.rules.no_command.get_aliases',
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
def test_get_all_callables(*args):
all_callables = _get_all_callables()
assert 'vim' in all_callables
assert 'fsck' in all_callables
assert 'fuck' not in all_callables
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'
@patch('thefuck.rules.no_command._safe', return_value=[])
@patch('thefuck.rules.no_command.get_aliases',
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
def test_match(*args):
assert match(Mock(stderr='vom: not found', script='vom file.py'), None)
assert match(Mock(stderr='fucck: not found', script='fucck'), None)
assert not match(Mock(stderr='qweqwe: not found', script='qweqwe'), None)
assert not match(Mock(stderr='some text', script='vom file.py'), None)
@patch('thefuck.rules.no_command._safe', return_value=[])
@patch('thefuck.rules.no_command.get_aliases',
return_value=['vim', 'apt-get', 'fsck', 'fuck'])
def test_get_new_command(*args):
assert get_new_command(
Mock(stderr='vom: not found',
script='vom file.py'),
None) == 'vim file.py'
assert get_new_command(
Mock(stderr='fucck: not found',
script='fucck'),
None) == '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,25 @@
import pytest
from thefuck.rules.pip_unknown_command import match, get_new_command
from tests.utils import Command
@pytest.fixture
def pip_unknown_cmd():
return '''ERROR: unknown command "instatl" - maybe you meant "install"'''
@pytest.fixture
def pip_unknown_cmd_without_recommend():
return '''ERROR: unknown command "i"'''
def test_match(pip_unknown_cmd, pip_unknown_cmd_without_recommend):
assert match(Command('pip instatl', stderr=pip_unknown_cmd), None)
assert not match(Command('pip i',
stderr=pip_unknown_cmd_without_recommend),
None)
def test_get_new_command(pip_unknown_cmd):
assert get_new_command(Command('pip instatl', stderr=pip_unknown_cmd),
None) == 'pip install'

View File

@@ -1,9 +1,12 @@
from thefuck.main import Command
from thefuck.rules.python_command import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('temp.py', '', 'Permission denied'), None)
assert not match(Command('', '', ''), None)
assert match(Command('temp.py', stderr='Permission denied'), None)
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(Command('./test_sudo.py', '', ''), None) == 'python ./test_sudo.py'
assert get_new_command(Command('./test_sudo.py'), None)\
== 'python ./test_sudo.py'

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

@@ -1,12 +1,20 @@
from thefuck.main import Command
import pytest
from thefuck.rules.rm_dir import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('rm foo', '', 'rm: foo: is a directory'), None)
assert not match(Command('rm foo', '', ''), None)
assert not match(Command('rm foo', '', 'foo bar baz'), None)
assert not match(Command('', '', ''), None)
@pytest.mark.parametrize('command', [
Command('rm foo', stderr='rm: foo: is a directory'),
Command('rm foo', stderr='rm: foo: Is a directory')])
def test_match(command):
assert match(command, None)
assert match(command, None)
@pytest.mark.parametrize('command', [
Command('rm foo'), Command('rm foo'), Command()])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():

View File

@@ -0,0 +1,21 @@
import pytest
from thefuck.rules.rm_root import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command(script='rm -rf /',
stderr='add --no-preserve-root'), None)
@pytest.mark.parametrize('command', [
Command(script='ls', stderr='add --no-preserve-root'),
Command(script='rm --no-preserve-root /', stderr='add --no-preserve-root'),
Command(script='rm -rf /', stderr='')])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():
assert get_new_command(Command(script='rm -rf /'), None) \
== 'rm -rf / --no-preserve-root'

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/"

12
tests/rules/test_sl_ls.py Normal file
View File

@@ -0,0 +1,12 @@
from thefuck.rules.sl_ls import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('sl'), None)
assert not match(Command('ls'), None)
def test_get_new_command():
assert get_new_command(Command('sl'), None) == 'ls'

View File

@@ -1,8 +1,9 @@
import os
import pytest
from mock import Mock
from thefuck.main import Command
from thefuck.rules.ssh_known_hosts import match, get_new_command, remove_offending_keys
from thefuck.rules.ssh_known_hosts import match, get_new_command,\
side_effect
from tests.utils import Command
@pytest.fixture
@@ -43,27 +44,23 @@ Host key verification failed.""".format(path, '98.765.432.321')
def test_match(ssh_error):
errormsg, _, _, _ = ssh_error
assert match(Command('ssh', '', errormsg), None)
assert match(Command('ssh', '', errormsg), None)
assert match(Command('scp something something', '', errormsg), None)
assert match(Command('scp something something', '', errormsg), None)
assert not match(Command('', '', errormsg), None)
assert not match(Command('notssh', '', errormsg), None)
assert not match(Command('ssh', '', ''), None)
assert match(Command('ssh', stderr=errormsg), None)
assert match(Command('ssh', stderr=errormsg), None)
assert match(Command('scp something something', stderr=errormsg), None)
assert match(Command('scp something something', stderr=errormsg), None)
assert not match(Command(stderr=errormsg), None)
assert not match(Command('notssh', stderr=errormsg), None)
assert not match(Command('ssh'), None)
def test_remove_offending_keys(ssh_error):
def test_side_effect(ssh_error):
errormsg, path, reset, known_hosts = ssh_error
command = Command('ssh user@host', '', errormsg)
remove_offending_keys(command, None)
command = Command('ssh user@host', stderr=errormsg)
side_effect(command, None)
expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n']
assert known_hosts(path) == expected
def test_get_new_command(ssh_error, monkeypatch):
errormsg, _, _, _ = ssh_error
method = Mock()
monkeypatch.setattr('thefuck.rules.ssh_known_hosts.remove_offending_keys', method)
assert get_new_command(Command('ssh user@host', '', errormsg), None) == 'ssh user@host'
assert method.call_count
assert get_new_command(Command('ssh user@host', stderr=errormsg), None) == 'ssh user@host'

View File

@@ -1,13 +1,24 @@
from thefuck.main import Command
import pytest
from thefuck.rules.sudo import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('', '', 'Permission denied'), None)
assert match(Command('', '', 'permission denied'), None)
assert match(Command('', '', "npm ERR! Error: EACCES, unlink"), None)
assert not match(Command('', '', ''), None)
@pytest.mark.parametrize('stderr, stdout', [
('Permission denied', ''),
('permission denied', ''),
("npm ERR! Error: EACCES, unlink", ''),
('requested operation requires superuser privilege', ''),
('need to be root', ''),
('need root', ''),
('must be root', ''),
('', "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)
def test_not_match():
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(Command('ls', '', ''), None) == 'sudo ls'
assert get_new_command(Command('ls'), None) == 'sudo ls'

View File

@@ -1,25 +1,27 @@
# -*- encoding: utf-8 -*-
from mock import Mock
import pytest
from thefuck.rules import switch_lang
from tests.utils import Command
def test_match():
assert switch_lang.match(Mock(stderr='command not found: фзе-пуе',
script=u'фзе-пуе'), None)
assert switch_lang.match(Mock(stderr='command not found: λσ',
script=u'λσ'), None)
assert not switch_lang.match(Mock(stderr='command not found: pat-get',
script=u'pat-get'), None)
assert not switch_lang.match(Mock(stderr='command not found: ls',
script=u'ls'), None)
assert not switch_lang.match(Mock(stderr='some info',
script=u'фзе-пуе'), None)
@pytest.mark.parametrize('command', [
Command(stderr='command not found: фзе-пуе', script=u'фзе-пуе'),
Command(stderr='command not found: λσ', script=u'λσ')])
def test_match(command):
assert switch_lang.match(command, None)
def test_get_new_command():
assert switch_lang.get_new_command(
Mock(script=u'фзе-пуе штыефдд мшь'), None) == 'apt-get install vim'
assert switch_lang.get_new_command(
Mock(script=u'λσ -λα'), None) == 'ls -la'
@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='some info', script=u'фзе-пуе')])
def test_not_match(command):
assert not switch_lang.match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(u'фзе-пуе штыефдд мшь'), 'apt-get install vim'),
(Command(u'λσ -λα'), 'ls -la')])
def test_get_new_command(command, new_command):
assert switch_lang.get_new_command(command, None) == new_command

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

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

102
tests/test_conf.py Normal file
View File

@@ -0,0 +1,102 @@
import pytest
import six
from mock import Mock
from thefuck import conf
from tests.utils import Rule
@pytest.mark.parametrize('enabled, rules, result', [
(True, conf.DEFAULT_RULES, True),
(False, conf.DEFAULT_RULES, False),
(False, conf.DEFAULT_RULES + ['test'], True)])
def test_default(enabled, rules, result):
assert (Rule('test', enabled_by_default=enabled) in rules) == result
@pytest.fixture
def load_source(mocker):
return mocker.patch('thefuck.conf.load_source')
@pytest.fixture
def environ(monkeypatch):
data = {}
monkeypatch.setattr('thefuck.conf.os.environ', data)
return data
@pytest.mark.usefixture('environ')
def test_settings_defaults(load_source):
load_source.return_value = object()
for key, val in conf.DEFAULT_SETTINGS.items():
assert getattr(conf.get_settings(Mock()), key) == val
@pytest.mark.usefixture('environ')
class TestSettingsFromFile(object):
def test_from_file(self, load_source):
load_source.return_value = Mock(rules=['test'],
wait_command=10,
require_confirmation=True,
no_colors=True,
priority={'vim': 100})
settings = conf.get_settings(Mock())
assert settings.rules == ['test']
assert settings.wait_command == 10
assert settings.require_confirmation is True
assert settings.no_colors is True
assert settings.priority == {'vim': 100}
def test_from_file_with_DEFAULT(self, load_source):
load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'],
wait_command=10,
require_confirmation=True,
no_colors=True)
settings = conf.get_settings(Mock())
assert settings.rules == conf.DEFAULT_RULES + ['test']
@pytest.mark.usefixture('load_source')
class TestSettingsFromEnv(object):
def test_from_env(self, environ):
environ.update({'THEFUCK_RULES': 'bash:lisp',
'THEFUCK_WAIT_COMMAND': '55',
'THEFUCK_REQUIRE_CONFIRMATION': 'true',
'THEFUCK_NO_COLORS': 'false',
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15'})
settings = conf.get_settings(Mock())
assert settings.rules == ['bash', 'lisp']
assert settings.wait_command == 55
assert settings.require_confirmation is True
assert settings.no_colors is False
assert settings.priority == {'bash': 10, 'vim': 15}
def test_from_env_with_DEFAULT(self, environ):
environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
settings = conf.get_settings(Mock())
assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp']
class TestInitializeSettingsFile(object):
def test_ignore_if_exists(self):
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
assert settings_path_mock.is_file.call_count == 1
assert not settings_path_mock.open.called
def test_create_if_doesnt_exists(self):
settings_file = six.StringIO()
settings_path_mock = Mock(
is_file=Mock(return_value=False),
open=Mock(return_value=Mock(
__exit__=lambda *args: None, __enter__=lambda *args: settings_file)))
user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock))
conf.initialize_settings_file(user_dir_mock)
settings_file_contents = settings_file.getvalue()
assert settings_path_mock.is_file.call_count == 1
assert settings_path_mock.open.call_count == 1
assert conf.SETTINGS_HEADER in settings_file_contents
for setting in conf.DEFAULT_SETTINGS.items():
assert '# {} = {}\n'.format(*setting) in settings_file_contents
settings_file.close()

7
tests/test_logs.py Normal file
View File

@@ -0,0 +1,7 @@
from mock import Mock
from thefuck import logs
def test_color():
assert logs.color('red', Mock(no_colors=False)) == 'red'
assert logs.color('red', Mock(no_colors=True)) == ''

View File

@@ -1,101 +1,173 @@
import pytest
from subprocess import PIPE
from pathlib import PosixPath, Path
from mock import patch, Mock
from thefuck import main
from mock import Mock
from thefuck import main, conf, types
from tests.utils import Rule, Command
def test_get_settings():
with patch('thefuck.main.load_source', return_value=Mock(rules=['bash'])):
assert main.get_settings(Path('/')).rules == ['bash']
with patch('thefuck.main.load_source', return_value=Mock(spec=[])):
assert main.get_settings(Path('/')).rules is None
def test_is_rule_enabled():
assert main.is_rule_enabled(Mock(rules=None), Path('bash.py'))
assert main.is_rule_enabled(Mock(rules=['bash']), Path('bash.py'))
assert not main.is_rule_enabled(Mock(rules=['bash']), Path('lisp.py'))
def test_load_rule():
def test_load_rule(mocker):
match = object()
get_new_command = object()
with patch('thefuck.main.load_source',
return_value=Mock(
match=match,
get_new_command=get_new_command)) as load_source:
assert main.load_rule(Path('/rules/bash.py')) == main.Rule('bash', match, get_new_command)
load_source.assert_called_once_with('bash', '/rules/bash.py')
load_source = mocker.patch(
'thefuck.main.load_source',
return_value=Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True,
priority=900))
assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py')
def test_get_rules():
with patch('thefuck.main.Path.glob') as glob, \
patch('thefuck.main.load_source',
lambda x, _: Mock(match=x, get_new_command=x)):
class TestGetRules(object):
@pytest.fixture(autouse=True)
def glob(self, mocker):
return mocker.patch('thefuck.main.Path.glob', return_value=[])
def _compare_names(self, rules, names):
return [r.name for r in rules] == names
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
(types.RulesNamesList(['bash']), ['bash', 'bash'])])
def test_get(self, monkeypatch, glob, conf_rules, rules):
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
assert main.get_rules(
Path('~'),
Mock(rules=None)) == [main.Rule('bash', 'bash', 'bash'),
main.Rule('lisp', 'lisp', 'lisp'),
main.Rule('bash', 'bash', 'bash'),
main.Rule('lisp', 'lisp', 'lisp')]
assert main.get_rules(
Path('~'),
Mock(rules=['bash'])) == [main.Rule('bash', 'bash', 'bash'),
main.Rule('bash', 'bash', 'bash')]
monkeypatch.setattr('thefuck.main.load_source',
lambda x, _: Rule(x))
assert self._compare_names(
main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
rules)
@pytest.mark.parametrize('priority, unordered, ordered', [
({},
[Rule('bash', priority=100), Rule('python', priority=5)],
['python', 'bash']),
({},
[Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)],
['c', 'lisp']),
({'python': 9999},
[Rule('bash', priority=100), Rule('python', priority=5)],
['bash', 'python'])])
def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered):
monkeypatch.setattr('thefuck.main._get_loaded_rules',
lambda *_: unordered)
assert self._compare_names(
main.get_rules(Path('~'), Mock(priority=priority)),
ordered)
def test_get_command():
with patch('thefuck.main.Popen') as Popen, \
patch('thefuck.main.os.environ',
new_callable=lambda: {}), \
patch('thefuck.main.wait_output',
return_value=True):
class TestGetCommand(object):
@pytest.fixture(autouse=True)
def Popen(self, monkeypatch):
Popen = Mock()
Popen.return_value.stdout.read.return_value = b'stdout'
Popen.return_value.stderr.read.return_value = b'stderr'
assert main.get_command(Mock(), ['thefuck', 'apt-get',
'search', 'vim']) \
== main.Command('apt-get search vim', 'stdout', 'stderr')
monkeypatch.setattr('thefuck.main.Popen', Popen)
return Popen
@pytest.fixture(autouse=True)
def prepare(self, monkeypatch):
monkeypatch.setattr('thefuck.main.os.environ', {})
monkeypatch.setattr('thefuck.main.wait_output', lambda *_: True)
@pytest.fixture(autouse=True)
def generic_shell(self, monkeypatch):
monkeypatch.setattr('thefuck.shells.from_shell', lambda x: x)
monkeypatch.setattr('thefuck.shells.to_shell', lambda x: x)
def test_get_command_calls(self, Popen):
assert main.get_command(Mock(),
['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'})
assert main.get_command(Mock(), ['']) is None
@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
else:
assert main.get_command(Mock(), args) is None
def test_get_matched_rule(capsys):
rules = [main.Rule('', lambda x, _: x.script == 'cd ..', None),
main.Rule('', lambda *_: False, None),
main.Rule('rule', Mock(side_effect=OSError('Denied')), None)]
assert main.get_matched_rule(main.Command('ls', '', ''),
rules, None) is None
assert main.get_matched_rule(main.Command('cd ..', '', ''),
rules, None) == rules[0]
assert capsys.readouterr()[1].split('\n')[0]\
== '[WARN] rule: Traceback (most recent call last):'
class TestGetMatchedRule(object):
def test_no_match(self):
assert main.get_matched_rule(
Command('ls'), [Rule('', lambda *_: False)],
Mock(no_colors=True)) is None
def test_match(self):
rule = Rule('', lambda x, _: x.script == 'cd ..')
assert main.get_matched_rule(
Command('cd ..'), [rule], Mock(no_colors=True)) == rule
def test_when_rule_failed(self, capsys):
main.get_matched_rule(
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
Mock(no_colors=True))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
def test_run_rule(capsys):
with patch('thefuck.main.confirm', return_value=True):
main.run_rule(main.Rule('', None, lambda *_: 'new-command'),
None, None)
class TestRunRule(object):
@pytest.fixture(autouse=True)
def confirm(self, mocker):
return mocker.patch('thefuck.main.confirm', return_value=True)
def test_run_rule(self, capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
Command(), None)
assert capsys.readouterr() == ('new-command\n', '')
with patch('thefuck.main.confirm', return_value=False):
main.run_rule(main.Rule('', None, lambda *_: 'new-command'),
None, None)
def test_run_rule_with_side_effect(self, capsys):
side_effect = Mock()
settings = Mock()
command = Command()
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
side_effect=side_effect),
command, settings)
assert capsys.readouterr() == ('new-command\n', '')
side_effect.assert_called_once_with(command, settings)
def test_when_not_comfirmed(self, capsys, confirm):
confirm.return_value = False
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
Command(), None)
assert capsys.readouterr() == ('', '')
def test_confirm(capsys):
# When confirmation not required:
assert main.confirm('command', Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n')
# When confirmation required and confirmed:
with patch('thefuck.main.sys.stdin.read', return_value='\n'):
assert main.confirm('command', Mock(require_confirmation=True))
assert capsys.readouterr() == ('', 'command [Enter/Ctrl+C]')
# When confirmation required and ctrl+c:
with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt):
assert not main.confirm('command', Mock(require_confirmation=True))
assert capsys.readouterr() == ('', 'command [Enter/Ctrl+C]Aborted\n')
class TestConfirm(object):
@pytest.fixture
def stdin(self, mocker):
return mocker.patch('sys.stdin.read', return_value='\n')
def test_when_not_required(self, capsys):
assert main.confirm('command', None, Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n')
def test_with_side_effect_and_without_confirmation(self, capsys):
assert main.confirm('command', Mock(), Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command*\n')
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed(self, capsys, stdin):
assert main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]')
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin):
assert main.confirm('command', Mock(), Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]')
def test_when_confirmation_required_and_aborted(self, capsys, stdin):
stdin.side_effect = KeyboardInterrupt
assert not main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n')

189
tests/test_shells.py Normal file
View File

@@ -0,0 +1,189 @@
import pytest
from thefuck import shells
@pytest.fixture
def builtins_open(mocker):
return mocker.patch('six.moves.builtins.open')
@pytest.fixture
def isfile(mocker):
return mocker.patch('os.path.isfile', return_value=True)
class TestGeneric(object):
@pytest.fixture
def shell(self):
return shells.Generic()
def test_from_shell(self, shell):
assert shell.from_shell('pwd') == 'pwd'
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()
assert 'thefuck' in shell.app_alias()
assert 'TF_ALIAS' in shell.app_alias()
@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\'')
return mock
@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, 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, 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()
assert 'thefuck' in shell.app_alias()
assert 'TF_ALIAS' in shell.app_alias()
@pytest.mark.usefixtures('isfile')
class TestFish(object):
@pytest.fixture
def shell(self):
return shells.Fish()
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen')
mock.return_value.stdout.read.return_value = (
b'fish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nmath')
return mock
@pytest.mark.parametrize('before, after', [
('pwd', 'pwd'),
('fuck', 'fish -ic "fuck"'),
('find', 'find'),
('funced', 'fish -ic "funced"'),
('awk', 'awk'),
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
('vim', 'vim'),
('ll', 'fish -ic "ll"')]) # Fish has no aliases but functions
def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after
def test_to_shell(self, shell):
assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, mocker, shell):
mocker.patch('thefuck.shells.time',
return_value=1430707243.3517463)
shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('- cmd: ls\n when: 1430707243\n')
def test_and_(self, shell):
assert shell.and_('foo', 'bar') == 'foo; and bar'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck',
'funced': 'funced',
'funcsave': 'funcsave',
'grep': 'grep',
'history': 'history',
'll': 'll',
'math': 'math'}
def test_app_alias(self, shell):
assert 'function fuck' in shell.app_alias()
assert 'thefuck' in shell.app_alias()
assert 'TF_ALIAS' in shell.app_alias()
@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, 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(': 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()
assert 'thefuck' in shell.app_alias()
assert 'TF_ALIAS' in shell.app_alias()

16
tests/test_types.py Normal file
View File

@@ -0,0 +1,16 @@
from thefuck.types import RulesNamesList, Settings
from tests.utils import Rule
def test_rules_names_list():
assert RulesNamesList(['bash', 'lisp']) == ['bash', 'lisp']
assert RulesNamesList(['bash', 'lisp']) == RulesNamesList(['bash', 'lisp'])
assert Rule('lisp') in RulesNamesList(['lisp'])
assert Rule('bash') not in RulesNamesList(['lisp'])
def test_update_settings():
settings = Settings({'key': 'val'})
new_settings = settings.update(key='new-val')
assert new_settings.key == 'new-val'
assert settings.key == 'val'

34
tests/test_utils.py Normal file
View File

@@ -0,0 +1,34 @@
import pytest
from mock import Mock
from thefuck.utils import sudo_support, wrap_settings, memoize
from thefuck.types import Settings
from tests.utils import Command
@pytest.mark.parametrize('override, old, new', [
({'key': 'val'}, {}, {'key': 'val'}),
({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})])
def test_wrap_settings(override, old, new):
fn = lambda _, settings: settings
assert wrap_settings(override)(fn)(None, Settings(old)) == new
@pytest.mark.parametrize('return_value, command, called, result', [
('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'),
('ls -lah', 'ls', 'ls', 'ls -lah'),
(True, 'sudo ls', 'ls', True),
(True, 'ls', 'ls', True),
(False, 'sudo ls', 'ls', False),
(False, 'ls', 'ls', False)])
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)
def test_memoize():
fn = Mock(__name__='fn')
memoized = memoize(fn)
memoized()
memoized()
fn.assert_called_once_with()

16
tests/utils.py Normal file
View File

@@ -0,0 +1,16 @@
from thefuck import types
from thefuck.conf import DEFAULT_PRIORITY
def Command(script='', stdout='', stderr=''):
return types.Command(script, stdout, stderr)
def Rule(name='', match=lambda *_: True,
get_new_command=lambda *_: '',
enabled_by_default=True,
side_effect=None,
priority=DEFAULT_PRIORITY):
return types.Rule(name, match, get_new_command,
enabled_by_default, side_effect,
priority)

132
thefuck/conf.py Normal file
View File

@@ -0,0 +1,132 @@
from copy import copy
from imp import load_source
import os
import sys
from six import text_type
from . import logs, types
class _DefaultRulesNames(types.RulesNamesList):
def __add__(self, items):
return _DefaultRulesNames(list(self) + items)
def __contains__(self, item):
return item.enabled_by_default or \
super(_DefaultRulesNames, self).__contains__(item)
def __eq__(self, other):
if isinstance(other, _DefaultRulesNames):
return super(_DefaultRulesNames, self).__eq__(other)
else:
return False
DEFAULT_RULES = _DefaultRulesNames([])
DEFAULT_PRIORITY = 1000
DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'wait_command': 3,
'require_confirmation': False,
'no_colors': False,
'priority': {}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_COMMAND': 'wait_command',
'THEFUCK_REQUIRE_CONFIRMATION': 'require_confirmation',
'THEFUCK_NO_COLORS': 'no_colors',
'THEFUCK_PRIORITY': 'priority'}
SETTINGS_HEADER = u"""# ~/.thefuck/settings.py: The Fuck settings file
#
# The rules are defined as in the example bellow:
#
# rules = ['cd_parent', 'git_push', 'python_command', 'sudo']
#
# The default values are as follows. Uncomment and change to fit your needs.
# See https://github.com/nvbn/thefuck#settings for more information.
#
"""
def _settings_from_file(user_dir):
"""Loads settings from file."""
settings = load_source('settings',
text_type(user_dir.joinpath('settings.py')))
return {key: getattr(settings, key)
for key in DEFAULT_SETTINGS.keys()
if hasattr(settings, key)}
def _rules_from_env(val):
"""Transforms rules list from env-string to python."""
val = val.split(':')
if 'DEFAULT_RULES' in val:
val = DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES']
return val
def _priority_from_env(val):
"""Gets priority pairs from env."""
for part in val.split(':'):
try:
rule, priority = part.split('=')
yield rule, int(priority)
except ValueError:
continue
def _val_from_env(env, attr):
"""Transforms env-strings to python."""
val = os.environ[env]
if attr == 'rules':
return _rules_from_env(val)
elif attr == 'priority':
return dict(_priority_from_env(val))
elif attr == 'wait_command':
return int(val)
elif attr in ('require_confirmation', 'no_colors'):
return val.lower() == 'true'
else:
return val
def _settings_from_env():
"""Loads settings from env."""
return {attr: _val_from_env(env, attr)
for env, attr in ENV_TO_ATTR.items()
if env in os.environ}
def get_settings(user_dir):
"""Returns settings filled with values from `settings.py` and env."""
conf = copy(DEFAULT_SETTINGS)
try:
conf.update(_settings_from_file(user_dir))
except Exception:
logs.exception("Can't load settings from file",
sys.exc_info(),
types.Settings(conf))
try:
conf.update(_settings_from_env())
except Exception:
logs.exception("Can't load settings from env",
sys.exc_info(),
types.Settings(conf))
if not isinstance(conf['rules'], types.RulesNamesList):
conf['rules'] = types.RulesNamesList(conf['rules'])
return types.Settings(conf)
def initialize_settings_file(user_dir):
settings_path = user_dir.joinpath('settings.py')
if not settings_path.is_file():
with settings_path.open(mode='w') as settings_file:
settings_file.write(SETTINGS_HEADER)
for setting in DEFAULT_SETTINGS.items():
settings_file.write(u'# {} = {}\n'.format(*setting))

54
thefuck/logs.py Normal file
View File

@@ -0,0 +1,54 @@
import sys
from traceback import format_exception
import colorama
def color(color_, settings):
"""Utility for ability to disabling colored output."""
if settings.no_colors:
return ''
else:
return color_
def exception(title, exc_info, settings):
sys.stderr.write(
u'{warn}[WARN] {title}:{reset}\n{trace}'
u'{warn}----------------------------{reset}\n\n'.format(
warn=color(colorama.Back.RED + colorama.Fore.WHITE
+ colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings),
title=title,
trace=''.join(format_exception(*exc_info))))
def rule_failed(rule, exc_info, settings):
exception('Rule {}'.format(rule.name), exc_info, settings)
def show_command(new_command, side_effect, settings):
sys.stderr.write('{bold}{command}{side_effect}{reset}\n'.format(
command=new_command,
side_effect='*' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
def confirm_command(new_command, side_effect, settings):
sys.stderr.write(
'{bold}{command}{side_effect}{reset} '
'[{green}enter{reset}/{red}ctrl+c{reset}]'.format(
command=new_command,
side_effect='*' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
green=color(colorama.Fore.GREEN, settings),
red=color(colorama.Fore.RED, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
sys.stderr.flush()
def failed(msg, settings):
sys.stderr.write('{red}{msg}{reset}\n'.format(
msg=msg,
red=color(colorama.Fore.RED, settings),
reset=color(colorama.Style.RESET_ALL, settings)))

View File

@@ -1,16 +1,13 @@
from collections import namedtuple
from imp import load_source
from pathlib import Path
from os.path import expanduser
from subprocess import Popen, PIPE
import os
import sys
from traceback import format_exception
from psutil import Process, TimeoutExpired
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command'))
import colorama
import six
from . import logs, conf, types, shells
def setup_user_dir():
@@ -19,43 +16,38 @@ def setup_user_dir():
rules_dir = user_dir.joinpath('rules')
if not rules_dir.is_dir():
rules_dir.mkdir(parents=True)
user_dir.joinpath('settings.py').touch()
conf.initialize_settings_file(user_dir)
return user_dir
def get_settings(user_dir):
"""Returns prepared settings module."""
settings = load_source('settings',
str(user_dir.joinpath('settings.py')))
settings.__dict__.setdefault('rules', None)
settings.__dict__.setdefault('wait_command', 3)
settings.__dict__.setdefault('require_confirmation', False)
return settings
def is_rule_enabled(settings, rule):
"""Returns `True` when rule mentioned in `rules` or `rules`
isn't defined.
"""
return settings.rules is None or rule.name[:-3] in settings.rules
def load_rule(rule):
"""Imports rule module and returns it."""
rule_module = load_source(rule.name[:-3], str(rule))
return Rule(rule.name[:-3], rule_module.match,
rule_module.get_new_command)
return types.Rule(rule.name[:-3], rule_module.match,
rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True),
getattr(rule_module, 'side_effect', None),
getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY))
def _get_loaded_rules(rules, settings):
"""Yields all available rules."""
for rule in rules:
if rule.name != '__init__.py':
loaded_rule = load_rule(rule)
if loaded_rule in settings.rules:
yield loaded_rule
def get_rules(user_dir, settings):
"""Returns all enabled rules."""
bundled = Path(__file__).parent\
.joinpath('rules')\
.glob('*.py')
bundled = Path(__file__).parent \
.joinpath('rules') \
.glob('*.py')
user = user_dir.joinpath('rules').glob('*.py')
return [load_rule(rule) for rule in sorted(list(bundled)) + list(user)
if rule.name != '__init__.py' and is_rule_enabled(settings, rule)]
rules = _get_loaded_rules(sorted(bundled) + sorted(user), settings)
return sorted(rules, key=lambda rule: settings.priority.get(
rule.name, rule.priority))
def wait_output(settings, popen):
@@ -70,7 +62,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
@@ -78,7 +70,7 @@ def wait_output(settings, popen):
def get_command(settings, args):
"""Creates command from `args` and executes it."""
if sys.version_info[0] < 3:
if six.PY2:
script = ' '.join(arg.decode('utf-8') for arg in args[1:])
else:
script = ' '.join(args[1:])
@@ -86,11 +78,12 @@ def get_command(settings, args):
if not script:
return
script = shells.from_shell(script)
result = Popen(script, shell=True, stdout=PIPE, stderr=PIPE,
env=dict(os.environ, LANG='C'))
if wait_output(settings, result):
return Command(script, result.stdout.read().decode('utf-8'),
result.stderr.read().decode('utf-8'))
return types.Command(script, result.stdout.read().decode('utf-8'),
result.stderr.read().decode('utf-8'))
def get_matched_rule(command, rules, settings):
@@ -100,52 +93,45 @@ def get_matched_rule(command, rules, settings):
if rule.match(command, settings):
return rule
except Exception:
sys.stderr.write(u'[WARN] {}: {}---------------------\n\n'.format(
rule.name, ''.join(format_exception(*sys.exc_info()))))
logs.rule_failed(rule, sys.exc_info(), settings)
def confirm(new_command, settings):
def confirm(new_command, side_effect, settings):
"""Returns `True` when running of new command confirmed."""
if not settings.require_confirmation:
sys.stderr.write(new_command + '\n')
logs.show_command(new_command, side_effect, settings)
return True
sys.stderr.write(new_command + ' [Enter/Ctrl+C]')
sys.stderr.flush()
logs.confirm_command(new_command, side_effect, settings)
try:
sys.stdin.read(1)
return True
except KeyboardInterrupt:
sys.stderr.write('Aborted\n')
logs.failed('Aborted', settings)
return False
def run_rule(rule, command, settings):
"""Runs command from rule for passed command."""
new_command = rule.get_new_command(command, settings)
if confirm(new_command, settings):
new_command = shells.to_shell(rule.get_new_command(command, settings))
if confirm(new_command, rule.side_effect, settings):
if rule.side_effect:
rule.side_effect(command, settings)
shells.put_to_history(new_command)
print(new_command)
def is_second_run(command):
"""Is it the second run of `fuck`?"""
return command.script.startswith('fuck')
def main():
colorama.init()
user_dir = setup_user_dir()
settings = get_settings(user_dir)
settings = conf.get_settings(user_dir)
command = get_command(settings, sys.argv)
if command:
if is_second_run(command):
print("echo Can't fuck twice")
return
rules = get_rules(user_dir, settings)
matched_rule = get_matched_rule(command, rules, settings)
if matched_rule:
run_rule(matched_rule, command, settings)
return
print('echo No fuck given')
logs.failed('No fuck given', settings)

27
thefuck/rules/apt_get.py Normal file
View File

@@ -0,0 +1,27 @@
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:
c = CommandNotFound.CommandNotFound()
pkgs = c.getPackages(command.script.split(" ")[0])
name, _ = pkgs[0]
return True
except IndexError:
# 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]
formatme = shells.and_('sudo apt-get install {}', '{}')
return formatme.format(name, command.script)

View File

@@ -0,0 +1,42 @@
import difflib
import os
import re
from subprocess import check_output
# Formulars are base on each local system's status
brew_formulas = []
try:
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):
if file_name.endswith('.rb'):
brew_formulas.append(file_name.replace('.rb', ''))
except:
pass
def _get_similar_formulars(formula_name):
return difflib.get_close_matches(formula_name, brew_formulas, 1, 0.85)
def match(command, settings):
is_proper_command = ('brew install' in command.script and
'No available formula' in command.stderr)
has_possible_formulas = False
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
return has_possible_formulas
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]
return command.script.replace(not_exist_formula, exist_formula, 1)

View File

@@ -0,0 +1,103 @@
import difflib
import os
import re
import subprocess
BREW_CMD_PATH = '/Library/Homebrew/cmd'
TAP_PATH = '/Library/Taps'
TAP_CMD_PATH = '/%s/%s/cmd'
def _get_brew_path_prefix():
"""To get brew path"""
try:
return subprocess.check_output(['brew', '--prefix'],
universal_newlines=True).strip()
except:
return None
def _get_brew_commands(brew_path_prefix):
"""To get brew default commands on local environment"""
brew_cmd_path = brew_path_prefix + BREW_CMD_PATH
commands = [name.replace('.rb', '') for name in os.listdir(brew_cmd_path)
if name.endswith('.rb')]
return commands
def _get_brew_tap_specific_commands(brew_path_prefix):
"""To get tap's specific commands
https://github.com/Homebrew/homebrew/blob/master/Library/brew.rb#L115"""
commands = []
brew_taps_path = brew_path_prefix + TAP_PATH
for user in _get_directory_names_only(brew_taps_path):
taps = _get_directory_names_only(brew_taps_path + '/%s' % user)
# Brew Taps's naming rule
# https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/brew-tap.md#naming-conventions-and-limitations
taps = (tap for tap in taps if tap.startswith('homebrew-'))
for tap in taps:
tap_cmd_path = brew_taps_path + TAP_CMD_PATH % (user, tap)
if os.path.isdir(tap_cmd_path):
commands += (name.replace('brew-', '').replace('.rb', '')
for name in os.listdir(tap_cmd_path)
if _is_brew_tap_cmd_naming(name))
return commands
def _is_brew_tap_cmd_naming(name):
if name.startswith('brew-') and name.endswith('.rb'):
return True
return False
def _get_directory_names_only(path):
return [d for d in os.listdir(path)
if os.path.isdir(os.path.join(path, d))]
brew_path_prefix = _get_brew_path_prefix()
# Failback commands for testing (Based on Homebrew 0.9.5)
brew_commands = ['info', 'home', 'options', 'install', 'uninstall',
'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
'doctor', 'create', 'edit']
if brew_path_prefix:
try:
brew_commands = _get_brew_commands(brew_path_prefix) \
+ _get_brew_tap_specific_commands(brew_path_prefix)
except OSError:
pass
def _get_similar_commands(command):
return difflib.get_close_matches(command, brew_commands)
def match(command, settings):
is_proper_command = ('brew' in command.script and
'Unknown command' in command.stderr)
has_possible_commands = False
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
return has_possible_commands
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]
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

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

@@ -0,0 +1,16 @@
import re
from thefuck import shells
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
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):
repl = shells.and_('mkdir -p \\1', 'cd \\1')
return re.sub(r'^cd (.*)', repl, command.script)

View File

@@ -0,0 +1,15 @@
import re
def match(command, settings):
return ('composer' in command.script
and ('did you mean this?' in command.stderr.lower()
or 'did you mean one of these?' in command.stderr.lower()))
def get_new_command(command, settings):
broken_cmd = re.findall(r"Command \"([^']*)\" is not defined", command.stderr)[0]
new_cmd = re.findall(r'Did you mean this\?[^\n]*\n\s*([^\n]*)', command.stderr)
if not new_cmd:
new_cmd = re.findall(r'Did you mean one of these\?[^\n]*\n\s*([^\n]*)', command.stderr)
return command.script.replace(broken_cmd, new_cmd[0].strip(), 1)

View File

@@ -1,10 +1,13 @@
import re
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
return command.script.startswith('cp ') \
and 'cp: omitting directory' in command.stderr.lower()
@sudo_support
def get_new_command(command, settings):
return re.sub(r'^cp', 'cp -a', command.script)

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

@@ -0,0 +1,15 @@
# -*- encoding: utf-8 -*-
import re
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
return ('command not found' in command.stderr.lower()
and u' ' in command.script)
@sudo_support
def get_new_command(command, settings):
return re.sub(u' ', ' ', command.script)

17
thefuck/rules/git_add.py Normal file
View File

@@ -0,0 +1,17 @@
import re
from thefuck import shells
def match(command, settings):
return ('git' in command.script
and 'did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" in command.stderr)
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]
formatme = shells.and_('git add -- {}', '{}')
return formatme.format(missing_file, command.script)

View File

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

View File

@@ -0,0 +1,17 @@
import re
from thefuck import shells
def match(command, settings):
return ('git' in command.script
and '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_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
formatme = shells.and_('git branch {}', '{}')
return formatme.format(missing_file, command.script)

View File

@@ -0,0 +1,6 @@
def match(command, settings):
return command.script.startswith('git d')
def get_new_command(command, settings):
return '{} --staged'.format(command.script)

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

@@ -0,0 +1,15 @@
from thefuck import shells
def match(command, settings):
return ('git' in command.script
and 'pull' in command.script
and 'set-upstream' in command.stderr)
def get_new_command(command, settings):
line = command.stderr.split('\n')[-3].strip()
branch = line.split(' ')[-1]
set_upstream = line.replace('<remote>', 'origin')\
.replace('<branch>', branch)
return shells.and_(set_upstream, command.script)

View File

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

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

@@ -1,11 +1,14 @@
import os
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
return os.path.exists(command.script.split()[0]) \
and 'command not found' in command.stderr
@sudo_support
def get_new_command(command, settings):
return u'./{}'.format(command.script)

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,12 +1,15 @@
import re
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
return (command.script.startswith('lein')
and "is not a task. See 'lein help'" in command.stderr
and 'Did you mean this?' in command.stderr)
@sudo_support
def get_new_command(command, settings):
broken_cmd = re.findall(r"'([^']*)' is not a task",
command.stderr)[0]

10
thefuck/rules/ls_lah.py Normal file
View File

@@ -0,0 +1,10 @@
def match(command, settings):
return (command.script == 'ls'
or command.script.startswith('ls ')
and not ('ls -' in command.script))
def get_new_command(command, settings):
command = command.script.split(' ')
command[0] = 'ls -lah'
return ' '.join(command)

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,34 @@
import re
from difflib import get_close_matches
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)
matches = get_close_matches(script[1], possisiblities)
if matches:
script[1] = matches[0]
else:
script[1] = possisiblities[0]
return ' '.join(script)

View File

@@ -1,9 +1,13 @@
import re
from thefuck.utils import sudo_support
@sudo_support
def match(command, settings):
return ('mkdir' in command.script
and 'No such file or directory' in command.stderr)
@sudo_support
def get_new_command(command, settings):
return re.sub('^mkdir (.*)', 'mkdir -p \\1', command.script)

View File

@@ -1,6 +1,8 @@
from difflib import get_close_matches
import os
from pathlib import Path
from thefuck.utils import sudo_support
from thefuck.shells import thefuck_alias, get_aliases
def _safe(fn, fallback):
@@ -10,21 +12,28 @@ def _safe(fn, fallback):
return fallback
def _get_all_bins():
def _get_all_callables():
tf_alias = thefuck_alias()
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)]
if not _safe(exe.is_dir, True)] + [
alias for alias in get_aliases() if alias != tf_alias]
@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_callables()))
@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_callables())[0]
return ' '.join([new_command] + command.script.split(' ')[1:])
priority = 3000

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