1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-11-04 17:12:05 +00:00

Compare commits

..

148 Commits
1.36 ... 1.46

Author SHA1 Message Date
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
Dugucloud
fc364b99b9 Revert "Added colorama in requirements.txt"
This reverts commit 742f6f9c94.
2015-04-22 23:18:11 +08:00
Dugucloud
742f6f9c94 Added colorama in requirements.txt 2015-04-22 21:48:17 +08:00
秋纫
cd1bee9cb0 Merge pull request #2 from nvbn/master
Sync with master
2015-04-22 21:36:07 +08:00
72 changed files with 1374 additions and 132 deletions

View File

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

View File

@@ -73,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: [settings](#settings) option:
```bash ```bash
@@ -104,7 +104,7 @@ sudo pip install thefuck
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation) [Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
And add to `.bashrc` or `.bash_profile`(for OSX): And add to the `.bashrc` or `.bash_profile`(for OSX):
```bash ```bash
alias fuck='eval $(thefuck $(fc -ln -1)); history -r' alias fuck='eval $(thefuck $(fc -ln -1)); history -r'
@@ -118,6 +118,11 @@ Or in your `.zshrc`:
alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R' alias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'
``` ```
If you are using `tcsh`:
```tcsh
alias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'
```
Alternatively, you can redirect the output of `thefuck-alias`: Alternatively, you can redirect the output of `thefuck-alias`:
```bash ```bash
@@ -127,6 +132,7 @@ thefuck-alias >> ~/.bashrc
[Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases) [Or in your shell config (Bash, Zsh, Fish, Powershell).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
Changes will be available only in a new shell session. 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 ## Update
@@ -137,36 +143,62 @@ sudo pip install thefuck --upgrade
## How it works ## How it works
The Fuck tries to match rule for the previous command, create new command The Fuck tries to match a rule for the previous command, creates a new command
using matched rule and run it. Rules enabled by default: using the matched rule and runs it. Rules enabled by default are as follows:
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`; * `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cd_parent` &ndash; changes `cd..` to `cd ..`; * `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_mkdir` &ndash; creates directories before cd'ing into them;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
* `composer_not_command` &ndash; fixes composer command name;
* `cp_omitting_directory` &ndash; adds `-a` when you `cp` directory; * `cp_omitting_directory` &ndash; adds `-a` when you `cp` directory;
* `cpp11` &ndash; add missing `-std=c++11` to `g++` or `clang++`;
* `dry` &ndash; fix repetitions like "git git push";
* `django_south_ghost` &ndash; adds `--delete-ghost-migrations` to failed because ghosts django south migration;
* `django_south_merge` &ndash; adds `--merge` to inconsistent django south migration;
* `fix_alt_space` &ndash; replaces Alt+Space with Space character; * `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `java` &ndash; removes `.java` extension when running Java programs;
* `git_add` &ndash; fix *"Did you forget to 'git add'?"*; * `git_add` &ndash; fix *"Did you forget to 'git add'?"*;
* `git_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_no_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`; * `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_stash` &ndash; stashes you local modifications before rebasing or switching branch;
* `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; * `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`; * `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `ls_lah` &ndash; adds -lah to ls;
* `man` &ndash; change manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent; * `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `no_command` &ndash; fixes wrong console commands, for example `vom/vim`; * `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed; * `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
* `open` &ndash; prepends `http` to address passed to `open`;
* `pip_unknown_command` &ndash; fixes wrong pip commands, for example `pip instatl/pip install`; * `pip_unknown_command` &ndash; fixes wrong pip commands, for example `pip instatl/pip install`;
* `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script; * `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script;
* `sl_ls` &ndash; changes `sl` to `ls`; * `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args'
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory; * `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning; * `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions; * `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` &ndash; switches command from your local layout to en; * `switch_layout` &ndash; switches command from your local layout to en;
* `whois` &ndash; fixes `whois` command.
Enabled by default only on specific platforms:
* `apt_get` &ndash; installs app from apt if it not installed; * `apt_get` &ndash; installs app from apt if it not installed;
* `brew_install` &ndash; fixes formula name for `brew install`; * `brew_install` &ndash; fixes formula name for `brew install`;
* `composer_not_command` &ndash; fixes composer command name. * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `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: Bundled, but not enabled by default:
* `ls_lah` &ndash; adds -lah to ls;
* `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command. * `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.
## Creating your own rules ## Creating your own rules
@@ -193,13 +225,13 @@ def match(command, settings):
def get_new_command(command, settings): def get_new_command(command, settings):
return 'sudo {}'.format(command.script) return 'sudo {}'.format(command.script)
# Optional: # Optional:
enabled_by_default = True enabled_by_default = True
def side_effect(command, settings): def side_effect(command, settings):
subprocess.call('chmod 777 .', shell=True) subprocess.call('chmod 777 .', shell=True)
priority = 1000 # Lower first priority = 1000 # Lower first
``` ```
@@ -208,10 +240,10 @@ priority = 1000 # Lower first
## 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 `thefuck.conf.DEFAULT_RULES`; * `rules` &ndash; list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
* `require_confirmation` &ndash; require confirmation before running new command, by default `False`; * `require_confirmation` &ndash; requires confirmation before running new command, by default `False`;
* `wait_command` &ndash; max amount of time in seconds for getting previous command output; * `wait_command` &ndash; max amount of time in seconds for getting previous command output;
* `no_colors` &ndash; disable colored output; * `no_colors` &ndash; disable colored output;
* `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first. * `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first.

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 tag {}'.format(version), shell=True)
call('git push', shell=True) call('git push', shell=True)
call('git push --tags', 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 pytest
mock mock
pytest-mock
wheel

2
setup.cfg Normal file
View File

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

View File

@@ -1,7 +1,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
VERSION = '1.36' VERSION = '1.46'
setup(name='thefuck', setup(name='thefuck',
@@ -17,4 +17,5 @@ setup(name='thefuck',
zip_safe=False, zip_safe=False,
install_requires=['pathlib', 'psutil', 'colorama', 'six'], install_requires=['pathlib', 'psutil', 'colorama', 'six'],
entry_points={'console_scripts': [ entry_points={'console_scripts': [
'thefuck = thefuck.main:main', 'thefuck-alias = thefuck.main:alias']}) 'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.shells:app_alias']})

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

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

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

View File

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

View File

@@ -0,0 +1,15 @@
import pytest
from thefuck.rules.brew_upgrade import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='brew upgrade')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('brew upgrade'), 'brew upgrade --all')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

View File

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

View File

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

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

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

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

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

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

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

View File

@@ -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

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

View File

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

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

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

View File

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

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

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

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

View File

@@ -14,10 +14,8 @@ def test_default(enabled, rules, result):
@pytest.fixture @pytest.fixture
def load_source(monkeypatch): def load_source(mocker):
mock = Mock() return mocker.patch('thefuck.conf.load_source')
monkeypatch.setattr('thefuck.conf.load_source', mock)
return mock
@pytest.fixture @pytest.fixture

View File

@@ -6,15 +6,15 @@ from thefuck import main, conf, types
from tests.utils import Rule, Command from tests.utils import Rule, Command
def test_load_rule(monkeypatch): def test_load_rule(mocker):
match = object() match = object()
get_new_command = object() get_new_command = object()
load_source = Mock() load_source = mocker.patch(
load_source.return_value = Mock(match=match, 'thefuck.main.load_source',
get_new_command=get_new_command, return_value=Mock(match=match,
enabled_by_default=True, get_new_command=get_new_command,
priority=900) enabled_by_default=True,
monkeypatch.setattr('thefuck.main.load_source', load_source) priority=900))
assert main.load_rule(Path('/rules/bash.py')) \ assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command, priority=900) == Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py') load_source.assert_called_once_with('bash', '/rules/bash.py')
@@ -22,10 +22,8 @@ def test_load_rule(monkeypatch):
class TestGetRules(object): class TestGetRules(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def glob(self, monkeypatch): def glob(self, mocker):
mock = Mock(return_value=[]) return mocker.patch('thefuck.main.Path.glob', return_value=[])
monkeypatch.setattr('thefuck.main.Path.glob', mock)
return mock
def _compare_names(self, rules, names): def _compare_names(self, rules, names):
return [r.name for r in rules] == names return [r.name for r in rules] == names
@@ -118,10 +116,8 @@ class TestGetMatchedRule(object):
class TestRunRule(object): class TestRunRule(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def confirm(self, monkeypatch): def confirm(self, mocker):
mock = Mock(return_value=True) return mocker.patch('thefuck.main.confirm', return_value=True)
monkeypatch.setattr('thefuck.main.confirm', mock)
return mock
def test_run_rule(self, capsys): def test_run_rule(self, capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
@@ -147,10 +143,8 @@ class TestRunRule(object):
class TestConfirm(object): class TestConfirm(object):
@pytest.fixture @pytest.fixture
def stdin(self, monkeypatch): def stdin(self, mocker):
mock = Mock(return_value='\n') return mocker.patch('sys.stdin.read', return_value='\n')
monkeypatch.setattr('sys.stdin.read', mock)
return mock
def test_when_not_required(self, capsys): def test_when_not_required(self, capsys):
assert main.confirm('command', None, Mock(require_confirmation=False)) assert main.confirm('command', None, Mock(require_confirmation=False))

View File

@@ -1,40 +1,48 @@
import pytest import pytest
from mock import Mock, MagicMock
from thefuck import shells from thefuck import shells
@pytest.fixture @pytest.fixture
def builtins_open(monkeypatch): def builtins_open(mocker):
mock = MagicMock() return mocker.patch('six.moves.builtins.open')
monkeypatch.setattr('six.moves.builtins.open', mock)
return mock
@pytest.fixture @pytest.fixture
def isfile(monkeypatch): def isfile(mocker):
mock = Mock(return_value=True) return mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setattr('os.path.isfile', mock)
return mock
class TestGeneric(object): class TestGeneric(object):
def test_from_shell(self): @pytest.fixture
assert shells.Generic().from_shell('pwd') == 'pwd' def shell(self):
return shells.Generic()
def test_to_shell(self): def test_from_shell(self, shell):
assert shells.Generic().to_shell('pwd') == 'pwd' assert shell.from_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open): def test_to_shell(self, shell):
assert shells.Generic().put_to_history('ls') is None assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, shell):
assert shell.put_to_history('ls') is None
assert builtins_open.call_count == 0 assert builtins_open.call_count == 0
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {}
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestBash(object): class TestBash(object):
@pytest.fixture
def shell(self):
return shells.Bash()
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, monkeypatch): def Popen(self, mocker):
mock = Mock() mock = mocker.patch('thefuck.shells.Popen')
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = ( mock.return_value.stdout.read.return_value = (
b'alias l=\'ls -CF\'\n' b'alias l=\'ls -CF\'\n'
b'alias la=\'ls -A\'\n' b'alias la=\'ls -A\'\n'
@@ -44,24 +52,84 @@ class TestBash(object):
@pytest.mark.parametrize('before, after', [ @pytest.mark.parametrize('before, after', [
('pwd', 'pwd'), ('pwd', 'pwd'),
('ll', 'ls -alF')]) ('ll', 'ls -alF')])
def test_from_shell(self, before, after): def test_from_shell(self, before, after, shell):
assert shells.Bash().from_shell(before) == after assert shell.from_shell(before) == after
def test_to_shell(self): def test_to_shell(self, shell):
assert shells.Bash().to_shell('pwd') == 'pwd' assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open): def test_put_to_history(self, builtins_open, shell):
shells.Bash().put_to_history('ls') shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value.\ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('ls\n') write.assert_called_once_with('ls\n')
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}
@pytest.mark.usefixtures('isfile')
class TestFish(object):
@pytest.fixture
def shell(self):
return shells.Fish()
@pytest.fixture(autouse=True)
def Popen(self, mocker):
mock = mocker.patch('thefuck.shells.Popen')
mock.return_value.stdout.read.return_value = (
b'fish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nmath')
return mock
@pytest.mark.parametrize('before, after', [
('pwd', 'pwd'),
('fuck', 'fish -ic "fuck"'),
('find', 'find'),
('funced', 'fish -ic "funced"'),
('awk', 'awk'),
('math "2 + 2"', r'fish -ic "math \"2 + 2\""'),
('vim', 'vim'),
('ll', 'fish -ic "ll"')]) # Fish has no aliases but functions
def test_from_shell(self, before, after, shell):
assert shell.from_shell(before) == after
def test_to_shell(self, shell):
assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, mocker, shell):
mocker.patch('thefuck.shells.time',
return_value=1430707243.3517463)
shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with('- cmd: ls\n when: 1430707243\n')
def test_and_(self, shell):
assert shell.and_('foo', 'bar') == 'foo; and bar'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'fish_config': 'fish_config',
'fuck': 'fuck',
'funced': 'funced',
'funcsave': 'funcsave',
'grep': 'grep',
'history': 'history',
'll': 'll',
'math': 'math'}
@pytest.mark.usefixtures('isfile') @pytest.mark.usefixtures('isfile')
class TestZsh(object): class TestZsh(object):
@pytest.fixture
def shell(self):
return shells.Zsh()
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def Popen(self, monkeypatch): def Popen(self, mocker):
mock = Mock() mock = mocker.patch('thefuck.shells.Popen')
monkeypatch.setattr('thefuck.shells.Popen', mock)
mock.return_value.stdout.read.return_value = ( mock.return_value.stdout.read.return_value = (
b'l=\'ls -CF\'\n' b'l=\'ls -CF\'\n'
b'la=\'ls -A\'\n' b'la=\'ls -A\'\n'
@@ -71,15 +139,23 @@ class TestZsh(object):
@pytest.mark.parametrize('before, after', [ @pytest.mark.parametrize('before, after', [
('pwd', 'pwd'), ('pwd', 'pwd'),
('ll', 'ls -alF')]) ('ll', 'ls -alF')])
def test_from_shell(self, before, after): def test_from_shell(self, before, after, shell):
assert shells.Zsh().from_shell(before) == after assert shell.from_shell(before) == after
def test_to_shell(self): def test_to_shell(self, shell):
assert shells.Zsh().to_shell('pwd') == 'pwd' assert shell.to_shell('pwd') == 'pwd'
def test_put_to_history(self, builtins_open, monkeypatch): def test_put_to_history(self, builtins_open, mocker, shell):
monkeypatch.setattr('thefuck.shells.time', mocker.patch('thefuck.shells.time',
lambda: 1430707243.3517463) return_value=1430707243.3517463)
shells.Zsh().put_to_history('ls') shell.put_to_history('ls')
builtins_open.return_value.__enter__.return_value. \ builtins_open.return_value.__enter__.return_value. \
write.assert_called_once_with(': 1430707243:0;ls\n') write.assert_called_once_with(': 1430707243:0;ls\n')
def test_and_(self, shell):
assert shell.and_('ls', 'cd') == 'ls && cd'
def test_get_aliases(self, shell):
assert shell.get_aliases() == {'l': 'ls -CF',
'la': 'ls -A',
'll': 'ls -alF'}

View File

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

View File

@@ -121,10 +121,6 @@ def run_rule(rule, command, settings):
print(new_command) print(new_command)
def alias():
print(shells.app_alias())
def main(): def main():
colorama.init() colorama.init()
user_dir = setup_user_dir() user_dir = setup_user_dir()

View File

@@ -1,9 +1,12 @@
from thefuck import shells
from thefuck.utils import sudo_support
try: try:
import CommandNotFound import CommandNotFound
except ImportError: except ImportError:
enabled_by_default = False enabled_by_default = False
@sudo_support
def match(command, settings): def match(command, settings):
if 'not found' in command.stderr: if 'not found' in command.stderr:
try: try:
@@ -15,9 +18,10 @@ def match(command, settings):
# IndexError is thrown when no matching package is found # IndexError is thrown when no matching package is found
return False return False
@sudo_support
def get_new_command(command, settings): def get_new_command(command, settings):
c = CommandNotFound.CommandNotFound() c = CommandNotFound.CommandNotFound()
pkgs = c.getPackages(command.script.split(" ")[0]) pkgs = c.getPackages(command.script.split(" ")[0])
name, _ = pkgs[0] name, _ = pkgs[0]
return "sudo apt-get install {} && {}".format(name, command.script) formatme = shells.and_('sudo apt-get install {}', '{}')
return formatme.format(name, command.script)

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

View File

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

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

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

View File

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

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

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

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

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

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

View File

@@ -1,6 +1,3 @@
enabled_by_default = False
def match(command, settings): def match(command, settings):
return 'ls' in command.script and not ('ls -' in command.script) return 'ls' in command.script and not ('ls -' in command.script)

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

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

View File

@@ -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

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

View File

@@ -1,5 +1,5 @@
"""Module with shell specific actions, each shell class should """Module with shell specific actions, each shell class should
implement `from_shell`, `to_shell`, `app_alias` and `put_to_history` implement `from_shell`, `to_shell`, `app_alias`, `put_to_history` and `get_aliases`
methods. methods.
""" """
@@ -8,15 +8,16 @@ from subprocess import Popen, PIPE
from time import time from time import time
import os import os
from psutil import Process from psutil import Process
from .utils import DEVNULL from .utils import DEVNULL, memoize
class Generic(object): class Generic(object):
def _get_aliases(self):
def get_aliases(self):
return {} return {}
def _expand_aliases(self, command_script): def _expand_aliases(self, command_script):
aliases = self._get_aliases() aliases = self.get_aliases()
binary = command_script.split(' ')[0] binary = command_script.split(' ')[0]
if binary in aliases: if binary in aliases:
return command_script.replace(binary, aliases[binary], 1) return command_script.replace(binary, aliases[binary], 1)
@@ -47,20 +48,28 @@ class Generic(object):
with open(history_file_name, 'a') as history: with open(history_file_name, 'a') as history:
history.write(self._get_history_line(command_script)) history.write(self._get_history_line(command_script))
def and_(self, *commands):
return u' && '.join(commands)
class Bash(Generic): class Bash(Generic):
def app_alias(self):
return "\nalias fuck='eval $(thefuck $(fc -ln -1)); history -r'\n"
def _parse_alias(self, alias): def _parse_alias(self, alias):
name, value = alias.replace('alias ', '', 1).split('=', 1) name, value = alias.replace('alias ', '', 1).split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
value = value[1:-1] value = value[1:-1]
return name, value return name, value
def _get_aliases(self): @memoize
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) def get_aliases(self):
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias) if alias and '=' in alias)
def _get_history_file_name(self): def _get_history_file_name(self):
return os.environ.get("HISTFILE", return os.environ.get("HISTFILE",
@@ -70,19 +79,68 @@ class Bash(Generic):
return u'{}\n'.format(command_script) return u'{}\n'.format(command_script)
class Fish(Generic):
def app_alias(self):
return ("function fuck -d 'Correct your previous console command'\n"
" set -l exit_code $status\n"
" set -l eval_script"
" (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
" set -l fucked_up_commandd $history[1]\n"
" thefuck $fucked_up_commandd > $eval_script\n"
" . $eval_script\n"
" rm $eval_script\n"
" if test $exit_code -ne 0\n"
" history --delete $fucked_up_commandd\n"
" end\n"
"end")
@memoize
def get_aliases(self):
proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
shell=True)
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
return {function: function for function in functions}
def _expand_aliases(self, command_script):
aliases = self.get_aliases()
binary = command_script.split(' ')[0]
if binary in aliases:
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
else:
return command_script
def from_shell(self, command_script):
"""Prepares command before running in app."""
return self._expand_aliases(command_script)
def _get_history_file_name(self):
return os.path.expanduser('~/.config/fish/fish_history')
def _get_history_line(self, command_script):
return u'- cmd: {}\n when: {}\n'.format(command_script, int(time()))
def and_(self, *commands):
return u'; and '.join(commands)
class Zsh(Generic): class Zsh(Generic):
def app_alias(self):
return "\nalias fuck='eval $(thefuck $(fc -ln -1 | tail -n 1)); fc -R'\n"
def _parse_alias(self, alias): def _parse_alias(self, alias):
name, value = alias.split('=', 1) name, value = alias.split('=', 1)
if value[0] == value[-1] == '"' or value[0] == value[-1] == "'": if value[0] == value[-1] == '"' or value[0] == value[-1] == "'":
value = value[1:-1] value = value[1:-1]
return name, value return name, value
def _get_aliases(self): @memoize
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL, shell=True) def get_aliases(self):
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict( return dict(
self._parse_alias(alias) self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n') for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias) if alias and '=' in alias)
def _get_history_file_name(self): def _get_history_file_name(self):
return os.environ.get("HISTFILE", return os.environ.get("HISTFILE",
@@ -92,13 +150,44 @@ class Zsh(Generic):
return u': {}:0;{}\n'.format(int(time()), command_script) return u': {}:0;{}\n'.format(int(time()), command_script)
class Tcsh(Generic):
def app_alias(self):
return "\nalias fuck 'set fucked_cmd=`history -h 2 | head -n 1` && eval `thefuck ${fucked_cmd}`'\n"
def _parse_alias(self, alias):
name, value = alias.split("\t", 1)
return name, value
@memoize
def get_aliases(self):
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
return dict(
self._parse_alias(alias)
for alias in proc.stdout.read().decode('utf-8').split('\n')
if alias and '\t' in alias)
def _get_history_file_name(self):
return os.environ.get("HISTFILE",
os.path.expanduser('~/.history'))
def _get_history_line(self, command_script):
return u'#+{}\n{}\n'.format(int(time()), command_script)
shells = defaultdict(lambda: Generic(), { shells = defaultdict(lambda: Generic(), {
'bash': Bash(), 'bash': Bash(),
'zsh': Zsh()}) 'fish': Fish(),
'zsh': Zsh(),
'csh': Tcsh(),
'tcsh': Tcsh()})
def _get_shell(): def _get_shell():
shell = Process(os.getpid()).parent().cmdline()[0] try:
shell = Process(os.getpid()).parent().name()
except TypeError:
shell = Process(os.getpid()).parent.name
return shells[shell] return shells[shell]
@@ -111,8 +200,16 @@ def to_shell(command):
def app_alias(): def app_alias():
return _get_shell().app_alias() print(_get_shell().app_alias())
def put_to_history(command): def put_to_history(command):
return _get_shell().put_to_history(command) return _get_shell().put_to_history(command)
def and_(*commands):
return _get_shell().and_(*commands)
def get_aliases():
return list(_get_shell().get_aliases().keys())

View File

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

6
tox.ini Normal file
View File

@@ -0,0 +1,6 @@
[tox]
envlist = py27,py33,py34
[testenv]
deps = -rrequirements.txt
commands = py.test