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

Compare commits

...

120 Commits
2.6 ... 2.9

Author SHA1 Message Date
nvbn
faa7ee6030 Bump to 2.9 2015-09-05 11:21:21 +03:00
nvbn
6321f25533 Fix bare run of func tests 2015-09-02 13:52:06 +03:00
nvbn
9a02e821cd Fix python 2 support 2015-09-02 11:54:58 +03:00
nvbn
4129ff2717 #353 Cache aliases in a temporary file 2015-09-02 11:10:03 +03:00
nvbn
ea6600be8b Kill containers after func tests 2015-09-02 10:33:45 +03:00
nvbn
b0195a8748 Reorganize imports 2015-09-02 09:43:40 +03:00
Vladimir Iakovlev
fc35ee657e Merge pull request #354 from mcarton/patch-1
Some README fixes
2015-09-02 09:37:28 +03:00
Vladimir Iakovlev
50207d8180 Merge pull request #352 from mcarton/slow
Fix slowness problems II
2015-09-02 09:36:48 +03:00
Martin Carton
b6855587fa Some README fixes 2015-09-02 00:05:14 +02:00
nvbn
45d849b1ac Use thefuck --alias in func tests 2015-09-01 18:36:25 +03:00
Vladimir Iakovlev
d8027bb499 Merge pull request #348 from myoung34/master
fix usage. why not
2015-09-01 18:21:21 +03:00
nvbn
4932122f71 #349 Add installation of command-not-found in install script 2015-09-01 18:18:28 +03:00
nvbn
8a4f4eea45 #349 Add note about python-commandnotfound dependency 2015-09-01 18:10:53 +03:00
mcarton
ff8d61a4fb Merge branch 'master' of github.com:nvbn/thefuck into slow 2015-09-01 14:43:41 +02:00
nvbn
6dcf9a3a14 Fix python 2 support 2015-09-01 15:32:23 +03:00
mcarton
8b62959fe3 Merge branch 'master' of github.com:nvbn/thefuck into slow 2015-09-01 14:28:30 +02:00
nvbn
21103d1b50 Simplify corrector steps 2015-09-01 14:43:27 +03:00
nvbn
61937e9e8f #334: Wait only for first matched rule; regression: always show arrows 2015-09-01 14:34:41 +03:00
nvbn
5d74344994 Make CorrectedCommand ignore priority when checking equality 2015-09-01 13:03:24 +03:00
nvbn
12394ca842 #334: Don't wait for all rules before showing result 2015-09-01 12:51:41 +03:00
nvbn
ebe53f0d18 Use decorator library 2015-08-27 16:52:26 +03:00
nvbn
0c283ff2b8 #334 Speed-up rules with caching for_app decorator 2015-08-27 16:42:09 +03:00
nvbn
bc78f1bbee git push origin masterMerge branch 'mlk-35_mvn' 2015-08-27 16:11:00 +03:00
nvbn
f2a7364e8c #351 Speed-up mvn rules, pep8 fixes 2015-08-27 16:10:50 +03:00
nvbn
9103c1ffd5 Add is_app/for_app helpers 2015-08-27 16:08:29 +03:00
nvbn
edf77a90ad Merge branch '35_mvn' of https://github.com/mlk/thefuck into mlk-35_mvn 2015-08-27 14:09:03 +03:00
mcarton
27c14a44af Fix tests
Thanks to [scorphus] for his [help].

[scorphus]: https://github.com/scorphus
[help]: https://github.com/nvbn/thefuck/pull/352#issuecomment-135248982
2015-08-27 10:54:42 +02:00
mcarton
514bb7df81 Don't run a shell just to run another shell 2015-08-26 23:38:33 +02:00
mcarton
3bd2c8d4c8 Remove unused imports 2015-08-26 21:43:20 +02:00
mcarton
e5ce000399 Improve the ssh_known_hosts rule import time 2015-08-26 21:43:20 +02:00
mcarton
9fc2bc904c Slightly improve the fix_file rule 2015-08-26 21:43:20 +02:00
mcarton
51f1f44162 Memoize thefuck.utils.which
It is used by some rules to determine if they should be enabled by
default and searches in the $PATH, which can be quiet slow.
2015-08-26 21:43:20 +02:00
mcarton
b0702d309f Improve brew* rules import time 2015-08-26 21:43:16 +02:00
mcarton
2b750bac8b Log rule import times 2015-08-26 19:14:38 +02:00
Michael Lee
8c02658a32 Removed debug statement 2015-08-26 15:02:46 +01:00
Michael Lee
301de75aee #35 - Fuzzy matching on maven lifecycle targets 2015-08-26 14:58:45 +01:00
Michael Lee
0d86fce9be #35 mvn will auto add clean package 2015-08-26 14:01:36 +01:00
nvbn
7be71a0121 #334 Add performance test 2015-08-26 14:34:39 +03:00
nvbn
354ae119c6 Don't duplicate project root in tests 2015-08-26 12:12:52 +03:00
myoung34
a2b2e5b5b8 fix usage. why not 2015-08-25 16:40:22 -05:00
nvbn
b21c9ebb43 Move all app/os specific utils to specific package 2015-08-25 14:09:47 +03:00
nvbn
2e002f666b Move utility functions from archlinux to utils 2015-08-25 13:55:33 +03:00
Vladimir Iakovlev
4163fb5f2e Merge pull request #340 from BuZZ-T/feature/apt-get-search
Adding rule for trying to search using apt-get
2015-08-25 13:48:41 +03:00
nvbn
e72c88e344 #346 Add support of other systems with get-pip 2015-08-25 13:47:30 +03:00
nvbn
f4eebbaaf9 #346 Improve installation script 2015-08-25 12:03:42 +03:00
nvbn
5e5a8e4dfa #346 Restart shell session after install 2015-08-25 10:18:21 +03:00
Bastian Gebhardt
8cbe236845 Adding rule for trying to search using apt-get 2015-08-25 08:09:31 +02:00
Bastian Gebhardt
2b3e8dc62a Adding rule for trying to search using apt-get 2015-08-25 00:20:21 +02:00
nvbn
5ab2cf646e Bump to 2.8 2015-08-24 20:31:28 +03:00
Vladimir Iakovlev
c5b4628c5c Update README.md 2015-08-24 20:27:11 +03:00
nvbn
d4cec3e850 Update readme 2015-08-24 20:25:59 +03:00
nvbn
564638c171 Add experimental installation script 2015-08-24 20:25:03 +03:00
nvbn
c6171a85e9 Improve readme test 2015-08-24 11:25:49 +03:00
Vladimir Iakovlev
ee610032b8 Merge pull request #345 from mcarton/slow
Fix slowness problems
2015-08-24 11:19:48 +03:00
mcarton
6754ebe20d Fix some spelling mistakes 2015-08-23 21:27:38 +02:00
mcarton
15d19fdf9c Fix slowness problems
The `get_aliases()` function was also sometimes called through
`shell.get_aliases()`.
Since the `history` rule also uses aliases, the shell was always
executed twice to get the aliases list.
2015-08-23 21:22:54 +02:00
Vladimir Iakovlev
967e30d914 Merge pull request #344 from trolley/typo-fix
Fix typo in alias warning
2015-08-23 16:39:31 +03:00
Mark Trolley
38ee31ebcb Fix typo in alias warning
Fixes a small typo in the deprecated alias warning.
2015-08-22 10:01:24 -04:00
Vladimir Iakovlev
7315958ea9 Merge pull request #343 from mlk/hdfs-rm-rm_X_Is_a_directory_add_minus_r
hdfs -rm -r /directory and hdfs -mkdir -p /directory/sub support
2015-08-21 18:34:19 +03:00
Vladimir Iakovlev
cce25b1ea4 Merge pull request #342 from mlk/322_vagrant
Issue: 322 Runs vagrant up when required
2015-08-21 18:33:42 +03:00
Michael Lee
1a57ef03c8 Removed additional assert 2015-08-21 16:06:12 +01:00
Michael Lee
298c04f89c Support for hdfs dfs -mkdir -p /directory/subdirectory 2015-08-21 16:05:49 +01:00
Michael Lee
42a8b4f639 Support for hdfs dfs -rm /directory 2015-08-21 15:48:54 +01:00
Michael Lee
336d8b7b4b Style change 2015-08-20 11:37:49 +01:00
Michael Lee
feb3eee2a0 Support for either starting only the machine requested, or starting all machines 2015-08-20 10:06:41 +01:00
Michael Lee
7cb0388ed0 Not matched unit tests, code style. 2015-08-20 09:41:01 +01:00
Michael Lee
004c0d06eb starts up vagrant if not already running 2015-08-19 17:12:54 +01:00
Vladimir Iakovlev
abbbd1f8eb Merge pull request #339 from mcarton/fix-338
Fix #338
2015-08-19 17:19:38 +03:00
Vladimir Iakovlev
700d9ac7e9 Merge pull request #337 from mcarton/cleanup
Some cleanup and fixes
2015-08-19 17:19:08 +03:00
Vladimir Iakovlev
8037a17b73 Merge pull request #333 from mlk/master
basic support for the hdfs dfs <command> when the command misses the …
2015-08-19 17:16:54 +03:00
mcarton
49917ce6b4 Fix #338 2015-08-19 11:00:27 +02:00
mcarton
c5e1139879 Fix some issues reported by pylint 2015-08-17 17:19:16 +02:00
mcarton
1becd92b12 Fix the open rule
It was simply wrong with `xdg-`, `gnome-` and `kde-open`.
2015-08-17 16:22:05 +02:00
mcarton
bc6b107066 Fix README and add a test so it won't happen again 2015-08-17 16:07:24 +02:00
mcarton
9b30ae0424 Handle columns it the fix_file rule 2015-08-17 16:07:24 +02:00
mcarton
88831c424f Fix the @wrap_settings annotation
It seems much more useful if it only adds settings that are not already
set.
2015-08-17 16:07:24 +02:00
mcarton
4a2f869c6d Add support for stdout in the fix_file rule
At least `pep8` and `py.test` consider errors as normal and print them
on stdout.
2015-08-17 16:07:24 +02:00
mcarton
7f0f9a966f Fix some pep8 issues, mostly spaces
Before:
    4       E101 indentation contains mixed spaces and tabs
    20      E122 continuation line missing indentation or outdented
    1       E124 closing bracket does not match visual indentation
    12      E127 continuation line over-indented for visual indent
    22      E128 continuation line under-indented for visual indent
    2       E211 whitespace before '('
    12      E302 expected 2 blank lines, found 1
    1       E303 too many blank lines (3)
    4       E402 module level import not at top of file
    123     E501 line too long (81 > 79 characters)
    2       E731 do not assign a lambda expression, use a def
    3       W191 indentation contains tabs
    20      W291 trailing whitespace
    3       W293 blank line contains whitespace
    2       W391 blank line at end of file
    69      W503 line break before binary operator

After:
    20      E122 continuation line missing indentation or outdented
    12      E127 continuation line over-indented for visual indent
    22      E128 continuation line under-indented for visual indent
    123     E501 line too long (81 > 79 characters)
    2       E731 do not assign a lambda expression, use a def
    1       W291 trailing whitespace
    68      W503 line break before binary operator
2015-08-17 16:07:10 +02:00
Vladimir Iakovlev
85647794dc Merge pull request #335 from mcarton/pacman
Add a new rule for pacman/yaourt and fix the apt_get rule
2015-08-16 03:38:50 +03:00
Michael Lee
8c9416e57f Renamed to unknown command to better match current functionality 2015-08-14 09:38:42 +01:00
mcarton
95607557d6 #277 Fix the apt_get rule with sudo 2015-08-13 18:29:04 +02:00
mcarton
e9ffe2ea9d Fix confusing sentence in the README
Ref: https://github.com/nvbn/thefuck/pull/283#issuecomment-120170664
2015-08-13 18:28:59 +02:00
Michael Lee
c08a8bddc9 More generic a solution, now works with any command that follows the same pattern of error message 2015-08-13 17:19:16 +01:00
mcarton
ca8222e764 Add the pacman_not_found rule 2015-08-13 18:16:25 +02:00
mcarton
986bbb30a7 Create thefuck.archlinux 2015-08-13 18:07:24 +02:00
Michael Lee
2cdfe105fb Reorderd be in alphabetical order 2015-08-13 16:23:11 +01:00
Michael Lee
b494c4e273 basic support for the hdfs dfs <command> when the command misses the dash 2015-08-13 13:09:19 +01:00
Vladimir Iakovlev
0ad70a1edc Merge pull request #332 from mcarton/331
#331 Fix bug with empty commands
2015-08-11 16:04:00 +03:00
mcarton
285d57eb01 #331 Fix bug with empty commands 2015-08-11 11:08:21 +02:00
nvbn
d20205249b Bump to 2.7 2015-08-11 01:17:06 +03:00
nvbn
b29113c229 #326 Add support of sudo with pipes 2015-08-11 01:15:05 +03:00
nvbn
41a0a766ce Merge branch 'master' of github.com:nvbn/thefuck 2015-08-09 22:56:00 +03:00
nvbn
6222985491 #330 Add support of a single argument 2015-08-09 22:55:48 +03:00
Vladimir Iakovlev
e09e5a9683 Merge pull request #329 from JakobGreen/master
Change failed message 'No fuck given' to the more popular 'No fucks g…
2015-08-08 04:45:42 +03:00
JakobGreen
6883d2dbeb Change failed message 'No fuck given' to the more popular 'No fucks given' 2015-08-07 14:51:51 -06:00
Vladimir Iakovlev
215c64d924 Merge pull request #327 from bugaevc/must-run-as-root
Add one more 'need root' phrase
2015-08-07 18:50:42 +03:00
Sergey Bugaev
ab76f87e01 Add one more 'need root' phrase 2015-08-06 20:33:31 +03:00
nvbn
fd759ea2ac #298 Don't suggest duplicates 2015-08-01 19:16:22 +03:00
nvbn
213e7bf74b #301 Fix UnicodeEncodeError in debug time tracker 2015-08-01 18:56:20 +03:00
Vladimir Iakovlev
1a2c1aa4e9 Merge pull request #325 from mcarton/324
Some adaptations for #324
2015-08-01 00:17:56 +03:00
mcarton
fc48e69921 Adapt the whois rule to #342 2015-07-31 22:19:16 +02:00
mcarton
88732a608e Adapt the tmux rule to #324 2015-07-31 22:19:16 +02:00
mcarton
8374be0872 Adapt the pacman rule to #324 2015-07-31 22:18:59 +02:00
mcarton
3ae01ac65d Adapt the man rule to #324 2015-07-31 21:41:07 +02:00
mcarton
4d467cce95 #324 Remove arrows in case there is only one match 2015-07-31 20:59:49 +02:00
Vladimir Iakovlev
8be353941f Merge pull request #324 from nvbn/298-variants
Add ability to select fixed command from variants
2015-07-31 15:39:57 +03:00
nvbn
d442f959e9 #298 Update readme 2015-07-31 15:36:08 +03:00
nvbn
cb2cddbdd9 #298 Fix zsh tests with BARE 2015-07-31 15:31:51 +03:00
nvbn
8632a29edc #298 Fix tests with BARE 2015-07-31 15:04:06 +03:00
nvbn
214acf56c5 #298 Wait before checking that history changed 2015-07-30 20:04:40 +03:00
nvbn
da3bc60942 #298 Fix arrow-tests on travis-ci 2015-07-30 18:39:41 +03:00
nvbn
70c89164b0 #298 Add func tests for selecting rule 2015-07-30 18:28:20 +03:00
nvbn
1a76bfd2a3 #298 Always clean-up after building container 2015-07-30 18:17:29 +03:00
nvbn
9d91b96780 #298 Simplify func tests 2015-07-29 16:30:32 +03:00
nvbn
8962cf2ec1 #298 Use eager decorator when we don't need lazines 2015-07-29 16:11:23 +03:00
nvbn
d6e80b7835 #298 Suggest more than one result in *_no_command rules 2015-07-29 16:09:26 +03:00
nvbn
4bc1cc7849 #298 Add support of list results in sudo_support 2015-07-29 15:40:21 +03:00
nvbn
e6af00ef97 #298 Fix selecting command 2015-07-29 15:33:29 +03:00
nvbn
c8550a0ce5 #298 Fix python 2 support 2015-07-29 15:22:24 +03:00
nvbn
7933e963d8 #298 Add ability to chose matched rule 2015-07-28 22:04:27 +03:00
136 changed files with 2276 additions and 1018 deletions

View File

@@ -13,6 +13,7 @@ addons:
- fish
- tcsh
- pandoc
- git
env:
- FUNCTIONAL=true BARE=true
install:

View File

@@ -14,7 +14,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
➜ fuck
sudo apt-get install vim [enter/ctrl+c]
sudo apt-get install vim [enter/↑/↓/ctrl+c]
[sudo] password for nvbn:
Reading package lists... Done
...
@@ -29,7 +29,7 @@ To push the current branch and set the remote as upstream, use
➜ fuck
git push --set-upstream origin master [enter/ctrl+c]
git push --set-upstream origin master [enter/↑/↓/ctrl+c]
Counting objects: 9, done.
...
```
@@ -42,7 +42,7 @@ No command 'puthon' found, did you mean:
zsh: command not found: puthon
➜ fuck
python [enter/ctrl+c]
python [enter/↑/↓/ctrl+c]
Python 3.4.2 (default, Oct 8 2014, 13:08:17)
...
```
@@ -55,7 +55,7 @@ Did you mean this?
branch
➜ fuck
git branch [enter/ctrl+c]
git branch [enter/↑/↓/ctrl+c]
* master
```
@@ -67,7 +67,7 @@ Did you mean this?
repl
➜ fuck
lein repl [enter/ctrl+c]
lein repl [enter/↑/↓/ctrl+c]
nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848
REPL-y 0.3.1
...
@@ -94,7 +94,15 @@ Reading package lists... Done
- pip
- python-dev
## Installation
## Installation [*experimental*]
On Ubuntu and OS X you can install `The Fuck` with installation script:
```bash
wget -O - https://raw.githubusercontent.com/nvbn/thefuck/master/install.sh | sh - && $0
```
## Manual installation
Install `The Fuck` with `pip`:
@@ -107,9 +115,9 @@ sudo pip install thefuck
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
```bash
eval "$(thefuck-alias)"
eval "$(thefuck --alias)"
# You can use whatever you want as an alias, like for Mondays:
eval "$(thefuck-alias FUCK)"
eval "$(thefuck --alias FUCK)"
```
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
@@ -161,9 +169,9 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `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;
* `gulp_not_task` &ndash; fixes misspelled gulp tasks;
* `gulp_not_task` &ndash; fixes misspelled `gulp` tasks;
* `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_no_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `history` &ndash; tries to replace command with most similar command from history;
* `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files;
@@ -173,33 +181,39 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mercurial` &ndash; fixes wrong `hg` commands;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `mvn_no_command` &ndash; adds `clean package` to `mvn`;
* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled lifecycle phases with `mvn`;
* `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'
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args';
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` &ndash; switches command from your local layout to en;
* `switch_lang` &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`;
* `tsuru_login` &ndash; runs `tsuru login` if not authenticated or session expired;
* `tsuru_not_command` &ndash; fixes wrong tsuru commands like `tsuru shell`;
* `tsuru_not_command` &ndash; fixes wrong `tsuru` commands like `tsuru shell`;
* `tmux` &ndash; fixes `tmux` commands;
* `unknown_command` &ndash; fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
* `vagrant_up` &ndash; starts up the vagrant instance;
* `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 (requires `python-commandnotfound` / `python3-commandnotfound`);
* `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`;
* `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.
* `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yaourt` if available);
* `pacman_not_found` &ndash; fixes package name with `pacman` or `yaourt`.
Bundled, but not enabled by default:
@@ -213,10 +227,14 @@ in `~/.thefuck/rules`. The rule should contain two functions:
```python
match(command: Command, settings: Settings) -> bool
get_new_command(command: Command, settings: Settings) -> str
get_new_command(command: Command, settings: Settings) -> str | list[str]
```
Also the rule can contain an optional function `side_effect(command: Command, settings: Settings) -> None`
Also the rule can contain an optional function
```python
side_effect(old_command: Command, fixed_command: str, settings: Settings) -> None
```
and optional `enabled_by_default`, `requires_output` and `priority` variables.
`Command` has three attributes: `script`, `stdout` and `stderr`.

53
install.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/sh
should_add_alias () {
[ -f $1 ] && ! grep -q thefuck $1
}
# Install os dependencies:
if [ -f $(which apt-get) ]; then
# Debian/ubuntu:
sudo apt-get update -yy
sudo apt-get install -yy python-pip python-dev command-not-found
if [[ -n $(apt-cache search python-commandnotfound) ]]; then
# In case of different python versions:
sudo apt-get install -yy python-commandnotfound
fi
else
if [ -f $(which brew) ]; then
# OS X:
brew update
brew install python
else
# Genreic way:
wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py
rm get-pip.py
fi
fi
# thefuck requires fresh versions of setuptools and pip:
sudo pip install -U pip setuptools
sudo pip install -U thefuck
# Setup aliases:
if should_add_alias ~/.bashrc; then
echo 'eval $(thefuck --alias)' >> ~/.bashrc
fi
if should_add_alias ~/.bash_profile; then
echo 'eval $(thefuck --alias)' >> ~/.bash_profile
fi
if should_add_alias ~/.zshrc; then
echo 'eval $(thefuck --alias)' >> ~/.zshrc
fi
if should_add_alias ~/.config/fish/config.fish; then
thefuck --alias >> ~/.config/fish/config.fish
fi
if should_add_alias ~/.tcshrc; then
echo 'eval `thefuck --alias`' >> ~/.tcshrc
fi

View File

@@ -5,3 +5,4 @@ wheel
setuptools>=17.1
pexpect
pypandoc
pytest-benchmark

View File

@@ -20,9 +20,9 @@ elif (3, 0) < version < (3, 3):
' ({}.{} detected).'.format(*version))
sys.exit(-1)
VERSION = '2.6'
VERSION = '2.9'
install_requires = ['psutil', 'colorama', 'six']
install_requires = ['psutil', 'colorama', 'six', 'decorator']
extras_require = {':python_version<"3.4"': ['pathlib']}
setup(name='thefuck',

View File

@@ -1,6 +1,17 @@
import pytest
from mock import Mock
@pytest.fixture
def no_memoize(monkeypatch):
monkeypatch.setattr('thefuck.utils.memoize.disabled', True)
@pytest.fixture
def settings():
return Mock(debug=False, no_colors=True)
@pytest.fixture(autouse=True)
def no_cache(monkeypatch):
monkeypatch.setattr('thefuck.utils.cache.disabled', True)

View File

@@ -1,10 +1,16 @@
from pexpect import TIMEOUT
def _set_confirmation(proc, require):
proc.sendline(u'mkdir -p ~/.thefuck')
proc.sendline(
u'echo "require_confirmation = {}" > ~/.thefuck/settings.py'.format(
require))
def with_confirmation(proc):
"""Ensures that command can be fixed when confirmation enabled."""
proc.sendline(u'mkdir -p ~/.thefuck')
proc.sendline(u'echo "require_confirmation = True" > ~/.thefuck/settings.py')
_set_confirmation(proc, True)
proc.sendline(u'ehco test')
@@ -17,10 +23,10 @@ def with_confirmation(proc):
assert proc.expect([TIMEOUT, u'test'])
def history_changed(proc):
def history_changed(proc, to):
"""Ensures that history changed."""
proc.send('\033[A')
assert proc.expect([TIMEOUT, u'echo test'])
assert proc.expect([TIMEOUT, to])
def history_not_changed(proc):
@@ -29,10 +35,29 @@ def history_not_changed(proc):
assert proc.expect([TIMEOUT, u'fuck'])
def select_command_with_arrows(proc):
"""Ensures that command can be selected with arrow keys."""
_set_confirmation(proc, True)
proc.sendline(u'git h')
assert proc.expect([TIMEOUT, u"git: 'h' is not a git command."])
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'git show'])
proc.send('\033[B')
assert proc.expect([TIMEOUT, u'git push'])
proc.send('\033[B')
assert proc.expect([TIMEOUT, u'git help'])
proc.send('\033[A')
assert proc.expect([TIMEOUT, u'git push'])
proc.send('\n')
assert proc.expect([TIMEOUT, u'Not a git repository'])
def refuse_with_confirmation(proc):
"""Ensures that fix can be refused when confirmation enabled."""
proc.sendline(u'mkdir -p ~/.thefuck')
proc.sendline(u'echo "require_confirmation = True" > ~/.thefuck/settings.py')
_set_confirmation(proc, True)
proc.sendline(u'ehco test')
@@ -47,8 +72,7 @@ def refuse_with_confirmation(proc):
def without_confirmation(proc):
"""Ensures that command can be fixed when confirmation disabled."""
proc.sendline(u'mkdir -p ~/.thefuck')
proc.sendline(u'echo "require_confirmation = False" > ~/.thefuck/settings.py')
_set_confirmation(proc, False)
proc.sendline(u'ehco test')

View File

@@ -1,51 +1,53 @@
import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, history_changed, history_not_changed
refuse_with_confirmation, history_changed, history_not_changed, \
select_command_with_arrows
from tests.functional.utils import spawn, functional, images
containers = images(('ubuntu-python3-bash', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
'''),
('ubuntu-python2-bash', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
'''))
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'bash') as proc:
proc.sendline(u"export PS1='$ '")
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'touch $HISTFILE')
with_confirmation(proc)
history_changed(proc)
@pytest.fixture(params=containers)
def proc(request):
tag, dockerfile = request.param
proc = spawn(request, tag, dockerfile, u'bash')
proc.sendline(u"export PS1='$ '")
proc.sendline(u'eval $(thefuck --alias)')
proc.sendline(u'echo > $HISTFILE')
return proc
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_refuse_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'bash') as proc:
proc.sendline(u"export PS1='$ '")
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'touch $HISTFILE')
refuse_with_confirmation(proc)
history_not_changed(proc)
def test_with_confirmation(proc):
with_confirmation(proc)
history_changed(proc, u'echo test')
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_without_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'bash') as proc:
proc.sendline(u"export PS1='$ '")
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'touch $HISTFILE')
without_confirmation(proc)
history_changed(proc)
def test_select_command_with_arrows(proc):
select_command_with_arrows(proc)
history_changed(proc, u'git push')
@functional
def test_refuse_with_confirmation(proc):
refuse_with_confirmation(proc)
history_not_changed(proc)
@functional
def test_without_confirmation(proc):
without_confirmation(proc)
history_changed(proc, u'echo test')

View File

@@ -1,53 +1,59 @@
import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation
refuse_with_confirmation, select_command_with_arrows
from tests.functional.utils import spawn, functional, images, bare
containers = images(('ubuntu-python3-fish', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev fish
RUN apt-get install -yy python3 python3-pip python3-dev fish git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy fish
'''),
('ubuntu-python2-fish', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev fish
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy fish
'''))
@functional
@pytest.mark.skipif(
bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71')
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'fish') as proc:
proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish')
proc.sendline(u'fish')
with_confirmation(proc)
@pytest.fixture(params=containers)
def proc(request):
tag, dockerfile = request.param
proc = spawn(request, tag, dockerfile, u'fish')
proc.sendline(u'thefuck --alias > ~/.config/fish/config.fish')
proc.sendline(u'fish')
return proc
@functional
@pytest.mark.skipif(
bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71')
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_refuse_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'fish') as proc:
proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish')
proc.sendline(u'fish')
refuse_with_confirmation(proc)
def test_with_confirmation(proc):
with_confirmation(proc)
@functional
@pytest.mark.skipif(
bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71')
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_without_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'fish') as proc:
proc.sendline(u'thefuck-alias > ~/.config/fish/config.fish')
proc.sendline(u'fish')
without_confirmation(proc)
def test_select_command_with_arrows(proc):
select_command_with_arrows(proc)
@functional
@pytest.mark.skipif(
bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71')
def test_refuse_with_confirmation(proc):
refuse_with_confirmation(proc)
@functional
@pytest.mark.skipif(
bool(bare), reason='https://github.com/travis-ci/apt-source-whitelist/issues/71')
def test_without_confirmation(proc):
without_confirmation(proc)
# TODO: ensure that history changes.

View File

@@ -0,0 +1,25 @@
import pytest
from pexpect import TIMEOUT
from tests.functional.utils import spawn, functional, bare
envs = ((u'bash', 'ubuntu-bash', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy bash
'''), (u'bash', 'generic-bash', u'''
FROM fedora:latest
RUN dnf install -yy python-devel sudo which gcc
'''))
@functional
@pytest.mark.skipif(
bool(bare), reason="Can't be tested in bare run")
@pytest.mark.parametrize('shell, tag, dockerfile', envs)
def test_installation(request, shell, tag, dockerfile):
proc = spawn(request, tag, dockerfile, shell, install=False)
proc.sendline(u'cat /src/install.sh | sh - && $0')
proc.sendline(u'thefuck --version')
assert proc.expect([TIMEOUT, u'The Fuck'], timeout=600)
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'No fucks given'])

View File

@@ -0,0 +1,56 @@
from pexpect import TIMEOUT
import pytest
import time
from tests.functional.utils import spawn, functional, bare
dockerfile = u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN adduser --disabled-password --gecos '' test
ENV SEED "{seed}"
COPY thefuck /src
WORKDIR /src
RUN pip install .
USER test
RUN echo 'eval $(thefuck --alias)' > /home/test/.bashrc
RUN echo > /home/test/.bash_history
RUN git config --global user.email "you@example.com"
RUN git config --global user.name "Your Name"
'''.format(seed=time.time())
@pytest.fixture
def proc(request):
return spawn(request, 'ubuntu-python3-bash-performance',
dockerfile, u'bash', install=False, copy_src=True)
def plot(proc):
proc.sendline(u'cd /home/test/')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'No fucks given'])
proc.sendline(u'git init')
proc.sendline(u'git add .')
proc.sendline(u'git commit -a -m init')
proc.sendline(u'git brnch')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'git branch'])
proc.send('\n')
assert proc.expect([TIMEOUT, u'master'])
proc.sendline(u'echo test')
proc.sendline(u'echo tst')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'echo test'])
proc.send('\n')
assert proc.expect([TIMEOUT, u'test'])
@functional
@pytest.mark.skipif(
bool(bare), reason='Would lie on a bare run')
@pytest.mark.benchmark(min_rounds=10)
def test_performance(proc, benchmark):
assert benchmark(plot, proc) is None

View File

@@ -1,47 +1,51 @@
import pytest
from tests.functional.utils import spawn, functional, images
from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation
refuse_with_confirmation, select_command_with_arrows
containers = images(('ubuntu-python3-tcsh', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev tcsh
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy tcsh
'''),
('ubuntu-python2-tcsh', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev tcsh
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy tcsh
'''))
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'tcsh') as proc:
proc.sendline(u'tcsh')
proc.sendline(u'eval `thefuck-alias`')
with_confirmation(proc)
@pytest.fixture(params=containers)
def proc(request):
tag, dockerfile = request.param
proc = spawn(request, tag, dockerfile, u'tcsh')
proc.sendline(u'tcsh')
proc.sendline(u'eval `thefuck --alias`')
return proc
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_refuse_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'tcsh') as proc:
proc.sendline(u'tcsh')
proc.sendline(u'eval `thefuck-alias`')
refuse_with_confirmation(proc)
def test_with_confirmation(proc):
with_confirmation(proc)
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_without_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'tcsh') as proc:
proc.sendline(u'tcsh')
proc.sendline(u'eval `thefuck-alias`')
without_confirmation(proc)
def test_select_command_with_arrows(proc):
select_command_with_arrows(proc)
@functional
def test_refuse_with_confirmation(proc):
refuse_with_confirmation(proc)
@functional
def test_without_confirmation(proc):
without_confirmation(proc)
# TODO: ensure that history changes.

View File

@@ -1,51 +1,57 @@
import pytest
from tests.functional.utils import spawn, functional, images
from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, history_changed, history_not_changed
refuse_with_confirmation, history_changed, history_not_changed, select_command_with_arrows
containers = images(('ubuntu-python3-zsh', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev zsh
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
RUN apt-get install -yy zsh
'''),
('ubuntu-python2-zsh', u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev zsh
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy zsh
'''))
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'zsh') as proc:
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'export HISTFILE=~/.zsh_history')
proc.sendline(u'touch $HISTFILE')
with_confirmation(proc)
history_changed(proc)
@pytest.fixture(params=containers)
def proc(request):
tag, dockerfile = request.param
proc = spawn(request, tag, dockerfile, u'zsh')
proc.sendline(u'eval $(thefuck --alias)')
proc.sendline(u'export HISTFILE=~/.zsh_history')
proc.sendline(u'echo > $HISTFILE')
proc.sendline(u'export SAVEHIST=100')
proc.sendline(u'export HISTSIZE=100')
proc.sendline(u'setopt INC_APPEND_HISTORY')
return proc
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_refuse_with_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'zsh') as proc:
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'export HISTFILE=~/.zsh_history')
proc.sendline(u'touch $HISTFILE')
refuse_with_confirmation(proc)
history_not_changed(proc)
def test_with_confirmation(proc):
with_confirmation(proc)
history_changed(proc, u'echo test')
@functional
@pytest.mark.parametrize('tag, dockerfile', containers)
def test_without_confirmation(tag, dockerfile):
with spawn(tag, dockerfile, u'zsh') as proc:
proc.sendline(u'eval $(thefuck-alias)')
proc.sendline(u'export HISTFILE=~/.zsh_history')
proc.sendline(u'touch $HISTFILE')
without_confirmation(proc)
history_changed(proc)
def test_select_command_with_arrows(proc):
select_command_with_arrows(proc)
history_changed(proc, u'git push')
@functional
def test_refuse_with_confirmation(proc):
refuse_with_confirmation(proc)
history_not_changed(proc)
@functional
def test_without_confirmation(proc):
without_confirmation(proc)
history_changed(proc, u'echo test')

View File

@@ -1,45 +1,56 @@
import pytest
import os
from contextlib import contextmanager
import subprocess
import shutil
from tempfile import mkdtemp
from pathlib import Path
import sys
import pexpect
import pytest
from tests.utils import root
root = str(Path(__file__).parent.parent.parent.resolve())
bare = os.environ.get('BARE')
enabled = os.environ.get('FUNCTIONAL')
def build_container(tag, dockerfile):
def build_container(tag, dockerfile, copy_src=False):
tmpdir = mkdtemp()
with Path(tmpdir).joinpath('Dockerfile').open('w') as file:
file.write(dockerfile)
if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir],
cwd=root) != 0:
raise Exception("Can't build a container")
shutil.rmtree(tmpdir)
try:
if copy_src:
subprocess.call(['cp', '-a', str(root), tmpdir])
dockerfile_path = Path(tmpdir).joinpath('Dockerfile')
with dockerfile_path.open('w') as file:
file.write(dockerfile)
if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir]) != 0:
raise Exception("Can't build a container")
finally:
shutil.rmtree(tmpdir)
@contextmanager
def spawn(tag, dockerfile, cmd):
def spawn(request, tag, dockerfile, cmd, install=True, copy_src=False):
if bare:
proc = pexpect.spawnu(cmd)
else:
tag = 'thefuck/{}'.format(tag)
build_container(tag, dockerfile)
proc = pexpect.spawnu('docker run --volume {}:/src --tty=true '
build_container(tag, dockerfile, copy_src)
proc = pexpect.spawnu('docker run --rm=true --volume {}:/src --tty=true '
'--interactive=true {} {}'.format(root, tag, cmd))
proc.sendline('pip install /src')
if install:
proc.sendline('pip install /src')
proc.sendline('cd /')
proc.logfile = sys.stdout
try:
yield proc
finally:
proc.terminate(force=bare)
def _finalizer():
proc.terminate()
if not bare:
container_id = subprocess.check_output(['docker', 'ps']) \
.decode('utf-8').split('\n')[-2].split()[0]
subprocess.check_call(['docker', 'kill', container_id])
request.addfinalizer(_finalizer)
return proc
def images(*items):

View File

@@ -16,6 +16,8 @@ def test_match(command):
@pytest.mark.parametrize('command, return_value', [
(Command(script='vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command(script='sudo 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')
@@ -38,7 +40,9 @@ def test_not_match(command):
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')])
(Command('convert'), 'sudo apt-get install imagemagick && convert'),
(Command('sudo vim'), 'sudo apt-get install vim && sudo vim'),
(Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command
@@ -47,6 +51,11 @@ def test_get_new_command(command, new_command):
(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')]),
(Command('sudo vim'), 'sudo apt-get install vim && sudo vim',
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert',
[('imagemagick', 'main'),
('graphicsmagick-imagemagick-compat', 'universe')])])
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
@@ -55,5 +64,3 @@ 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,25 @@
import pytest
from thefuck.rules.apt_get_search import get_new_command, match
from tests.utils import Command
def test_match():
assert match(Command('apt-get search foo'), None)
@pytest.mark.parametrize('command', [
Command('apt-cache search foo'),
Command('aptitude search foo'),
Command('apt search foo'),
Command('apt-get install foo'),
Command('apt-get source foo'),
Command('apt-get clean'),
Command('apt-get remove'),
Command('apt-get update')
])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():
assert get_new_command(Command('apt-get search foo'), None) == 'apt-cache search foo'

View File

@@ -1,6 +1,6 @@
import pytest
from thefuck.rules.brew_install import match, get_new_command
from thefuck.rules.brew_install import brew_formulas
from thefuck.rules.brew_install import _get_formulas
from tests.utils import Command
@@ -20,9 +20,7 @@ def brew_already_installed():
def _is_not_okay_to_test():
if 'elasticsearch' not in brew_formulas:
return True
return False
return 'elasticsearch' not in _get_formulas()
@pytest.mark.skipif(_is_not_okay_to_test(),

View File

@@ -1,6 +1,6 @@
import pytest
from thefuck.rules.brew_unknown_command import match, get_new_command
from thefuck.rules.brew_unknown_command import brew_commands
from thefuck.rules.brew_unknown_command import _brew_commands
from tests.utils import Command
@@ -16,13 +16,14 @@ def brew_unknown_cmd2():
def test_match(brew_unknown_cmd):
assert match(Command('brew inst', stderr=brew_unknown_cmd), None)
for command in brew_commands:
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'
None) == ['brew list', 'brew install', 'brew uninstall']
assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2),
None) == 'brew install'
cmds = get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2), None)
assert 'brew install' in cmds
assert 'brew uninstall' in cmds

View File

@@ -5,34 +5,38 @@ from tests.utils import Command
@pytest.fixture
def composer_not_command():
return """
[InvalidArgumentException]
Command "udpate" is not defined.
Did you mean this?
update
"""
# that weird spacing is part of the actual command output
return (
'\n'
'\n'
' \n'
' [InvalidArgumentException] \n'
' Command "udpate" is not defined. \n'
' Did you mean this? \n'
' update \n'
' \n'
'\n'
'\n'
)
@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
"""
# that weird spacing is part of the actual command output
return (
'\n'
'\n'
' \n'
' [InvalidArgumentException] \n'
' Command "pdate" is not defined. \n'
' Did you mean one of these? \n'
' selfupdate \n'
' self-update \n'
' update \n'
' \n'
'\n'
'\n'
)
def test_match(composer_not_command, composer_not_command_one_of_this):

View File

@@ -40,6 +40,7 @@ parametrize_script = pytest.mark.parametrize('script, fixed', [
('tar -xvf {}', 'mkdir -p foo && tar -xvf {} -C foo'),
('tar --extract -f {}', 'mkdir -p foo && tar --extract -f {} -C foo')])
@parametrize_filename
@parametrize_script
def test_match(tar_error, filename, script, fixed):
@@ -51,7 +52,7 @@ def test_match(tar_error, filename, script, fixed):
@parametrize_script
def test_side_effect(tar_error, filename, script, fixed):
tar_error(filename)
side_effect(Command(script=script.format(filename)), None)
side_effect(Command(script=script.format(filename)), None, None)
assert(os.listdir('.') == [filename])

View File

@@ -34,7 +34,7 @@ def test_match(zip_error, script):
'unzip foo',
'unzip foo.zip'])
def test_side_effect(zip_error, script):
side_effect(Command(script=script), None)
side_effect(Command(script=script), None, None)
assert(os.listdir('.') == ['foo.zip'])

View File

@@ -122,8 +122,8 @@ def test_not_match(script, stderr):
@pytest.mark.usefixtures('docker_help')
@pytest.mark.parametrize('wrong, fixed', [
('pes', 'ps'),
('tags', 'tag')])
('pes', ['ps', 'push', 'pause']),
('tags', ['tag', 'stats', 'images'])])
def test_get_new_command(wrong, fixed):
command = Command('docker {}'.format(wrong), stderr=stderr(wrong))
assert get_new_command(command, None) == 'docker {}'.format(fixed)
assert get_new_command(command, None) == ['docker {}'.format(x) for x in fixed]

View File

@@ -2,11 +2,12 @@ import pytest
import os
from thefuck.rules.fix_file import match, get_new_command
from tests.utils import Command
from thefuck.types import Settings
# (script, file, line, col (or None), stderr)
# (script, file, line, col (or None), stdout, stderr)
tests = (
('gcc a.c', 'a.c', 3, 1,
('gcc a.c', 'a.c', 3, 1, '',
"""
a.c: In function 'main':
a.c:3:1: error: expected expression before '}' token
@@ -14,47 +15,47 @@ a.c:3:1: error: expected expression before '}' token
^
"""),
('clang a.c', 'a.c', 3, 1,
('clang a.c', 'a.c', 3, 1, '',
"""
a.c:3:1: error: expected expression
}
^
"""),
('perl a.pl', 'a.pl', 3, None,
('perl a.pl', 'a.pl', 3, None, '',
"""
syntax error at a.pl line 3, at EOF
Execution of a.pl aborted due to compilation errors.
"""),
('perl a.pl', 'a.pl', 2, None,
('perl a.pl', 'a.pl', 2, None, '',
"""
Search pattern not terminated at a.pl line 2.
"""),
('sh a.sh', 'a.sh', 2, None,
('sh a.sh', 'a.sh', 2, None, '',
"""
a.sh: line 2: foo: command not found
"""),
('zsh a.sh', 'a.sh', 2, None,
('zsh a.sh', 'a.sh', 2, None, '',
"""
a.sh:2: command not found: foo
"""),
('bash a.sh', 'a.sh', 2, None,
('bash a.sh', 'a.sh', 2, None, '',
"""
a.sh: line 2: foo: command not found
"""),
('rustc a.rs', 'a.rs', 2, 5,
('rustc a.rs', 'a.rs', 2, 5, '',
"""
a.rs:2:5: 2:6 error: unexpected token: `+`
a.rs:2 +
^
"""),
('cargo build', 'src/lib.rs', 3, 5,
('cargo build', 'src/lib.rs', 3, 5, '',
"""
Compiling test v0.1.0 (file:///tmp/fix-error/test)
src/lib.rs:3:5: 3:6 error: unexpected token: `+`
@@ -65,7 +66,7 @@ Could not compile `test`.
To learn more, run the command again with --verbose.
"""),
('python a.py', 'a.py', 2, None,
('python a.py', 'a.py', 2, None, '',
"""
File "a.py", line 2
+
@@ -73,7 +74,7 @@ To learn more, run the command again with --verbose.
SyntaxError: invalid syntax
"""),
('python a.py', 'a.py', 8, None,
('python a.py', 'a.py', 8, None, '',
"""
Traceback (most recent call last):
File "a.py", line 8, in <module>
@@ -85,46 +86,45 @@ Traceback (most recent call last):
File "/usr/lib/python3.4/re.py", line 293, in _compile
raise TypeError("first argument must be string or compiled pattern")
TypeError: first argument must be string or compiled pattern
"""
),
"""),
('ruby a.rb', 'a.rb', 3, None,
('ruby a.rb', 'a.rb', 3, None, '',
"""
a.rb:3: syntax error, unexpected keyword_end
"""),
('lua a.lua', 'a.lua', 2, None,
('lua a.lua', 'a.lua', 2, None, '',
"""
lua: a.lua:2: unexpected symbol near '+'
"""),
('fish a.sh', '/tmp/fix-error/a.sh', 2, None,
('fish a.sh', '/tmp/fix-error/a.sh', 2, None, '',
"""
fish: Unknown command 'foo'
/tmp/fix-error/a.sh (line 2): foo
^
"""),
('./a', './a', 2, None,
('./a', './a', 2, None, '',
"""
awk: ./a:2: BEGIN { print "Hello, world!" + }
awk: ./a:2: ^ syntax error
"""),
('llc a.ll', 'a.ll', 1, None,
('llc a.ll', 'a.ll', 1, 2, '',
"""
llc: a.ll:1:1: error: expected top-level entity
llc: a.ll:1:2: error: expected top-level entity
+
^
"""),
('go build a.go', 'a.go', 1, None,
('go build a.go', 'a.go', 1, 2, '',
"""
can't load package:
a.go:1:1: expected 'package', found '+'
a.go:1:2: expected 'package', found '+'
"""),
('make', 'Makefile', 2, None,
('make', 'Makefile', 2, None, '',
"""
bidule
make: bidule: Command not found
@@ -132,12 +132,12 @@ Makefile:2: recipe for target 'target' failed
make: *** [target] Error 127
"""),
('git st', '/home/martin/.config/git/config', 1, None,
('git st', '/home/martin/.config/git/config', 1, None, '',
"""
fatal: bad config file line 1 in /home/martin/.config/git/config
"""),
('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5,
('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5, '',
"""
/Users/pablo/Workspace/barebones/fuck.js:2
conole.log(arg); // this should read console.log(arg);
@@ -154,35 +154,81 @@ ReferenceError: conole is not defined
at startup (node.js:129:16)
at node.js:814:3
"""),
('pep8', './tests/rules/test_systemctl.py', 17, 80,
"""
./tests/rules/test_systemctl.py:17:80: E501 line too long (93 > 79 characters)
./tests/rules/test_systemctl.py:18:80: E501 line too long (103 > 79 characters)
./tests/rules/test_whois.py:20:80: E501 line too long (89 > 79 characters)
./tests/rules/test_whois.py:22:80: E501 line too long (83 > 79 characters)
""", ''),
('py.test', '/home/thefuck/tests/rules/test_fix_file.py', 218, None,
"""
monkeypatch = <_pytest.monkeypatch.monkeypatch object at 0x7fdb76a25b38>
test = ('fish a.sh', '/tmp/fix-error/a.sh', 2, None, '', "\\nfish: Unknown command 'foo'\\n/tmp/fix-error/a.sh (line 2): foo\\n ^\\n")
@pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize')
def test_get_new_command(monkeypatch, test):
> mocker.patch('os.path.isfile', return_value=True)
E NameError: name 'mocker' is not defined
/home/thefuck/tests/rules/test_fix_file.py:218: NameError
""", ''),
)
@pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize')
def test_match(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setenv('EDITOR', 'dummy_editor')
assert match(Command(stderr=test[4]), None)
assert match(Command(stdout=test[4], stderr=test[5]), None)
@pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize')
def test_no_editor(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=True)
if 'EDITOR' in os.environ:
monkeypatch.delenv('EDITOR')
assert not match(Command(stderr=test[4]), None)
assert not match(Command(stdout=test[4], stderr=test[5]), None)
@pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize')
def test_not_file(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=False)
monkeypatch.setenv('EDITOR', 'dummy_editor')
assert not match(Command(stderr=test[4]), None)
assert not match(Command(stdout=test[4], stderr=test[5]), None)
@pytest.mark.parametrize('test', tests)
def test_get_new_command(monkeypatch, test):
@pytest.mark.usefixtures('no_memoize')
def test_get_new_command(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setenv('EDITOR', 'dummy_editor')
assert (get_new_command(Command(script=test[0], stderr=test[4]), None) ==
'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
cmd = Command(script=test[0], stdout=test[4], stderr=test[5])
#assert (get_new_command(cmd, Settings({})) ==
# 'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
@pytest.mark.parametrize('test', tests)
@pytest.mark.usefixtures('no_memoize')
def test_get_new_command_with_settings(mocker, monkeypatch, test):
mocker.patch('os.path.isfile', return_value=True)
monkeypatch.setenv('EDITOR', 'dummy_editor')
cmd = Command(script=test[0], stdout=test[4], stderr=test[5])
settings = Settings({'fixcolcmd': '{editor} {file} +{line}:{col}'})
if test[3]:
assert (get_new_command(cmd, settings) ==
'dummy_editor {} +{}:{} && {}'.format(test[1], test[2], test[3], test[0]))
else:
assert (get_new_command(cmd, settings) ==
'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))

View File

@@ -2,6 +2,7 @@ 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)

View File

@@ -10,7 +10,7 @@ usage: git stash list [<options>]
or: git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]
or: git stash branch <branchname> [<stash>]
or: git stash [save [--patch] [-k|--[no-]keep-index] [-q|--quiet]
[-u|--include-untracked] [-a|--all] [<message>]]
\t\t [-u|--include-untracked] [-a|--all] [<message>]]
or: git stash clear
'''

View File

@@ -30,8 +30,8 @@ def git_not_command_closest():
return '''git: 'tags' is not a git command. See 'git --help'.
Did you mean one of these?
stage
tag
\tstage
\ttag
'''
@@ -50,8 +50,8 @@ def test_match(git_not_command, git_command, git_not_command_one_of_this):
def test_get_new_command(git_not_command, git_not_command_one_of_this,
git_not_command_closest):
assert get_new_command(Command('git brnch', stderr=git_not_command), None) \
== 'git branch'
== ['git branch']
assert get_new_command(Command('git st', stderr=git_not_command_one_of_this),
None) == 'git status'
None) == ['git stats', 'git stash', 'git stage']
assert get_new_command(Command('git tags', stderr=git_not_command_closest),
None) == 'git tag'
None) == ['git tag', 'git stage']

View File

@@ -4,7 +4,7 @@ from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='go run foo'),
Command(script='go run foo'),
Command(script='go run bar')])
def test_match(command):
assert match(command, None)

View File

@@ -25,4 +25,4 @@ def test_get_new_command(mocker):
mocker.patch('thefuck.rules.gulp_not_task.get_gulp_tasks', return_value=[
'serve', 'build', 'default'])
command = Command('gulp srve', stdout('srve'))
assert get_new_command(command, None) == 'gulp serve'
assert get_new_command(command, None) == ['gulp serve', 'gulp default']

View File

@@ -27,8 +27,8 @@ def test_not_match(script, stderr):
@pytest.mark.parametrize('cmd, result', [
('log', 'heroku logs'),
('pge', 'heroku pg')])
('log', ['heroku logs', 'heroku pg']),
('pge', ['heroku pg', 'heroku logs'])])
def test_get_new_command(cmd, result):
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))
assert get_new_command(command, None) == result

View File

@@ -1,6 +1,6 @@
import pytest
from mock import Mock
from thefuck.rules.lein_not_task import match, get_new_command
from tests.utils import Command
@pytest.fixture
@@ -9,14 +9,15 @@ def is_not_task():
Did you mean this?
repl
jar
'''
def test_match(is_not_task):
assert match(Mock(script='lein rpl', stderr=is_not_task), None)
assert not match(Mock(script='ls', stderr=is_not_task), None)
assert match(Command(script='lein rpl', stderr=is_not_task), None)
assert not match(Command(script='ls', stderr=is_not_task), None)
def test_get_new_command(is_not_task):
assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task),
None) == 'lein repl --help'
assert get_new_command(Command(script='lein rpl --help', stderr=is_not_task),
None) == ['lein repl --help', 'lein jar --help']

View File

@@ -1,16 +1,16 @@
from mock import patch, Mock
from thefuck.rules.ls_lah import match, get_new_command
from tests.utils import 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)
assert match(Command(script='ls'), None)
assert match(Command(script='ls file.py'), None)
assert match(Command(script='ls /opt'), None)
assert not match(Command(script='ls -lah /opt'), None)
assert not match(Command(script='pacman -S binutils'), None)
assert not match(Command(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'
assert get_new_command(Command(script='ls file.py'), None) == 'ls -lah file.py'
assert get_new_command(Command(script='ls'), None) == 'ls -lah'

View File

@@ -23,7 +23,7 @@ def test_not_match(command):
@pytest.mark.parametrize('command, new_command', [
(Command('man read'), 'man 3 read'),
(Command('man read'), ['man 3 read', 'man 2 read']),
(Command('man 2 read'), 'man 3 read'),
(Command('man 3 read'), 'man 2 read'),
(Command('man -s2 read'), 'man -s3 read'),

View File

@@ -2,7 +2,7 @@ import pytest
from tests.utils import Command
from thefuck.rules.mercurial import (
extract_possisiblities, match, get_new_command
extract_possibilities, match, get_new_command
)
@@ -96,8 +96,8 @@ def test_not_match(command):
'\n rebase recover remove rename resolve revert'
)), ['rebase', 'recover', 'remove', 'rename', 'resolve', 'revert']),
])
def test_extract_possisiblities(command, possibilities):
assert extract_possisiblities(command) == possibilities
def test_extract_possibilities(command, possibilities):
assert extract_possibilities(command) == possibilities
@pytest.mark.parametrize('command, new_command', [

View File

@@ -3,20 +3,29 @@ 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',
stderr='mkdir: foo/bar: No such file or directory'),
None)
@pytest.mark.parametrize('command', [
Command('mkdir foo/bar/baz', stderr='mkdir: foo/bar: No such file or directory'),
Command('./bin/hdfs dfs -mkdir foo/bar/baz', stderr='mkdir: `foo/bar/baz\': No such file or directory'),
Command('hdfs dfs -mkdir foo/bar/baz', stderr='mkdir: `foo/bar/baz\': No such file or directory')
])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command('mkdir foo/bar/baz'),
Command('mkdir foo/bar/baz', stderr='foo bar baz'),
Command('hdfs dfs -mkdir foo/bar/baz'),
Command('./bin/hdfs dfs -mkdir 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'
@pytest.mark.parametrize('command, new_command', [
(Command('mkdir foo/bar/baz'), 'mkdir -p foo/bar/baz'),
(Command('hdfs dfs -mkdir foo/bar/baz'), 'hdfs dfs -mkdir -p foo/bar/baz'),
(Command('./bin/hdfs dfs -mkdir foo/bar/baz'), './bin/hdfs dfs -mkdir -p foo/bar/baz')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,40 @@
import pytest
from thefuck.rules.mvn_no_command import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='mvn', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='mvn clean', stdout="""
[INFO] Scanning for projects...[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building test 0.2
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
[INFO] Deleting /home/mlk/code/test/target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.477s
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
[INFO] Final Memory: 6M/240M
[INFO] ------------------------------------------------------------------------
"""),
Command(script='mvn --help'),
Command(script='mvn -v')
])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='mvn', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean package', 'mvn clean install']),
(Command(script='mvn -N', stdout='[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn -N clean package', 'mvn -N clean install'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,40 @@
import pytest
from thefuck.rules.mvn_unknown_lifecycle_phase import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='mvn cle', stdout='[ERROR] Unknown lifecycle phase "cle". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='mvn clean', stdout="""
[INFO] Scanning for projects...[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building test 0.2
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ test ---
[INFO] Deleting /home/mlk/code/test/target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.477s
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
[INFO] Final Memory: 6M/240M
[INFO] ------------------------------------------------------------------------
"""),
Command(script='mvn --help'),
Command(script='mvn -v')
])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='mvn cle', stdout='[ERROR] Unknown lifecycle phase "cle". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean', 'mvn compile']),
(Command(script='mvn claen package', stdout='[ERROR] Unknown lifecycle phase "claen". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]'), ['mvn clean package'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -22,8 +22,8 @@ def test_get_new_command():
assert get_new_command(
Command(stderr='vom: not found',
script='vom file.py'),
None) == 'vim file.py'
None) == ['vim file.py']
assert get_new_command(
Command(stderr='fucck: not found',
script='fucck'),
Command) == 'fsck'
Command) == ['fsck']

View File

@@ -9,7 +9,10 @@ from tests.utils import Command
Command(script='open foo.org'),
Command(script='open foo.net'),
Command(script='open foo.se'),
Command(script='open foo.io')])
Command(script='open foo.io'),
Command(script='xdg-open foo.com'),
Command(script='gnome-open foo.com'),
Command(script='kde-open foo.com')])
def test_match(command):
assert match(command, None)
@@ -20,6 +23,9 @@ def test_match(command):
(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')])
(Command('open foo.io'), 'open http://foo.io'),
(Command('xdg-open foo.io'), 'xdg-open http://foo.io'),
(Command('gnome-open foo.io'), 'gnome-open http://foo.io'),
(Command('kde-open foo.io'), 'kde-open http://foo.io')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -7,17 +7,14 @@ 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_SUDO = 'core/sudo 1.8.13-13/usr/bin/sudo'
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
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
'''
extra/vim-python3 7.4.712-1 \t/usr/bin/vim'''
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
@@ -32,7 +29,7 @@ def test_match(command):
@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('thefuck.specific.archlinux.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
@@ -46,23 +43,39 @@ def test_not_match(command):
assert not match(command, None)
sudo_vim_possibilities = ['{} -S extra/gvim && sudo vim',
'{} -S extra/gvim-python3 && sudo vim',
'{} -S extra/vim && sudo vim',
'{} -S extra/vim-minimal && sudo vim',
'{} -S extra/vim-python3 && sudo vim']
sudo_vim_possibilities = [s.format(pacman_cmd) for s in sudo_vim_possibilities]
vim_possibilities = ['{} -S extra/gvim && vim',
'{} -S extra/gvim-python3 && vim',
'{} -S extra/vim && vim',
'{} -S extra/vim-minimal && vim',
'{} -S extra/vim-python3 && vim']
vim_possibilities = [s.format(pacman_cmd) for s in vim_possibilities]
@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))])
(Command('vim'), vim_possibilities),
(Command('sudo vim'), sudo_vim_possibilities),
(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')
(Command('vim'), vim_possibilities, PKGFILE_OUTPUT_VIM),
(Command('sudo vim'), sudo_vim_possibilities, PKGFILE_OUTPUT_VIM),
(Command('convert'), ['{} -S extra/imagemagick && convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT),
(Command('sudo'), ['{} -S core/sudo && sudo'.format(pacman_cmd)], PKGFILE_OUTPUT_SUDO),
(Command('sudo convert'), ['{} -S extra/imagemagick && sudo convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT)])
@patch('thefuck.specific.archlinux.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

View File

@@ -0,0 +1,48 @@
import pytest
from mock import patch
from thefuck.rules import pacman_not_found
from thefuck.rules.pacman_not_found import match, get_new_command
from tests.utils import Command
PKGFILE_OUTPUT_LLC = '''extra/llvm 3.6.0-5 /usr/bin/llc
extra/llvm35 3.5.2-13/usr/bin/llc'''
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
reason='Skip if pacman is not available')
@pytest.mark.parametrize('command', [
Command(script='yaourt -S llc', stderr='error: target not found: llc'),
Command(script='pacman llc', stderr='error: target not found: llc'),
Command(script='sudo pacman llc', stderr='error: target not found: llc')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='yaourt -S llc', stderr='error: target not found: llc'),
Command(script='pacman llc', stderr='error: target not found: llc'),
Command(script='sudo pacman llc', stderr='error: target not found: llc')])
@patch('thefuck.specific.archlinux.subprocess')
def test_match_mocked(subp_mock, command):
subp_mock.check_output.return_value = PKGFILE_OUTPUT_LLC
assert match(command, None)
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
reason='Skip if pacman is not available')
@pytest.mark.parametrize('command, fixed', [
(Command(script='yaourt -S llc', stderr='error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
(Command(script='pacman -S llc', stderr='error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
(Command(script='sudo pacman -S llc', stderr='error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
def test_get_new_command(command, fixed):
assert get_new_command(command, None) == fixed
@pytest.mark.parametrize('command, fixed', [
(Command(script='yaourt -S llc', stderr='error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
(Command(script='pacman -S llc', stderr='error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
(Command(script='sudo pacman -S llc', stderr='error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
@patch('thefuck.specific.archlinux.subprocess')
def test_get_new_command_mocked(subp_mock, command, fixed):
subp_mock.check_output.return_value = PKGFILE_OUTPUT_LLC
assert get_new_command(command, None) == fixed

View File

@@ -5,17 +5,27 @@ from tests.utils import Command
@pytest.mark.parametrize('command', [
Command('rm foo', stderr='rm: foo: is a directory'),
Command('rm foo', stderr='rm: foo: Is a directory')])
Command('rm foo', stderr='rm: foo: Is a directory'),
Command('hdfs dfs -rm foo', stderr='rm: `foo`: Is a directory'),
Command('./bin/hdfs dfs -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()])
Command('rm foo'),
Command('hdfs dfs -rm foo'),
Command('./bin/hdfs dfs -rm foo'),
Command()])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():
assert get_new_command(Command('rm foo', '', ''), None) == 'rm -rf foo'
@pytest.mark.parametrize('command, new_command', [
(Command('rm foo'), 'rm -rf foo'),
(Command('hdfs dfs -rm foo'), 'hdfs dfs -rm -r foo')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -56,7 +56,7 @@ def test_match(ssh_error):
def test_side_effect(ssh_error):
errormsg, path, reset, known_hosts = ssh_error
command = Command('ssh user@host', stderr=errormsg)
side_effect(command, None)
side_effect(command, None, None)
expected = ['123.234.567.890 asdjkasjdakjsd\n', '111.222.333.444 qwepoiwqepoiss\n']
assert known_hosts(path) == expected

View File

@@ -21,5 +21,9 @@ def test_not_match():
assert not match(Command(), None)
def test_get_new_command():
assert get_new_command(Command('ls'), None) == 'sudo ls'
@pytest.mark.parametrize('before, after', [
('ls', 'sudo ls'),
('echo a > b', 'sudo sh -c "echo a > b"'),
('echo "a" >> b', 'sudo sh -c "echo \\"a\\" >> b"')])
def test_get_new_command(before, after):
assert get_new_command(Command(before), None) == after

View File

@@ -3,7 +3,6 @@ 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)
@@ -13,6 +12,7 @@ def test_match():
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"

View File

@@ -16,4 +16,4 @@ def test_match(tmux_ambiguous):
def test_get_new_command(tmux_ambiguous):
assert get_new_command(Command('tmux list', stderr=tmux_ambiguous), None)\
== 'tmux list-keys'
== ['tmux list-keys', 'tmux list-panes', 'tmux list-windows']

View File

@@ -61,30 +61,30 @@ def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
@pytest.mark.parametrize('command, new_commands', [
(Command('tsuru log', stderr=(
'tsuru: "log" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-log\n'
'\tlogin\n'
'\tlogout\n'
)), 'tsuru login'),
)), ['tsuru login', 'tsuru logout', 'tsuru app-log']),
(Command('tsuru app-l', stderr=(
'tsuru: "app-l" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-list\n'
'\tapp-log\n'
)), 'tsuru app-log'),
)), ['tsuru app-log', 'tsuru app-list']),
(Command('tsuru user-list', stderr=(
'tsuru: "user-list" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tteam-user-list\n'
)), 'tsuru team-user-list'),
)), ['tsuru team-user-list']),
(Command('tsuru targetlist', stderr=(
'tsuru: "targetlist" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\ttarget-list\n'
)), 'tsuru target-list'),
)), ['tsuru target-list']),
])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command
def test_get_new_command(command, new_commands):
assert get_new_command(command, None) == new_commands

View File

@@ -0,0 +1,35 @@
import pytest
from thefuck.rules.unknown_command import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='./bin/hdfs dfs ls', stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'),
Command(script='hdfs dfs ls',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'),
Command(script='hdfs dfs ls /foo/bar', stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='./bin/hdfs dfs -ls', stderr=''),
Command(script='./bin/hdfs dfs -ls /foo/bar', stderr=''),
Command(script='hdfs dfs -ls -R /foo/bar', stderr=''),
Command()])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command('hdfs dfs ls',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['hdfs dfs -ls']),
(Command('hdfs dfs rm /foo/bar',
stderr='rm: Unknown command\nDid you mean -rm? This command begins with a dash.'), ['hdfs dfs -rm /foo/bar']),
(Command('./bin/hdfs dfs ls -R /foo/bar',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -ls -R /foo/bar']),
(Command('./bin/hdfs dfs -Dtest=fred ls -R /foo/bar',
stderr='ls: Unknown command\nDid you mean -ls? This command begins with a dash.'), ['./bin/hdfs dfs -Dtest=fred -ls -R /foo/bar'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -0,0 +1,35 @@
import pytest
from thefuck.rules.vagrant_up import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='vagrant ssh', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'),
Command(script='vagrant ssh devbox', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'),
Command(script='vagrant rdp',
stderr='VM must be created before running this command. Run `vagrant up` first.'),
Command(script='vagrant rdp devbox',
stderr='VM must be created before running this command. Run `vagrant up` first.')])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command(script='vagrant ssh', stderr=''),
Command(script='vagrant ssh jeff', stderr='The machine with the name \'jeff\' was not found configured for this Vagrant environment.'),
Command(script='vagrant ssh', stderr='A Vagrant environment or target machine is required to run this command. Run `vagrant init` to create a new Vagrant environment. Or, get an ID of a target machine from `vagrant global-status` to run this command on. A final option is to change to a directory with a Vagrantfile and to try again.'),
Command()])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_command', [
(Command(script='vagrant ssh', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), 'vagrant up && vagrant ssh'),
(Command(script='vagrant ssh devbox', stderr='VM must be running to open SSH connection. Run `vagrant up`\nto start the virtual machine.'), ['vagrant up devbox && vagrant ssh devbox', 'vagrant up && vagrant ssh devbox']),
(Command(script='vagrant rdp',
stderr='VM must be created before running this command. Run `vagrant up` first.'), 'vagrant up && vagrant rdp'),
(Command(script='vagrant rdp devbox',
stderr='VM must be created before running this command. Run `vagrant up` first.'), ['vagrant up devbox && vagrant rdp devbox', 'vagrant up && vagrant rdp devbox'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

@@ -6,7 +6,7 @@ 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')])
Command(script='whois meta.unix.stackexchange.com')])
def test_match(command):
assert match(command, None)
@@ -15,9 +15,12 @@ def test_not_match():
assert not match(Command(script='whois'), None)
# `whois com` actually makes sense
@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')])
(Command('whois meta.unix.stackexchange.com'), ['whois unix.stackexchange.com',
'whois stackexchange.com',
'whois com'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

View File

@@ -0,0 +1,29 @@
import pytest
from thefuck.specific.git import git_support
from tests.utils import Command
@pytest.mark.parametrize('called, command, stderr', [
('git co', 'git checkout', "19:22:36.299340 git.c:282 trace: alias expansion: co => 'checkout'"),
('git com file', 'git commit --verbose file',
"19:23:25.470911 git.c:282 trace: alias expansion: com => 'commit' '--verbose'")])
def test_git_support(called, command, stderr):
@git_support
def fn(command, settings): return command.script
assert fn(Command(script=called, stderr=stderr), None) == command
@pytest.mark.parametrize('command, is_git', [
('git pull', True),
('hub pull', True),
('git push --set-upstream origin foo', True),
('hub push --set-upstream origin foo', True),
('ls', False),
('cat git', False),
('cat hub', False)])
def test_git_support_match(command, is_git):
@git_support
def fn(command, settings): return True
assert fn(Command(script=command), None) == is_git

View File

@@ -0,0 +1,20 @@
import pytest
from mock import Mock
from thefuck.specific.sudo import sudo_support
from tests.utils import Command
@pytest.mark.parametrize('return_value, command, called, result', [
('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'),
('ls -lah', 'ls', 'ls', 'ls -lah'),
(['ls -lah'], 'sudo ls', 'ls', ['sudo 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):
def fn(command, settings):
assert command == Command(called)
return return_value
assert sudo_support(fn)(Command(command), None) == result

85
tests/test_corrector.py Normal file
View File

@@ -0,0 +1,85 @@
import pytest
from pathlib import PosixPath, Path
from mock import Mock
from thefuck import corrector, conf, types
from tests.utils import Rule, Command, CorrectedCommand
from thefuck.corrector import make_corrected_commands, get_corrected_commands
def test_load_rule(mocker):
match = object()
get_new_command = object()
load_source = mocker.patch(
'thefuck.corrector.load_source',
return_value=Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True,
priority=900,
requires_output=True))
assert corrector.load_rule(Path('/rules/bash.py'), settings=Mock(priority={})) \
== Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py')
class TestGetRules(object):
@pytest.fixture(autouse=True)
def glob(self, mocker):
return mocker.patch('thefuck.corrector.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')]
monkeypatch.setattr('thefuck.corrector.load_source',
lambda x, _: Rule(x))
assert self._compare_names(
corrector.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
rules)
class TestIsRuleMatch(object):
def test_no_match(self, settings):
assert not corrector.is_rule_match(
Command('ls'), Rule('', lambda *_: False), settings)
def test_match(self, settings):
rule = Rule('', lambda x, _: x.script == 'cd ..')
assert corrector.is_rule_match(Command('cd ..'), rule, settings)
def test_when_rule_failed(self, capsys, settings):
rule = Rule('test', Mock(side_effect=OSError('Denied')),
requires_output=False)
assert not corrector.is_rule_match(
Command('ls'), rule, settings)
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
class TestMakeCorrectedCommands(object):
def test_with_rule_returns_list(self):
rule = Rule(get_new_command=lambda x, _: [x.script + '!', x.script + '@'],
priority=100)
assert list(make_corrected_commands(Command(script='test'), rule, None)) \
== [CorrectedCommand(script='test!', priority=100),
CorrectedCommand(script='test@', priority=200)]
def test_with_rule_returns_command(self):
rule = Rule(get_new_command=lambda x, _: x.script + '!',
priority=100)
assert list(make_corrected_commands(Command(script='test'), rule, None)) \
== [CorrectedCommand(script='test!', priority=100)]
def test_get_corrected_commands(mocker):
command = Command('test', 'test', 'test')
rules = [Rule(match=lambda *_: False),
Rule(match=lambda *_: True,
get_new_command=lambda x, _: x.script + '!', priority=100),
Rule(match=lambda *_: True,
get_new_command=lambda x, _: [x.script + '@', x.script + ';'],
priority=60)]
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
assert [cmd.script for cmd in get_corrected_commands(command, None, Mock(debug=False))] \
== ['test!', 'test@', 'test;']

View File

@@ -1,61 +1,8 @@
import pytest
from subprocess import PIPE
from pathlib import PosixPath, Path
from mock import Mock
from thefuck import main, conf, types
from tests.utils import Rule, Command
def test_load_rule(mocker):
match = object()
get_new_command = object()
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,
requires_output=True))
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')
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')]
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)
from thefuck import main
from tests.utils import Command
class TestGetCommand(object):
@@ -79,7 +26,7 @@ class TestGetCommand(object):
def test_get_command_calls(self, Popen):
assert main.get_command(Mock(env={}),
['thefuck', 'apt-get', 'search', 'vim']) \
['thefuck', 'apt-get', 'search', 'vim']) \
== Command('apt-get search vim', 'stdout', 'stderr')
Popen.assert_called_once_with('apt-get search vim',
shell=True,
@@ -88,6 +35,8 @@ class TestGetCommand(object):
env={})
@pytest.mark.parametrize('args, result', [
(['thefuck', ''], None),
(['thefuck', '', ''], None),
(['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')])
def test_get_command_script(self, args, result):
@@ -95,80 +44,3 @@ class TestGetCommand(object):
assert main.get_command(Mock(env={}), args).script == result
else:
assert main.get_command(Mock(env={}), args) is None
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, debug=False))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
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', '')
def test_run_rule_with_side_effect(self, capsys):
side_effect = Mock()
settings = Mock(debug=False)
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() == ('', '')
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 (+side effect)\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 (+side effect) [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')

16
tests/test_readme.py Normal file
View File

@@ -0,0 +1,16 @@
from tests.utils import root
def test_readme():
with root.joinpath('README.md').open() as f:
readme = f.read()
bundled = root \
.joinpath('thefuck') \
.joinpath('rules') \
.glob('*.py')
for rule in bundled:
if rule.stem != '__init__':
assert rule.stem in readme,\
'Missing rule "{}" in README.md'.format(rule.stem)

View File

@@ -1,5 +1,6 @@
from thefuck.types import RulesNamesList, Settings
from tests.utils import Rule
from thefuck.types import RulesNamesList, Settings, \
SortedCorrectedCommandsSequence
from tests.utils import Rule, CorrectedCommand
def test_rules_names_list():
@@ -11,6 +12,47 @@ def test_rules_names_list():
def test_update_settings():
settings = Settings({'key': 'val'})
new_settings = settings.update(key='new-val')
assert new_settings.key == 'new-val'
new_settings = settings.update(key='new-val', unset='unset-value')
assert new_settings.key == 'val'
assert new_settings.unset == 'unset-value'
assert settings.key == 'val'
class TestSortedCorrectedCommandsSequence(object):
def test_realises_generator_only_on_demand(self, settings):
should_realise = False
def gen():
yield CorrectedCommand('git commit')
yield CorrectedCommand('git branch', priority=200)
assert should_realise
yield CorrectedCommand('git checkout', priority=100)
commands = SortedCorrectedCommandsSequence(gen(), settings)
assert commands[0] == CorrectedCommand('git commit')
should_realise = True
assert commands[1] == CorrectedCommand('git checkout', priority=100)
assert commands[2] == CorrectedCommand('git branch', priority=200)
def test_remove_duplicates(self, settings):
side_effect = lambda *_: None
seq = SortedCorrectedCommandsSequence(
iter([CorrectedCommand('ls', priority=100),
CorrectedCommand('ls', priority=200),
CorrectedCommand('ls', side_effect, 300)]),
settings)
assert set(seq) == {CorrectedCommand('ls', priority=100),
CorrectedCommand('ls', side_effect, 300)}
class TestCorrectedCommand(object):
def test_equality(self):
assert CorrectedCommand('ls', None, 100) == \
CorrectedCommand('ls', None, 200)
assert CorrectedCommand('ls', None, 100) != \
CorrectedCommand('ls', lambda *_: _, 100)
def test_hashable(self):
assert {CorrectedCommand('ls', None, 100),
CorrectedCommand('ls', None, 200)} == {CorrectedCommand('ls')}

121
tests/test_ui.py Normal file
View File

@@ -0,0 +1,121 @@
# -*- encoding: utf-8 -*-
from mock import Mock
import pytest
from itertools import islice
from thefuck import ui
from thefuck.types import CorrectedCommand, SortedCorrectedCommandsSequence
@pytest.fixture
def patch_getch(monkeypatch):
def patch(vals):
def getch():
for val in vals:
if val == KeyboardInterrupt:
raise val
else:
yield val
getch_gen = getch()
monkeypatch.setattr('thefuck.ui.getch', lambda: next(getch_gen))
return patch
def test_read_actions(patch_getch):
patch_getch([ # Enter:
'\n',
# Enter:
'\r',
# Ignored:
'x', 'y',
# Up:
'\x1b', '[', 'A',
# Down:
'\x1b', '[', 'B',
# Ctrl+C:
KeyboardInterrupt], )
assert list(islice(ui.read_actions(), 5)) \
== [ui.SELECT, ui.SELECT, ui.PREVIOUS, ui.NEXT, ui.ABORT]
def test_command_selector():
selector = ui.CommandSelector([1, 2, 3])
assert selector.value == 1
changes = []
selector.on_change(changes.append)
selector.next()
assert selector.value == 2
selector.next()
assert selector.value == 3
selector.next()
assert selector.value == 1
selector.previous()
assert selector.value == 3
assert changes == [1, 2, 3, 1, 3]
class TestSelectCommand(object):
@pytest.fixture
def commands_with_side_effect(self, settings):
return SortedCorrectedCommandsSequence(
iter([CorrectedCommand('ls', lambda *_: None, 100),
CorrectedCommand('cd', lambda *_: None, 100)]),
settings)
@pytest.fixture
def commands(self, settings):
return SortedCorrectedCommandsSequence(
iter([CorrectedCommand('ls', None, 100),
CorrectedCommand('cd', None, 100)]),
settings)
def test_without_commands(self, capsys):
assert ui.select_command([], Mock(debug=False, no_color=True)) is None
assert capsys.readouterr() == ('', 'No fucks given\n')
def test_without_confirmation(self, capsys, commands):
assert ui.select_command(commands,
Mock(debug=False, no_color=True,
require_confirmation=False)) == commands[0]
assert capsys.readouterr() == ('', 'ls\n')
def test_without_confirmation_with_side_effects(self, capsys,
commands_with_side_effect):
assert ui.select_command(commands_with_side_effect,
Mock(debug=False, no_color=True,
require_confirmation=False)) \
== commands_with_side_effect[0]
assert capsys.readouterr() == ('', 'ls (+side effect)\n')
def test_with_confirmation(self, capsys, patch_getch, commands):
patch_getch(['\n'])
assert ui.select_command(commands,
Mock(debug=False, no_color=True,
require_confirmation=True)) == commands[0]
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\n')
def test_with_confirmation_abort(self, capsys, patch_getch, commands):
patch_getch([KeyboardInterrupt])
assert ui.select_command(commands,
Mock(debug=False, no_color=True,
require_confirmation=True)) is None
assert capsys.readouterr() == ('', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\nAborted\n')
def test_with_confirmation_with_side_effct(self, capsys, patch_getch,
commands_with_side_effect):
patch_getch(['\n'])
assert ui.select_command(commands_with_side_effect,
Mock(debug=False, no_color=True,
require_confirmation=True))\
== commands_with_side_effect[0]
assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
def test_with_confirmation_select_second(self, capsys, patch_getch, commands):
patch_getch(['\x1b', '[', 'B', '\n'])
assert ui.select_command(commands,
Mock(debug=False, no_color=True,
require_confirmation=True)) == commands[1]
assert capsys.readouterr() == (
'', u'\x1b[1K\rls [enter/↑/↓/ctrl+c]\x1b[1K\rcd [enter/↑/↓/ctrl+c]\n')

View File

@@ -1,56 +1,22 @@
import pytest
from mock import Mock
from thefuck.utils import git_support, sudo_support, wrap_settings,\
import six
from thefuck.utils import wrap_settings, \
memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands
get_all_matched_commands, is_app, for_app, cache
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'})])
({'key': 'new-val'}, {'key': 'val'}, {'key': 'val'}),
({'key': 'new-val', 'unset': 'unset'}, {'key': 'val'}, {'key': 'val', 'unset': 'unset'})])
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)
@pytest.mark.parametrize('called, command, stderr', [
('git co', 'git checkout', "19:22:36.299340 git.c:282 trace: alias expansion: co => 'checkout'"),
('git com file', 'git commit --verbose file', "19:23:25.470911 git.c:282 trace: alias expansion: com => 'commit' '--verbose'")])
def test_git_support(called, command, stderr):
@git_support
def fn(command, settings): return command.script
assert fn(Command(script=called, stderr=stderr), None) == command
@pytest.mark.parametrize('command, is_git', [
('git pull', True),
('hub pull', True),
('git push --set-upstream origin foo', True),
('hub push --set-upstream origin foo', True),
('ls', False),
('cat git', False),
('cat hub', False)])
def test_git_support_match(command, is_git):
@git_support
def fn(command, settings): return True
assert fn(Command(script=command), None) == is_git
def test_memoize():
fn = Mock(__name__='fn')
memoized = memoize(fn)
@@ -69,7 +35,6 @@ def test_no_memoize():
class TestGetClosest(object):
def test_when_can_match(self):
assert 'branch' == get_closest('brnch', ['branch', 'status'])
@@ -129,3 +94,89 @@ def test_replace_argument(args, result):
'service-status', 'service-unbind'])])
def test_get_all_matched_commands(stderr, result):
assert list(get_all_matched_commands(stderr)) == result
@pytest.mark.usefixtures('no_memoize')
@pytest.mark.parametrize('script, names, result', [
('git diff', ['git', 'hub'], True),
('hub diff', ['git', 'hub'], True),
('hg diff', ['git', 'hub'], False)])
def test_is_app(script, names, result):
assert is_app(Command(script), *names) == result
@pytest.mark.usefixtures('no_memoize')
@pytest.mark.parametrize('script, names, result', [
('git diff', ['git', 'hub'], True),
('hub diff', ['git', 'hub'], True),
('hg diff', ['git', 'hub'], False)])
def test_for_app(script, names, result):
@for_app(*names)
def match(command, settings):
return True
assert match(Command(script), None) == result
class TestCache(object):
@pytest.fixture(autouse=True)
def enable_cache(self, monkeypatch):
monkeypatch.setattr('thefuck.utils.cache.disabled', False)
@pytest.fixture
def shelve(self, mocker):
value = {}
class _Shelve(object):
def __init__(self, path):
pass
def __setitem__(self, k, v):
value[k] = v
def __getitem__(self, k):
return value[k]
def get(self, k, v=None):
return value.get(k, v)
def close(self):
return
mocker.patch('thefuck.utils.shelve.open', new_callable=lambda: _Shelve)
return value
@pytest.fixture(autouse=True)
def mtime(self, mocker):
mocker.patch('thefuck.utils.os.path.getmtime', return_value=0)
@pytest.fixture
def fn(self):
@cache('~/.bashrc')
def fn():
return 'test'
return fn
@pytest.fixture
def key(self):
if six.PY3:
return 'tests.test_utils.<function TestCache.fn.<locals>.fn '
else:
return 'tests.test_utils.<function fn '
def test_with_blank_cache(self, shelve, fn, key):
assert shelve == {}
assert fn() == 'test'
assert shelve == {key: {'etag': '0', 'value': 'test'}}
def test_with_filled_cache(self, shelve, fn, key):
cache_value = {key: {'etag': '0', 'value': 'new-value'}}
shelve.update(cache_value)
assert fn() == 'new-value'
assert shelve == cache_value
def test_when_etag_changed(self, shelve, fn, key):
shelve.update({key: {'etag': '-1', 'value': 'old-value'}})
assert fn() == 'test'
assert shelve == {key: {'etag': '0', 'value': 'test'}}

View File

@@ -1,3 +1,4 @@
from pathlib import Path
from thefuck import types
from thefuck.conf import DEFAULT_PRIORITY
@@ -15,3 +16,10 @@ def Rule(name='', match=lambda *_: True,
return types.Rule(name, match, get_new_command,
enabled_by_default, side_effect,
priority, requires_output)
def CorrectedCommand(script='', side_effect=None, priority=DEFAULT_PRIORITY):
return types.CorrectedCommand(script, side_effect, priority)
root = Path(__file__).parent.parent.resolve()

71
thefuck/corrector.py Normal file
View File

@@ -0,0 +1,71 @@
import sys
from imp import load_source
from pathlib import Path
from . import conf, types, logs
def load_rule(rule, settings):
"""Imports rule module and returns it."""
name = rule.name[:-3]
with logs.debug_time(u'Importing rule: {};'.format(name), settings):
rule_module = load_source(name, str(rule))
priority = getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY)
return types.Rule(name, rule_module.match,
rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True),
getattr(rule_module, 'side_effect', None),
settings.priority.get(name, priority),
getattr(rule_module, 'requires_output', True))
def get_loaded_rules(rules, settings):
"""Yields all available rules."""
for rule in rules:
if rule.name != '__init__.py':
loaded_rule = load_rule(rule, settings)
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')
user = user_dir.joinpath('rules').glob('*.py')
return sorted(get_loaded_rules(sorted(bundled) + sorted(user), settings),
key=lambda rule: rule.priority)
def is_rule_match(command, rule, settings):
"""Returns first matched rule for command."""
script_only = command.stdout is None and command.stderr is None
if script_only and rule.requires_output:
return False
try:
with logs.debug_time(u'Trying rule: {};'.format(rule.name),
settings):
if rule.match(command, settings):
return True
except Exception:
logs.rule_failed(rule, sys.exc_info(), settings)
def make_corrected_commands(command, rule, settings):
new_commands = rule.get_new_command(command, settings)
if not isinstance(new_commands, list):
new_commands = (new_commands,)
for n, new_command in enumerate(new_commands):
yield types.CorrectedCommand(script=new_command,
side_effect=rule.side_effect,
priority=(n + 1) * rule.priority)
def get_corrected_commands(command, user_dir, settings):
corrected_commands = (
corrected for rule in get_rules(user_dir, settings)
if is_rule_match(command, rule, settings)
for corrected in make_corrected_commands(command, rule, settings))
return types.SortedCorrectedCommandsSequence(corrected_commands, settings)

View File

@@ -1,3 +1,5 @@
# -*- encoding: utf-8 -*-
from contextlib import contextmanager
from datetime import datetime
import sys
@@ -28,27 +30,6 @@ 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}{reset}{side_effect}\n'.format(
command=new_command,
side_effect=' (+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}{reset}{side_effect} '
'[{green}enter{reset}/{red}ctrl+c{reset}]'.format(
command=new_command,
side_effect=' (+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,
@@ -56,6 +37,29 @@ def failed(msg, settings):
reset=color(colorama.Style.RESET_ALL, settings)))
def show_corrected_command(corrected_command, settings):
sys.stderr.write('{bold}{script}{reset}{side_effect}\n'.format(
script=corrected_command.script,
side_effect=' (+side effect)' if corrected_command.side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
def confirm_text(corrected_command, settings):
sys.stderr.write(
('{clear}{bold}{script}{reset}{side_effect} '
'[{green}enter{reset}/{blue}{reset}/{blue}{reset}'
'/{red}ctrl+c{reset}]').format(
script=corrected_command.script,
side_effect=' (+side effect)' if corrected_command.side_effect else '',
clear='\033[1K\r',
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),
blue=color(colorama.Fore.BLUE, settings)))
def debug(msg, settings):
if settings.debug:
sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
@@ -71,4 +75,4 @@ def debug_time(msg, settings):
try:
yield
finally:
debug('{} took: {}'.format(msg, datetime.now() - started), settings)
debug(u'{} took: {}'.format(msg, datetime.now() - started), settings)

View File

@@ -1,7 +1,9 @@
from imp import load_source
from argparse import ArgumentParser
from warnings import warn
from pathlib import Path
from os.path import expanduser
from pprint import pformat
import pkg_resources
from subprocess import Popen, PIPE
import os
import sys
@@ -9,6 +11,8 @@ from psutil import Process, TimeoutExpired
import colorama
import six
from . import logs, conf, types, shells
from .corrector import get_corrected_commands
from .ui import select_command
def setup_user_dir():
@@ -21,40 +25,9 @@ def setup_user_dir():
return user_dir
def load_rule(rule):
"""Imports rule module and returns it."""
rule_module = load_source(rule.name[:-3], str(rule))
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),
getattr(rule_module, 'requires_output', True))
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')
user = user_dir.joinpath('rules').glob('*.py')
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):
"""Returns `True` if we can get output of the command in the
`wait_command` time.
`settings.wait_command` time.
Command will be killed if it wasn't finished in the time.
@@ -77,6 +50,7 @@ def get_command(settings, args):
else:
script = ' '.join(args[1:])
script = script.strip()
if not script:
return
@@ -100,51 +74,17 @@ def get_command(settings, args):
return types.Command(script, None, None)
def get_matched_rule(command, rules, settings):
"""Returns first matched rule for command."""
script_only = command.stdout is None and command.stderr is None
for rule in rules:
if script_only and rule.requires_output:
continue
try:
with logs.debug_time(u'Trying rule: {};'.format(rule.name),
settings):
if rule.match(command, settings):
return rule
except Exception:
logs.rule_failed(rule, sys.exc_info(), settings)
def confirm(new_command, side_effect, settings):
"""Returns `True` when running of new command confirmed."""
if not settings.require_confirmation:
logs.show_command(new_command, side_effect, settings)
return True
logs.confirm_command(new_command, side_effect, settings)
try:
sys.stdin.read(1)
return True
except KeyboardInterrupt:
logs.failed('Aborted', settings)
return False
def run_rule(rule, command, settings):
def run_command(old_cmd, command, settings):
"""Runs command from rule for passed command."""
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)
if command.side_effect:
command.side_effect(old_cmd, command.script, settings)
shells.put_to_history(command.script)
print(command.script)
# Entry points:
def main():
def fix_command():
colorama.init()
user_dir = setup_user_dir()
settings = conf.get_settings(user_dir)
@@ -152,22 +92,46 @@ def main():
logs.debug(u'Run with settings: {}'.format(pformat(settings)), settings)
command = get_command(settings, sys.argv)
rules = get_rules(user_dir, settings)
logs.debug(
u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
settings)
matched_rule = get_matched_rule(command, rules, settings)
if matched_rule:
logs.debug(u'Matched rule: {}'.format(matched_rule.name), settings)
run_rule(matched_rule, command, settings)
if not command:
logs.debug('Empty command, nothing to do', settings)
return
logs.failed('No fuck given', settings)
corrected_commands = get_corrected_commands(command, user_dir, settings)
selected_command = select_command(corrected_commands, settings)
if selected_command:
run_command(command, selected_command, settings)
def print_alias():
def print_alias(entry_point=True):
if entry_point:
warn('`thefuck-alias` is deprecated, use `thefuck --alias` instead.')
position = 1
else:
position = 2
alias = shells.thefuck_alias()
if len(sys.argv) > 1:
alias = sys.argv[1]
if len(sys.argv) > position:
alias = sys.argv[position]
print(shells.app_alias(alias))
def main():
parser = ArgumentParser(prog='thefuck')
parser.add_argument('-v', '--version',
action='version',
version='%(prog)s {}'.format(
pkg_resources.require('thefuck')[0].version))
parser.add_argument('-a', '--alias',
action='store_true',
help='[custom-alias-name] prints alias for current shell')
parser.add_argument('command',
nargs='*',
help='command that should be fixed')
known_args = parser.parse_args(sys.argv[1:2])
if known_args.alias:
print_alias(False)
elif known_args.command:
fix_command()
else:
parser.print_usage()

View File

@@ -1,27 +1,30 @@
from thefuck import shells
from thefuck.utils import sudo_support
from thefuck.utils import memoize
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
@memoize
def get_package(command):
try:
c = CommandNotFound.CommandNotFound()
cmd = command.split(' ')
pkgs = c.getPackages(cmd[0] if cmd[0] != 'sudo' else cmd[1])
name, _ = pkgs[0]
return name
except IndexError:
# IndexError is thrown when no matching package is found
return None
def match(command, settings):
return 'not found' in command.stderr and get_package(command.script)
def get_new_command(command, settings):
c = CommandNotFound.CommandNotFound()
pkgs = c.getPackages(command.script.split(" ")[0])
name, _ = pkgs[0]
name = get_package(command.script)
formatme = shells.and_('sudo apt-get install {}', '{}')
return formatme.format(name, command.script)

View File

@@ -0,0 +1,11 @@
import re
from thefuck.utils import for_app
@for_app('apt-get')
def match(command, settings):
return command.script.startswith('apt-get search')
def get_new_command(command, settings):
return re.sub(r'^apt-get', 'apt-cache', command.script)

View File

@@ -1,38 +1,38 @@
import os
import re
from subprocess import check_output
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import get_closest, replace_argument, which
from thefuck.specific.brew import get_brew_path_prefix
# 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'
enabled_by_default = bool(which('brew'))
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_formulas():
# Formulas are based on each local system's status
try:
brew_path_prefix = get_brew_path_prefix()
brew_formula_path = brew_path_prefix + '/Library/Formula'
for file_name in os.listdir(brew_formula_path):
if file_name.endswith('.rb'):
yield file_name[:-3]
except:
pass
def _get_similar_formula(formula_name):
return get_closest(formula_name, brew_formulas, 1, 0.85)
return get_closest(formula_name, _get_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 = bool(_get_similar_formula(formula))
return has_possible_formulas
return bool(_get_similar_formula(formula))
return False
def get_new_command(command, settings):

View File

@@ -1,30 +1,19 @@
import os
import re
import subprocess
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import get_closest, replace_command
from thefuck.specific.brew import get_brew_path_prefix
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
return [name[:-3] for name in os.listdir(brew_cmd_path)
if name.endswith('.rb')]
def _get_brew_tap_specific_commands(brew_path_prefix):
@@ -51,10 +40,7 @@ def _get_brew_tap_specific_commands(brew_path_prefix):
def _is_brew_tap_cmd_naming(name):
if name.startswith('brew-') and name.endswith('.rb'):
return True
return False
return name.startswith('brew-') and name.endswith('.rb')
def _get_directory_names_only(path):
@@ -62,41 +48,33 @@ def _get_directory_names_only(path):
if os.path.isdir(os.path.join(path, d))]
brew_path_prefix = _get_brew_path_prefix()
def _brew_commands():
brew_path_prefix = get_brew_path_prefix()
if brew_path_prefix:
try:
return _get_brew_commands(brew_path_prefix) \
+ _get_brew_tap_specific_commands(brew_path_prefix)
except OSError:
pass
# 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_command(command):
return get_closest(command, brew_commands)
# Failback commands for testing (Based on Homebrew 0.9.5)
return ['info', 'home', 'options', 'install', 'uninstall',
'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
'doctor', 'create', 'edit']
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 = bool(_get_similar_command(broken_cmd))
return has_possible_commands
return bool(get_closest(broken_cmd, _brew_commands()))
return False
def get_new_command(command, settings):
broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
command.stderr)[0]
new_cmd = _get_similar_command(broken_cmd)
return replace_argument(command.script, broken_cmd, new_cmd)
return replace_command(command, broken_cmd, _brew_commands())

View File

@@ -1,14 +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')
return command.script == 'brew upgrade'
def get_new_command(command, settings):
return command.script + ' --all'

View File

@@ -1,10 +1,10 @@
import re
from thefuck.utils import replace_argument
from thefuck.utils import replace_argument, for_app
@for_app('cargo')
def match(command, settings):
return ('cargo' in command.script
and 'No such subcommand' in command.stderr
return ('No such subcommand' in command.stderr
and 'Did you mean' in command.stderr)

View File

@@ -1,12 +1,12 @@
#!/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.specific.sudo import sudo_support
from thefuck.rules import cd_mkdir
from thefuck.utils import for_app
__author__ = "mmussomele"
MAX_ALLOWED_DIFF = 0.6
@@ -17,6 +17,7 @@ def _get_sub_dirs(parent):
@sudo_support
@for_app('cd')
def match(command, settings):
"""Match function copied from cd_mkdir.py"""
return (command.script.startswith('cd ')
@@ -28,8 +29,8 @@ def match(command, settings):
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.
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)

View File

@@ -1,13 +1,14 @@
import re
from thefuck import shells
from thefuck.utils import sudo_support
from thefuck.utils import for_app
from thefuck.specific.sudo import sudo_support
@sudo_support
@for_app('cd')
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()))
return (('no such file or directory' in command.stderr.lower()
or 'cd: can\'t cd to' in command.stderr.lower()))
@sudo_support

View File

@@ -7,8 +7,10 @@
# > cd..
# cd..: command not found
def match(command, settings):
return command.script == 'cd..'
def get_new_command(command, settings):
return 'cd ..'

View File

@@ -1,11 +1,11 @@
import re
from thefuck.utils import replace_argument
from thefuck.utils import replace_argument, for_app
@for_app('composer')
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()))
return (('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):

View File

@@ -1,12 +1,13 @@
import re
from thefuck.utils import sudo_support
from thefuck.specific.sudo import sudo_support
from thefuck.utils import for_app
@sudo_support
@for_app('cp')
def match(command, settings):
stderr = command.stderr.lower()
return command.script.startswith('cp ') \
and ('omitting directory' in stderr or 'is a directory' in stderr)
return 'omitting directory' in stderr or 'is a directory' in stderr
@sudo_support

View File

@@ -1,8 +1,11 @@
from thefuck.utils import for_app
@for_app(['g++', 'clang++'])
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))
return ('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):

View File

@@ -1,6 +1,7 @@
from thefuck import shells
import os
import tarfile
from thefuck import shells
from thefuck.utils import for_app
def _is_tar_extract(cmd):
@@ -13,29 +14,29 @@ def _is_tar_extract(cmd):
def _tar_file(cmd):
tar_extentions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
'.tar.lzma', '.tar.xz', '.taz', '.tb2', '.tbz', '.tbz2',
'.tgz', '.tlz', '.txz', '.tz')
for c in cmd.split():
for ext in tar_extentions:
for ext in tar_extensions:
if c.endswith(ext):
return (c, c[0:len(c)-len(ext)])
return (c, c[0:len(c) - len(ext)])
@for_app('tar')
def match(command, settings):
return (command.script.startswith('tar')
and '-C' not in command.script
return ('-C' not in command.script
and _is_tar_extract(command.script)
and _tar_file(command.script) is not None)
def get_new_command(command, settings):
return shells.and_('mkdir -p {dir}', '{cmd} -C {dir}') \
.format(dir=_tar_file(command.script)[1], cmd=command.script)
.format(dir=_tar_file(command.script)[1], cmd=command.script)
def side_effect(command, settings):
with tarfile.TarFile(_tar_file(command.script)[0]) as archive:
def side_effect(old_cmd, command, settings):
with tarfile.TarFile(_tar_file(old_cmd.script)[0]) as archive:
for file in archive.getnames():
os.remove(file)

View File

@@ -1,5 +1,6 @@
import os
import zipfile
from thefuck.utils import for_app
def _is_bad_zip(file):
@@ -20,9 +21,9 @@ def _zip_file(command):
return '{}.zip'.format(c)
@for_app('unzip')
def match(command, settings):
return (command.script.startswith('unzip')
and '-d' not in command.script
return ('-d' not in command.script
and _is_bad_zip(_zip_file(command)))
@@ -30,8 +31,8 @@ def get_new_command(command, settings):
return '{} -d {}'.format(command.script, _zip_file(command)[:-4])
def side_effect(command, settings):
with zipfile.ZipFile(_zip_file(command), 'r') as archive:
def side_effect(old_cmd, command, settings):
with zipfile.ZipFile(_zip_file(old_cmd), 'r') as archive:
for file in archive.namelist():
os.remove(file)

View File

@@ -1,13 +1,14 @@
from itertools import dropwhile, takewhile, islice
import re
import subprocess
from thefuck.utils import get_closest, sudo_support, replace_argument
from thefuck.utils import replace_command, for_app
from thefuck.specific.sudo import sudo_support
@sudo_support
@for_app('docker')
def match(command, settings):
return command.script.startswith('docker') \
and 'is not a docker command' in command.stderr
return 'is not a docker command' in command.stderr
def get_docker_commands():
@@ -23,5 +24,4 @@ def get_docker_commands():
def get_new_command(command, settings):
wrong_command = re.findall(
r"docker: '(\w+)' is not a docker command.", command.stderr)[0]
fixed_command = get_closest(wrong_command, get_docker_commands())
return replace_argument(command.script, wrong_command, fixed_command)
return replace_command(command, wrong_command, get_docker_commands())

View File

@@ -1,7 +1,7 @@
# -*- encoding: utf-8 -*-
import re
from thefuck.utils import sudo_support
from thefuck.specific.sudo import sudo_support
@sudo_support

View File

@@ -1,9 +1,10 @@
import re
import os
from thefuck.utils import memoize
from thefuck.utils import memoize, wrap_settings
from thefuck import shells
# order is important: only the first match is considered
patterns = (
# js, node:
'^ at {file}:{line}:{col}',
@@ -20,13 +21,13 @@ patterns = (
# lua:
'^lua: {file}:{line}:',
# fish:
'^{file} \(line {line}\):',
'^{file} \\(line {line}\\):',
# bash, sh, ssh:
'^{file}: line {line}: ',
# cargo, clang, gcc, go, pep8, rustc:
'^{file}:{line}:{col}',
# ghc, make, ruby, zsh:
'^{file}:{line}:',
# cargo, clang, gcc, go, rustc:
'^{file}:{line}:{col}',
# perl:
'at {file} line {line}',
)
@@ -34,18 +35,18 @@ patterns = (
# for the sake of readability do not use named groups above
def _make_pattern(pattern):
pattern = pattern.replace('{file}', '(?P<file>[^:\n]+)')
pattern = pattern.replace('{line}', '(?P<line>[0-9]+)')
pattern = pattern.replace('{col}', '(?P<col>[0-9]+)')
pattern = pattern.replace('{file}', '(?P<file>[^:\n]+)') \
.replace('{line}', '(?P<line>[0-9]+)') \
.replace('{col}', '(?P<col>[0-9]+)')
return re.compile(pattern, re.MULTILINE)
patterns = [_make_pattern(p) for p in patterns]
patterns = [_make_pattern(p).search for p in patterns]
@memoize
def _search(stderr):
for pattern in patterns:
m = re.search(pattern, stderr)
if m:
m = pattern(stderr)
if m and os.path.isfile(m.group('file')):
return m
@@ -53,15 +54,24 @@ def match(command, settings):
if 'EDITOR' not in os.environ:
return False
m = _search(command.stderr)
return m and os.path.isfile(m.group('file'))
return _search(command.stderr) or _search(command.stdout)
@wrap_settings({'fixlinecmd': '{editor} {file} +{line}',
'fixcolcmd': None})
def get_new_command(command, settings):
m = _search(command.stderr)
m = _search(command.stderr) or _search(command.stdout)
# Note: there does not seem to be a standard for columns, so they are just
# ignored for now
return shells.and_('{} {} +{}'.format(os.environ['EDITOR'], m.group('file'), m.group('line')),
command.script)
# ignored by default
if settings.fixcolcmd and 'col' in m.groupdict():
editor_call = settings.fixcolcmd.format(editor=os.environ['EDITOR'],
file=m.group('file'),
line=m.group('line'),
col=m.group('col'))
else:
editor_call = settings.fixlinecmd.format(editor=os.environ['EDITOR'],
file=m.group('file'),
line=m.group('line'))
return shells.and_(editor_call, command.script)

View File

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

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

View File

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

View File

@@ -2,9 +2,10 @@ import re
import subprocess
from thefuck import shells, utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return ('did not match any file(s) known to git.' in command.stderr
and "Did you forget to 'git add'?" not in command.stderr)
@@ -23,11 +24,11 @@ def get_branches():
yield line.strip()
@utils.git_support
@git_support
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
r"did not match any file\(s\) known to git.", command.stderr)[0]
closest_branch = utils.get_closest(missing_file, get_branches(),
fallback_to_first=False)
if closest_branch:

View File

@@ -1,13 +1,13 @@
from thefuck import utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return ('diff' in command.script and
'--staged' not in command.script)
@utils.git_support
@git_support
def get_new_command(command, settings):
return replace_argument(command.script, 'diff', 'diff --staged')

View File

@@ -1,8 +1,9 @@
from thefuck import utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return (command.script.split()[1] == 'stash'
and 'usage:' in command.stderr)
@@ -19,7 +20,7 @@ stash_commands = (
'show')
@utils.git_support
@git_support
def get_new_command(command, settings):
stash_cmd = command.script.split()[2]
fixed = utils.get_closest(stash_cmd, stash_commands, fallback_to_first=False)

View File

@@ -1,6 +1,6 @@
import re
from thefuck.utils import (get_closest, git_support, replace_argument,
get_all_matched_commands)
from thefuck.utils import get_all_matched_commands, replace_command
from thefuck.specific.git import git_support
@git_support
@@ -13,7 +13,5 @@ def match(command, settings):
def get_new_command(command, settings):
broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
command.stderr)[0]
new_cmd = get_closest(broken_cmd,
get_all_matched_commands(command.stderr))
return replace_argument(command.script, broken_cmd, new_cmd)
matched = get_all_matched_commands(command.stderr)
return replace_command(command, broken_cmd, matched)

View File

@@ -1,13 +1,14 @@
from thefuck import shells, utils
from thefuck import shells
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return ('pull' in command.script
and 'set-upstream' in command.stderr)
@utils.git_support
@git_support
def get_new_command(command, settings):
line = command.stderr.split('\n')[-3].strip()
branch = line.split(' ')[-1]

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
from thefuck import utils
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return ('push' in command.script
and '! [rejected]' in command.stderr
@@ -10,7 +10,7 @@ def match(command, settings):
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
@utils.git_support
@git_support
def get_new_command(command, settings):
return replace_argument(command.script, 'push', 'push --force')

View File

@@ -1,8 +1,9 @@
from thefuck import utils, shells
from thefuck import shells
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@utils.git_support
@git_support
def match(command, settings):
return ('push' in command.script
and '! [rejected]' in command.stderr
@@ -10,7 +11,7 @@ def match(command, settings):
and 'Updates were rejected because the tip of your current branch is behind' in command.stderr)
@utils.git_support
@git_support
def get_new_command(command, settings):
return shells.and_(replace_argument(command.script, 'push', 'pull'),
command.script)

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
from thefuck.utils import for_app
@for_app('grep')
def match(command, settings):
return (command.script.startswith('grep')
and 'is a directory' in command.stderr.lower())
return 'is a directory' in command.stderr.lower()
def get_new_command(command, settings):

View File

@@ -1,11 +1,11 @@
import re
import subprocess
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import replace_command, for_app
@for_app('gulp')
def match(command, script):
return command.script.startswith('gulp')\
and 'is not in your gulpfile' in command.stdout
return 'is not in your gulpfile' in command.stdout
def get_gulp_tasks():
@@ -18,5 +18,4 @@ def get_gulp_tasks():
def get_new_command(command, script):
wrong_task = re.findall(r"Task '(\w+)' is not in your gulpfile",
command.stdout)[0]
fixed_task = get_closest(wrong_task, get_gulp_tasks())
return replace_argument(command.script, wrong_task, fixed_task)
return replace_command(command, wrong_task, get_gulp_tasks())

View File

@@ -1,5 +1,5 @@
import os
from thefuck.utils import sudo_support
from thefuck.specific.sudo import sudo_support
@sudo_support
@@ -11,4 +11,3 @@ def match(command, settings):
@sudo_support
def get_new_command(command, settings):
return u'./{}'.format(command.script)

View File

@@ -1,10 +1,10 @@
import re
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import replace_command, for_app
@for_app('heroku')
def match(command, settings):
return command.script.startswith('heroku') and \
'is not a heroku command' in command.stderr and \
return 'is not a heroku command' in command.stderr and \
'Perhaps you meant' in command.stderr
@@ -16,5 +16,4 @@ def _get_suggests(stderr):
def get_new_command(command, settings):
wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0]
correct = get_closest(wrong, _get_suggests(command.stderr))
return replace_argument(command.script, wrong, correct)
return replace_command(command, wrong, _get_suggests(command.stderr))

View File

@@ -23,6 +23,7 @@ def _history_of_exists_without_current(command):
if not line.startswith(tf_alias) and not line == command.script
and line.split(' ')[0] in executables]
def match(command, settings):
return len(get_close_matches(command.script,
_history_of_exists_without_current(command)))

View File

@@ -1,13 +1,16 @@
# Fixes common java command mistake
#
# Example:
# > java foo.java
# Error: Could not find or load main class foo.java
"""Fixes common java command mistake
Example:
> java foo.java
Error: Could not find or load main class foo.java
"""
from thefuck.utils import for_app
@for_app('java')
def match(command, settings):
return (command.script.startswith('java ')
and command.script.endswith('.java'))
return command.script.endswith('.java')
def get_new_command(command, settings):

View File

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

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