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

Compare commits

...

107 Commits
3.11 ... 3.14

Author SHA1 Message Date
Vladimir Iakovlev
3a9942061d Bump to 3.14 2017-01-11 15:05:29 +01:00
Vladimir Iakovlev
a65f90813b Bump to 3.13 2017-01-11 14:59:18 +01:00
Vladimir Iakovlev
a778ea6203 #588: Stop using bashlex 2017-01-11 14:58:50 +01:00
Vladimir Iakovlev
03a828d586 Bump to 3.12 2017-01-09 18:17:50 +01:00
Vladimir Iakovlev
4a0d71c1c4 #N/A: Add ifconfig_device_not_found rule 2017-01-09 18:13:37 +01:00
Vladimir Iakovlev
a6f63c0568 #580: Use bashlex in generic shell 2017-01-09 17:50:23 +01:00
Vladimir Iakovlev
d1b9492085 Merge pull request #586 from duboviy/master
Add Python 3.6 support
2017-01-09 17:38:37 +01:00
Eugene Duboviy
993a661c60 Update .travis.yml 2017-01-08 17:13:22 +02:00
Eugene Duboviy
bc9121cb13 Update appveyor.yml 2017-01-08 17:08:38 +02:00
Eugene Duboviy
7db140c456 Update tox.ini 2017-01-08 17:06:45 +02:00
Vladimir Iakovlev
e313ff73a9 Merge pull request #582 from josephfrazier/git_stash_pop
Fix `git stash pop` with local changes
2016-12-22 14:02:05 +01:00
Joseph Frazier
8c62706db4 Fix git stash pop with local changes
When there are local changes to a file, and a git stash is popped that
contains other changes to that same file, git fails as follows:

    $ git stash pop
    error: Your local changes to the following files would be overwritten by merge:
            src/index.js
    Please commit your changes or stash them before you merge.
    Aborting
    $

This change adds a rule that corrects this problem as suggested [here]:

    $ git stash pop
    error: Your local changes to the following files would be overwritten by merge:
            src/index.js
    Please commit your changes or stash them before you merge.
    Aborting
    $ fuck
    git add . && git stash pop && git reset . [enter/↑/↓/ctrl+c]
    Auto-merging src/index.js
    On branch flow
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)

            modified:   src/index.js

    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)

            modified:   src/index.js

    Dropped refs/stash@{0} (f94776d484c4278997ac6837a7b138b9b9cdead1)
    Unstaged changes after reset:
    M        src/index.js
    $

[here]: https://stackoverflow.com/questions/15126463/how-do-i-merge-local-modifications-with-a-git-stash-without-an-extra-commit/15126489#15126489
2016-12-11 12:44:04 -05:00
Vladimir Iakovlev
6baa7f650e Merge pull request #580 from josephfrazier/bash-command-substitution
bash: fix parsing of command substitution
2016-11-30 15:49:47 +01:00
Joseph Frazier
4ae32cf4ee bash: use generic shell's UTF-8 methods 2016-11-23 22:42:16 -05:00
Joseph Frazier
385746850e generic shell: extract UTF-8 encoding/decoding into methods 2016-11-23 07:53:22 -05:00
Joseph Frazier
4f87141f0c bash: fallback to generic parser if bashlex fails 2016-11-23 07:43:25 -05:00
Joseph Frazier
dbedcc7aa6 Test parsing bash arithmetic expressions 2016-11-23 07:36:58 -05:00
Vladimir Iakovlev
e0b5d47fa5 Merge pull request #578 from scorphus/fish-builtin-history
#577: Use builtin `history` in Fish function
2016-11-22 12:03:06 +01:00
Joseph Frazier
ca44ee0640 bash: use bashlex for split_command, not shlex 2016-11-18 14:43:07 -05:00
Joseph Frazier
892e8a8e65 Test parsing bash command substitution
This is to help address bad corrections like the following (note the
position of the -p flag):

    thefuck 'git log $(git ls-files thefuck | grep python_command) -p'
    git log $(git ls-files thefuck | grep -p python_command) [enter/↑/↓/ctrl+c]
2016-11-18 14:43:00 -05:00
Pablo Santiago Blum de Aguiar
a947259eef #577: Use builtin history in Fish function
Fix #577
2016-11-17 22:57:11 -02:00
Vladimir Iakovlev
785cb83ff3 Merge branch 'josephfrazier-git-flag-after-filename' 2016-11-08 23:53:50 +01:00
Vladimir Iakovlev
aec8fe3233 #570: Refine tests 2016-11-08 23:53:40 +01:00
Vladimir Iakovlev
c21dbd2be3 Merge branch 'git-flag-after-filename' of https://github.com/josephfrazier/thefuck into josephfrazier-git-flag-after-filename 2016-11-08 23:48:40 +01:00
Vladimir Iakovlev
6173913291 Merge pull request #572 from josephfrazier/ls_all
Suggest `ls -A` when `ls` has no output
2016-11-01 12:51:04 +01:00
Vladimir Iakovlev
6f0d1e287d #571: Don't put empty string in history in zsh 2016-10-31 18:52:48 +01:00
Joseph Frazier
756044e087 Suggest ls -A when ls has no output 2016-10-31 13:49:38 -04:00
Vladimir Iakovlev
ddd8788353 #571: always honor alter_history setting in zsh 2016-10-31 12:57:31 +01:00
Vladimir Iakovlev
76c0e7bc70 Merge pull request #571 from josephfrazier/bash-honor-alter-history
bash: always honor alter_history setting
2016-10-31 12:56:31 +01:00
Vladimir Iakovlev
4865bdd81f Merge pull request #569 from scorphus/rebase-skip
#N/A: Add `git_rebase_merge_dir` rule
2016-10-31 12:55:05 +01:00
Joseph Frazier
fa169c686c test_git_flag_after_filename.py: dedupe test commands 2016-10-31 00:22:24 -04:00
Joseph Frazier
9cae0bffff git_flag_after_filename: fix flake8 errors
These were found by creating a `.flake8` file containing:

    [flake8]
    ignore = E501,W503
    exclude = venv

then running:

    flake8 $(git diff master... --name-only)

See https://github.com/nvbn/thefuck/pull/563 for running `flake8` in CI
2016-10-31 00:22:24 -04:00
Joseph Frazier
b519d317f7 bash: always honor alter_history setting
This ensures that even if the command suggested and run by `thefuck`
fails, it will still be added to the history, allowing the user to tweak
it further (or run `fuck` again) if desired.

Note that the fish shell appears to already behave this way.
2016-10-30 23:17:52 -04:00
Joseph Frazier
5b420204c9 git: fix fatal: bad flag '...' after filename
For example:

    $ git log README.md -p
    fatal: bad flag '-p' used after filename
    $ fuck
    git log -p README.md [enter/↑/↓/ctrl+c]
    Aborted

    $ git log -p README.md --name-only
    fatal: bad flag '--name-only' used after filename
    $ fuck
    git log -p --name-only README.md [enter/↑/↓/ctrl+c]
    Aborted

    $ git log README.md -p CONTRIBUTING.md
    fatal: bad flag '-p' used after filename
    $ fuck
    git log -p README.md CONTRIBUTING.md [enter/↑/↓/ctrl+c]
2016-10-30 21:40:25 -04:00
Pablo Santiago Blum de Aguiar
07005b591a #N/A: Add git_rebase_merge_dir rule 2016-10-30 20:30:26 -02:00
Vladimir Iakovlev
cb99e42e02 Merge pull request #567 from scorphus/git-rm-local-modifications
#N/A: Add `git_rm_local_modifications` rule
2016-10-30 19:51:20 +01:00
Vladimir Iakovlev
51f77964c6 Merge pull request #568 from scorphus/osx-brew-install
#N/A: Do not fail if formula is already installed
2016-10-30 19:50:02 +01:00
Pablo Santiago Blum de Aguiar
30b1c44f91 #N/A: Do not fail if formula is already installed 2016-10-30 15:02:12 -02:00
Pablo Santiago Blum de Aguiar
af28f0334a #N/A: Add git_rm_local_modifications rule 2016-10-29 17:51:55 -02:00
Vladimir Iakovlev
5ee5439c1e #565: Refine git_push rule 2016-10-08 12:24:48 +02:00
Vladimir Iakovlev
cf006dac2c Merge branch 'master' into josephfrazier-git-push-u
# Conflicts:
#	thefuck/rules/git_push.py
2016-10-08 12:20:23 +02:00
Vladimir Iakovlev
5b535077bf #N/A: Stop changing Command inside rules 2016-10-08 12:18:33 +02:00
Vladimir Iakovlev
cf3acbfa2e Merge branch 'git-push-u' of https://github.com/josephfrazier/thefuck into josephfrazier-git-push-u 2016-10-07 10:40:02 +02:00
Vladimir Iakovlev
4d714994a3 Merge pull request #564 from josephfrazier/docker-python
Use official Python images for Docker tests
2016-10-07 10:38:49 +02:00
Vladimir Iakovlev
02f717a0e8 Merge pull request #562 from josephfrazier/man-help
Suggest `foo --help` when `man foo` shows no pages
2016-10-07 10:37:33 +02:00
Vladimir Iakovlev
8f4f2f03a7 Merge pull request #561 from josephfrazier/ag-literal
Suggest `ag -Q` when relevant
2016-10-07 10:35:34 +02:00
Joseph Frazier
feb36ede5c Fix suggestion for git push -u
This was broken by https://github.com/nvbn/thefuck/pull/559
2016-10-06 13:09:40 -04:00
Joseph Frazier
16a440cb9d test_zsh.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:15:18 -04:00
Joseph Frazier
10b20574d1 test_tcsh.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:12:38 -04:00
Joseph Frazier
91fceb401a test_fish.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 11:09:19 -04:00
Joseph Frazier
4b79e23ba7 test_bash.py: use official python images, not ubuntu
This should help reduce build times.
2016-10-06 10:56:37 -04:00
Joseph Frazier
f915a6ed0c test_performance.py: use python:3 image, not ubuntu
This should help reduce build times.
2016-10-06 10:54:47 -04:00
Joseph Frazier
a964af7e95 ag_literal.py: use endswith() rather than in
https://github.com/nvbn/thefuck/pull/561#discussion_r81898499
2016-10-05 10:55:49 -04:00
Joseph Frazier
77fc021a6c Refactor tests/rules/test_ag_literal.py
https://github.com/nvbn/thefuck/pull/561#discussion_r81894710
2016-10-05 10:52:24 -04:00
Joseph Frazier
4822ceb87a ag_literal.py: remove unused import (Flake8 F401)
https://github.com/nvbn/thefuck/pull/561#discussion_r81892699
2016-10-05 10:34:17 -04:00
Joseph Frazier
b2947aba8d test_ag_literal.py: Add blank line (PEP 8 E302)
https://github.com/nvbn/thefuck/pull/561#discussion_r81892174
2016-10-05 10:32:14 -04:00
Joseph Frazier
d2e0a19aae Add missing semicolon to aws_cli entry in README 2016-10-03 14:22:17 -04:00
Joseph Frazier
0c84eefa55 Don't suggest man 2/3 foo if no man pages exist
Suggest `foo --help` instead. However, if there are man pages, suggest
`foo --help` after `man 2/3 foo`

This addresses the comment in the previous commit message:

> However, in cases where multiple sections have man pages for `foo`,
> running `man foo` could bring up the "wrong" section of man pages.
> `man read` is an example of this, but that should probably be handled in
> a way that still suggests `foo --help` first when there are *no* man
> pages for `foo` in any section.
2016-10-03 14:10:42 -04:00
Joseph Frazier
8bd6c5da67 For man foo, try foo --help before man 3 foo
`man` without a section searches all sections, so having `foo --help`
suggested first makes more sense than adding a specific section. See
https://github.com/nvbn/thefuck/pull/562#issuecomment-251142710

However, in cases where multiple sections have man pages for `foo`,
running `man foo` could bring up the "wrong" section of man pages.
`man read` is an example of this, but that should probably be handled in
a way that still suggests `foo --help` first when there are *no* man
pages for `foo` in any section.

Closes https://github.com/nvbn/thefuck/issues/546
2016-10-03 12:03:57 -04:00
Vladimir Iakovlev
ce6b82c92d #560: Fix code style 2016-10-03 13:07:30 +02:00
Joseph Frazier
5dbbb3b1ed Add ... --help to man suggestions
This is along the lines of what @waldyrious suggested in
https://github.com/nvbn/thefuck/issues/546, but it just adds a new
suggestion rather than replacing the other ones.
2016-10-03 03:57:53 -04:00
Joseph Frazier
db4b37910d Suggest ag -Q when relevant
This detects when `ag` suggests the `-Q` option, and adds it.
2016-10-03 00:33:40 -04:00
Joseph Frazier
2b88ea11ea Suggest git diff --no-index when relevant
This makes it easier to use `git diff` on untracked files.
2016-10-03 00:05:01 -04:00
Vladimir Iakovlev
db7dffdb44 Merge pull request #559 from josephfrazier/git-push-explicit-upstream
Fix suggestions for `git push -u origin`
2016-10-02 17:21:53 +02:00
Vladimir Iakovlev
92f3c8fb52 Merge pull request #557 from OJFord/patch-1
Add sudo rule for Aura
2016-10-02 17:21:04 +02:00
Vladimir Iakovlev
7c4f0d2e55 Merge pull request #551 from scorphus/git-bisect-usage
#N/A: Add `git_bisect_usage` rule
2016-10-02 17:20:43 +02:00
Vladimir Iakovlev
d05eb0a6dc #552: Fix code style 2016-10-02 17:19:33 +02:00
Vladimir Iakovlev
cf352fd788 Merge branch 'remove-trailing-cedilla' of https://github.com/wikiti/thefuck into wikiti-remove-trailing-cedilla 2016-10-02 17:18:24 +02:00
Vladimir Iakovlev
3c1cce6bd2 Merge branch 'brew-link' of https://github.com/josephfrazier/thefuck into josephfrazier-brew-link
# Conflicts:
#	README.md
2016-10-02 17:17:15 +02:00
Vladimir Iakovlev
5d3a727d1a Merge pull request #555 from josephfrazier/brew-uninstall-force
Suggest `brew uninstall --force` when relevant
2016-10-02 17:14:54 +02:00
Vladimir Iakovlev
ea87d55771 Merge pull request #554 from JordonPhillips/aws-rule
Add new aws cli rule
2016-10-02 17:14:14 +02:00
Joseph Frazier
aa6b18d0ce Fix suggestions for git push -u origin
Resolves https://github.com/nvbn/thefuck/issues/558
2016-09-30 16:13:50 -04:00
Joseph Frazier
934eeaf4fc Test that git push -u origin still works
This was broken by https://github.com/nvbn/thefuck/pull/538
2016-09-30 16:11:46 -04:00
Ollie Ford
3ad8d52a84 Add sudo rule for Aura
When installing from Arch User Repository without root:
    aura >>= You have to use `sudo` for that.

This commit adds the slightly more general, but unambiguous, "use `sudo`".

This commit closes #543.
2016-09-30 20:32:40 +01:00
Joseph Frazier
bb5c7c576f Suggest brew link --overwrite --dry-run when relevant
This makes it easier to see which files would be overwritten by
`brew link --overwrite`
2016-09-30 15:31:25 -04:00
Joseph Frazier
17c3935078 Test brew uninstall --force suggestion 2016-09-29 17:44:07 -04:00
Joseph Frazier
a734b94fec Suggest brew uninstall --force when relevant
Resolves https://github.com/nvbn/thefuck/issues/553
2016-09-29 17:26:20 -04:00
JordonPhillips
7bf405e9c3 Add aws cli rule
This rule corrects spelling mistakes for aws cli commands and
subcommands.
2016-09-29 14:22:08 -07:00
Daniel Herzog
c3bcdd7dee Update README 2016-09-29 21:43:02 +01:00
Daniel Herzog
ad53023860 Fix encoding for Python 2.7 2016-09-29 21:41:50 +01:00
Daniel
8938323229 Fix encoding 2016-09-29 11:06:56 +01:00
Daniel
92133f77d6 Add test file 2016-09-29 10:44:17 +01:00
Daniel
64eaf96eb8 Add rule 2016-09-29 10:34:41 +01:00
Pablo Santiago Blum de Aguiar
c9264aff10 #N/A: Add git_bisect_usage rule 2016-09-27 19:42:01 -03:00
Vladimir Iakovlev
9660ec7813 Merge branch 'juzim-git-pull-uncommitted-changes' 2016-09-20 00:28:51 +02:00
Vladimir Iakovlev
9ac47d8f78 #550: Use shell.and_ 2016-09-20 00:28:09 +02:00
Julian Zimmermann
6e2b82911f Removed linebreak 2016-09-19 13:07:48 +02:00
Julian Zimmermann
af9d34c299 Added rule that stashes changed files before pulling and pops them afterwards. 2016-09-19 12:52:23 +02:00
Vladimir Iakovlev
bcc11219e6 Merge pull request #545 from waldyrious/patch-1
readme: add -H flag to second sudo pip command
2016-09-06 22:38:17 +02:00
Waldir Pimenta
495a66088b readme: add -H flag to second sudo pip command 2016-09-06 17:01:09 +01:00
Vladimir Iakovlev
4fe64e3dfa #N/A: Match git_add only if pathspec exists 2016-08-23 13:03:49 +03:00
Vladimir Iakovlev
cae76eb55f Merge branch 'kthrift-fix/prevent-cwd-tilde-dir-creation' 2016-08-22 05:45:41 +03:00
Vladimir Iakovlev
afd2ed4e51 #540: Fix code style, add test 2016-08-22 05:45:27 +03:00
Vladimir Iakovlev
1a4d74d487 Merge branch 'fix/prevent-cwd-tilde-dir-creation' of https://github.com/kthrift/thefuck into kthrift-fix/prevent-cwd-tilde-dir-creation 2016-08-22 05:20:29 +03:00
Kyle Thrift
0bd3e85e08 fix: new config dirs created in $HOME/.config/thefuck instead of $CWD
fix: use correct path in warning message when XDG_CONFIG_HOME defined
2016-08-21 15:59:16 -04:00
Vladimir Iakovlev
faeeef7666 Merge pull request #539 from blahgeek/master
prevent infinity loop while detecting shell
2016-08-21 15:22:36 +08:00
Vladimir Iakovlev
4d65d6a1df Merge pull request #538 from lukechilds/git-push-with-args
Preserve args for git_push
2016-08-21 15:20:54 +08:00
BlahGeek
cfa51506fb prevent infinity loop while detecting shell
In OS X, Process(pid=0).parent() == Process(pid=0)
2016-08-20 12:00:03 +08:00
Luke Childs
5df350254e Check arguments are preserved in git_push 2016-08-19 22:29:43 +01:00
Luke Childs
612c393ec4 Check git_push matches without specifying a branch 2016-08-19 22:19:09 +01:00
Luke Childs
4d89b3499e Preserve args for git_push 2016-08-19 22:08:30 +01:00
Vladimir Iakovlev
070bb2ff28 #N/A: Show deprecation warning when ~/.thefuck used 2016-08-14 20:02:33 +03:00
Vladimir Iakovlev
71025dff17 #N/A: Monkeypatch old pathlib even on unix 2016-08-14 15:32:53 +03:00
Vladimir Iakovlev
621b455334 #N/A: Monkeypatch pathlib on windows 2016-08-14 15:15:03 +03:00
Vladimir Iakovlev
176924c18d #N/A: Move imports from pathlib/pathlib2 to utils 2016-08-14 15:01:00 +03:00
Vladimir Iakovlev
1f75fc1ea9 #N/A: Remove deprecated thefuck-alias entry point 2016-08-14 14:43:13 +03:00
Vladimir Iakovlev
46cb87615e #N/A: Remove old-style rules support 2016-08-14 14:37:32 +03:00
69 changed files with 958 additions and 212 deletions

View File

@@ -2,6 +2,9 @@ language: python
sudo: false sudo: false
matrix: matrix:
include: include:
- os: linux
dist: trusty
python: "3.6"
- os: linux - os: linux
dist: trusty dist: trusty
python: "3.5" python: "3.5"
@@ -29,7 +32,7 @@ addons:
- python3-commandnotfound - python3-commandnotfound
before_install: before_install:
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew install $FORMULA; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then if brew ls --versions $FORMULA; then brew upgrade $FORMULA || echo Python is up to date; else brew install $FORMULA; fi; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi - if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
- pip install -U pip - pip install -U pip
@@ -41,7 +44,7 @@ install:
script: script:
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1} - export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
- export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests" - export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests"
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi - if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
- if [[ $TRAVIS_PYTHON_VERSION != 3.5 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi - if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
after_success: after_success:
- coveralls - coveralls

View File

@@ -103,7 +103,6 @@ brew install thefuck
``` ```
On Ubuntu you can install `The Fuck` with: On Ubuntu you can install `The Fuck` with:
```bash ```bash
sudo apt update sudo apt update
sudo apt install python3-dev python3-pip sudo apt install python3-dev python3-pip
@@ -135,7 +134,7 @@ To make them available immediately, run `source ~/.bashrc` (or your shell config
## Update ## Update
```bash ```bash
sudo pip install thefuck --upgrade sudo -H pip install thefuck --upgrade
``` ```
**Aliases changed in 1.34.** **Aliases changed in 1.34.**
@@ -145,6 +144,8 @@ sudo pip install thefuck --upgrade
The Fuck tries to match a rule for the previous command, creates a new command The Fuck tries to match a rule for the previous command, creates a new command
using the matched rule and runs it. Rules enabled by default are as follows: using the matched rule and runs it. Rules enabled by default are as follows:
* `ag_literal` &ndash; adds `-Q` to `ag` when suggested;
* `aws_cli` &ndash; fixes misspelled commands like `aws dynamdb scan`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`; * `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`; * `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`;
* `cd_correction` &ndash; spellchecks and correct failed cd commands; * `cd_correction` &ndash; spellchecks and correct failed cd commands;
@@ -164,22 +165,29 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `fix_alt_space` &ndash; replaces Alt+Space with Space character; * `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `fix_file` &ndash; opens a file with an error in your `$EDITOR`; * `fix_file` &ndash; opens a file with an error in your `$EDITOR`;
* `git_add` &ndash; fixes *"pathspec 'foo' did not match any file(s) known to git."*; * `git_add` &ndash; fixes *"pathspec 'foo' did not match any file(s) known to git."*;
* `git_bisect_usage` &ndash; fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
* `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`; * `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
* `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists; * `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch; * `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
* `git_checkout` &ndash; fixes branch name or creates new branch; * `git_checkout` &ndash; fixes branch name or creates new branch;
* `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
* `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output; * `git_diff_staged` &ndash; adds `--staged` to previous `git diff` with unexpected output;
* `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`); * `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
* `git_flag_after_filename` &ndash; fixes `fatal: bad flag '...' after filename`
* `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command; * `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command;
* `git_not_command` &ndash; fixes wrong git commands like `git brnch`; * `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; sets upstream before executing previous `git pull`; * `git_pull` &ndash; sets upstream before executing previous `git pull`;
* `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist; * `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
* `git_pull_uncommitted_changes` &ndash; stashes changes before pulling and pops them afterwards;
* `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`; * `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `git_push_pull` &ndash; runs `git pull` when `push` was rejected; * `git_push_pull` &ndash; runs `git pull` when `push` was rejected;
* `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes; * `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
* `git_rm_local_modifications` &ndash; adds `-f` or `--cached` when you try to `rm` a locally modified file;
* `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory; * `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory;
* `git_rebase_merge_dir` &ndash; offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
* `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistant remote; * `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistant remote;
* `git_stash` &ndash; stashes you local modifications before rebasing or switching branch; * `git_stash` &ndash; stashes you local modifications before rebasing or switching branch;
* `git_stash_pop` &ndash; adds your local modifications before popping stash, then resets;
* `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`; * `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
* `go_run` &ndash; appends `.go` extension when compiling/running Go programs; * `go_run` &ndash; appends `.go` extension when compiling/running Go programs;
* `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task; * `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task;
@@ -191,11 +199,13 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `has_exists_script` &ndash; prepends `./` when script/binary exists; * `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_not_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; * `history` &ndash; tries to replace command with most similar command from history;
* `ifconfig_device_not_found` &ndash; fixes wrong device names like `wlan0` to `wlp2s0`;
* `java` &ndash; removes `.java` extension when running Java programs; * `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files; * `javac` &ndash; appends missing `.java` when compiling Java files;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`; * `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link; * `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link;
* `ln_s_order` &ndash; fixes `ln -s` arguments order; * `ln_s_order` &ndash; fixes `ln -s` arguments order;
* `ls_all` &ndash; adds `-A` to `ls` when output is empty;
* `ls_lah` &ndash; adds `-lah` to `ls`; * `ls_lah` &ndash; adds `-lah` to `ls`;
* `man` &ndash; changes manual section; * `man` &ndash; changes manual section;
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`; * `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
@@ -215,6 +225,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `python_execute` &ndash; appends missing `.py` when executing Python files; * `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';
* `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands; * `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands;
* `remove_trailing_cedilla` &ndash; remove trailling cedillas `ç`, a common typo for european keyboard layouts;
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory; * `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands; * `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`; * `sl_ls` &ndash; changes `sl` to `ls`;
@@ -238,6 +249,8 @@ Enabled by default only on specific platforms:
* `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`; * `apt_get_search` &ndash; changes trying to search using `apt-get` with searching using `apt-cache`;
* `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`; * `apt_invalid_operation` &ndash; fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
* `brew_install` &ndash; fixes formula name for `brew install`; * `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_link` &ndash; adds `--overwrite --dry-run` if linking fails;
* `brew_uninstall` &ndash; adds `--force` to `brew uninstall` if multiple versions were installed;
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`; * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`; * `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`;
* `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour; * `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
@@ -266,7 +279,9 @@ side_effect(old_command: Command, fixed_command: str) -> None
``` ```
and optional `enabled_by_default`, `requires_output` and `priority` variables. and optional `enabled_by_default`, `requires_output` and `priority` variables.
`Command` has three attributes: `script`, `stdout` and `stderr`. `Command` has three attributes: `script`, `stdout`, `stderr` and `script_parts`.
Rule shouldn't change `Command`.
*Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`. *Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`.
`settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)). `settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)).

View File

@@ -6,6 +6,7 @@ environment:
- PYTHON: "C:/Python33" - PYTHON: "C:/Python33"
- PYTHON: "C:/Python34" - PYTHON: "C:/Python34"
- PYTHON: "C:/Python35" - PYTHON: "C:/Python35"
- PYTHON: "C:/Python36"
init: init:
- "ECHO %PYTHON%" - "ECHO %PYTHON%"

View File

@@ -29,7 +29,7 @@ elif (3, 0) < version < (3, 3):
' ({}.{} detected).'.format(*version)) ' ({}.{} detected).'.format(*version))
sys.exit(-1) sys.exit(-1)
VERSION = '3.11' VERSION = '3.14'
install_requires = ['psutil', 'colorama', 'six', 'decorator'] install_requires = ['psutil', 'colorama', 'six', 'decorator']
extras_require = {':python_version<"3.4"': ['pathlib2'], extras_require = {':python_version<"3.4"': ['pathlib2'],
@@ -51,5 +51,4 @@ setup(name='thefuck',
extras_require=extras_require, extras_require=extras_require,
entry_points={'console_scripts': [ entry_points={'console_scripts': [
'thefuck = thefuck.main:main', 'thefuck = thefuck.main:main',
'thefuck-alias = thefuck.main:print_alias',
'fuck = thefuck.main:how_to_configure_alias']}) 'fuck = thefuck.main:how_to_configure_alias']})

View File

@@ -1,10 +1,7 @@
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
import pytest import pytest
from thefuck import shells from thefuck import shells
from thefuck import conf, const from thefuck import conf, const
from thefuck.system import Path
shells.shell = shells.Generic() shells.shell = shells.Generic()

View File

@@ -3,18 +3,11 @@ 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, how_to_configure select_command_with_arrows, how_to_configure
containers = ((u'thefuck/ubuntu-python3-bash', containers = ((u'thefuck/python3-bash',
u'''FROM ubuntu:latest u'FROM python:3',
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''',
u'bash'), u'bash'),
(u'thefuck/ubuntu-python2-bash', (u'thefuck/python2-bash',
u'''FROM ubuntu:latest u'FROM python:2',
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools''',
u'bash')) u'bash'))

View File

@@ -2,19 +2,20 @@ import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, select_command_with_arrows refuse_with_confirmation, select_command_with_arrows
containers = (('thefuck/ubuntu-python3-fish', containers = (('thefuck/python3-fish',
u'''FROM ubuntu:latest u'''FROM python:3
# Use jessie-backports since it has the fish package. See here for details:
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
RUN apt-get update RUN apt-get update
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''', RUN apt-get install -yy fish''',
u'fish'), u'fish'),
('thefuck/ubuntu-python2-fish', ('thefuck/python2-fish',
u'''FROM ubuntu:latest u'''FROM python:2
# Use jessie-backports since it has the fish package. See here for details:
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy fish''', RUN apt-get install -yy fish''',
u'fish')) u'fish'))

View File

@@ -2,11 +2,7 @@ import pytest
import time import time
dockerfile = u''' dockerfile = u'''
FROM ubuntu:latest FROM python:3
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 RUN adduser --disabled-password --gecos '' test
ENV SEED "{seed}" ENV SEED "{seed}"
WORKDIR /src WORKDIR /src
@@ -42,7 +38,7 @@ def plot(proc, TIMEOUT):
@pytest.mark.functional @pytest.mark.functional
@pytest.mark.benchmark(min_rounds=10) @pytest.mark.benchmark(min_rounds=10)
def test_performance(spawnu, TIMEOUT, benchmark): def test_performance(spawnu, TIMEOUT, benchmark):
proc = spawnu(u'thefuck/ubuntu-python3-bash-performance', proc = spawnu(u'thefuck/python3-bash-performance',
dockerfile, u'bash') dockerfile, u'bash')
proc.sendline(u'pip install /src') proc.sendline(u'pip install /src')
proc.sendline(u'su test') proc.sendline(u'su test')

View File

@@ -2,19 +2,14 @@ import pytest
from tests.functional.plots import with_confirmation, without_confirmation, \ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, select_command_with_arrows refuse_with_confirmation, select_command_with_arrows
containers = (('thefuck/ubuntu-python3-tcsh', containers = (('thefuck/python3-tcsh',
u'''FROM ubuntu:latest u'''FROM python:3
RUN apt-get update 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 apt-get install -yy tcsh''', RUN apt-get install -yy tcsh''',
u'tcsh'), u'tcsh'),
('thefuck/ubuntu-python2-tcsh', ('thefuck/python2-tcsh',
u'''FROM ubuntu:latest u'''FROM python:2
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy tcsh''', RUN apt-get install -yy tcsh''',
u'tcsh')) u'tcsh'))

View File

@@ -3,19 +3,14 @@ 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, how_to_configure select_command_with_arrows, how_to_configure
containers = (('thefuck/ubuntu-python3-zsh', containers = (('thefuck/python3-zsh',
u'''FROM ubuntu:latest u'''FROM python:3
RUN apt-get update 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 apt-get install -yy zsh''', RUN apt-get install -yy zsh''',
u'zsh'), u'zsh'),
('thefuck/ubuntu-python2-zsh', ('thefuck/python2-zsh',
u'''FROM ubuntu:latest u'''FROM python:2
RUN apt-get update RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools
RUN apt-get install -yy zsh''', RUN apt-get install -yy zsh''',
u'zsh')) u'zsh'))

View File

@@ -0,0 +1,25 @@
import pytest
from thefuck.rules.ag_literal import get_new_command, match
from tests.utils import Command
@pytest.fixture
def stderr():
return ('ERR: Bad regex! pcre_compile() failed at position 1: missing )\n'
'If you meant to search for a literal string, run ag with -Q\n')
@pytest.mark.parametrize('script', ['ag \('])
def test_match(script, stderr):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['ag foo'])
def test_not_match(script):
assert not match(Command(script=script))
@pytest.mark.parametrize('script, new_cmd', [
('ag \(', 'ag -Q \(')])
def test_get_new_command(script, new_cmd, stderr):
assert get_new_command((Command(script=script, stderr=stderr))) == new_cmd

101
tests/rules/test_aws_cli.py Normal file
View File

@@ -0,0 +1,101 @@
import pytest
from thefuck.rules.aws_cli import match, get_new_command
from tests.utils import Command
no_suggestions = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument command: Invalid choice, valid choices are:
dynamodb | dynamodbstreams
ec2 | ecr
'''
misspelled_command = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument command: Invalid choice, valid choices are:
dynamodb | dynamodbstreams
ec2 | ecr
Invalid choice: 'dynamdb', maybe you meant:
* dynamodb
'''
misspelled_subcommand = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument operation: Invalid choice, valid choices are:
query | scan
update-item | update-table
Invalid choice: 'scn', maybe you meant:
* scan
'''
misspelled_subcommand_with_multiple_options = '''\
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument operation: Invalid choice, valid choices are:
describe-table | get-item
list-tables | put-item
Invalid choice: 't-item', maybe you meant:
* put-item
* get-item
'''
@pytest.mark.parametrize('command', [
Command('aws dynamdb scan', stderr=misspelled_command),
Command('aws dynamodb scn', stderr=misspelled_subcommand),
Command('aws dynamodb t-item',
stderr=misspelled_subcommand_with_multiple_options)])
def test_match(command):
assert match(command)
def test_not_match():
assert not match(Command('aws dynamodb invalid', stderr=no_suggestions))
@pytest.mark.parametrize('command, result', [
(Command('aws dynamdb scan', stderr=misspelled_command),
['aws dynamodb scan']),
(Command('aws dynamodb scn', stderr=misspelled_subcommand),
['aws dynamodb scan']),
(Command('aws dynamodb t-item',
stderr=misspelled_subcommand_with_multiple_options),
['aws dynamodb put-item', 'aws dynamodb get-item'])])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -0,0 +1,38 @@
import pytest
from tests.utils import Command
from thefuck.rules.brew_link import get_new_command, match
@pytest.fixture
def stderr():
return ("Error: Could not symlink bin/gcp\n"
"Target /usr/local/bin/gcp\n"
"already exists. You may want to remove it:\n"
"rm '/usr/local/bin/gcp'\n"
"\n"
"To force the link and overwrite all conflicting files:\n"
"brew link --overwrite coreutils\n"
"\n"
"To list all files that would be deleted:\n"
"brew link --overwrite --dry-run coreutils\n")
@pytest.fixture
def new_command(formula):
return 'brew link --overwrite --dry-run {}'.format(formula)
@pytest.mark.parametrize('script', ['brew link coreutils', 'brew ln coreutils'])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['brew link coreutils'])
def test_not_match(script):
stderr=''
assert not match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script, formula, ', [('brew link coreutils', 'coreutils')])
def test_get_new_command(stderr, new_command, script, formula):
assert get_new_command(Command(script=script, stderr=stderr)) == new_command

View File

@@ -0,0 +1,31 @@
import pytest
from tests.utils import Command
from thefuck.rules.brew_uninstall import get_new_command, match
@pytest.fixture
def stdout():
return ("Uninstalling /usr/local/Cellar/tbb/4.4-20160916... (118 files, 1.9M)\n"
"tbb 4.4-20160526, 4.4-20160722 are still installed.\n"
"Remove all versions with `brew uninstall --force tbb`.\n")
@pytest.fixture
def new_command(formula):
return 'brew uninstall --force {}'.format(formula)
@pytest.mark.parametrize('script', ['brew uninstall tbb', 'brew rm tbb', 'brew remove tbb'])
def test_match(stdout, script):
assert match(Command(script=script, stdout=stdout))
@pytest.mark.parametrize('script', ['brew remove gnuplot'])
def test_not_match(script):
stdout='Uninstalling /usr/local/Cellar/gnuplot/5.0.4_1... (44 files, 2.3M)\n'
assert not match(Command(script=script, stdout=stdout))
@pytest.mark.parametrize('script, formula, ', [('brew uninstall tbb', 'tbb')])
def test_get_new_command(stdout, new_command, script, formula):
assert get_new_command(Command(script=script, stdout=stdout)) == new_command

View File

@@ -3,6 +3,12 @@ from thefuck.rules.git_add import match, get_new_command
from tests.utils import Command from tests.utils import Command
@pytest.fixture(autouse=True)
def path_exists(mocker):
return mocker.patch('thefuck.rules.git_add.Path.exists',
return_value=True)
@pytest.fixture @pytest.fixture
def stderr(target): def stderr(target):
return ("error: pathspec '{}' did not match any " return ("error: pathspec '{}' did not match any "
@@ -16,10 +22,13 @@ def test_match(stderr, script, target):
assert match(Command(script=script, stderr=stderr)) assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', [ @pytest.mark.parametrize('script, target, exists', [
'git submodule update known', 'git commit known']) ('git submodule update known', '', True),
def test_not_match(script): ('git commit known', '', True),
assert not match(Command(script=script, stderr='')) ('git submodule update known', stderr, False)])
def test_not_match(path_exists, stderr, script, target, exists):
path_exists.return_value = exists
assert not match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script, target, new_command', [ @pytest.mark.parametrize('script, target, new_command', [

View File

@@ -0,0 +1,30 @@
import pytest
from tests.utils import Command
from thefuck.rules.git_bisect_usage import match, get_new_command
@pytest.fixture
def stderr():
return ("usage: git bisect [help|start|bad|good|new|old"
"|terms|skip|next|reset|visualize|replay|log|run]")
@pytest.mark.parametrize('script', [
'git bisect strt', 'git bisect rset', 'git bisect goood'])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', [
'git bisect', 'git bisect start', 'git bisect good'])
def test_not_match(script):
assert not match(Command(script=script, stderr=''))
@pytest.mark.parametrize('script, new_cmd, ', [
('git bisect goood', ['good', 'old', 'log']),
('git bisect strt', ['start', 'terms', 'reset']),
('git bisect rset', ['reset', 'next', 'start'])])
def test_get_new_command(stderr, script, new_cmd):
new_cmd = ['git bisect %s' % cmd for cmd in new_cmd]
assert get_new_command(Command(script=script, stderr=stderr)) == new_cmd

View File

@@ -0,0 +1,23 @@
import pytest
from thefuck.rules.git_diff_no_index import match, get_new_command
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='git diff foo bar')])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command(script='git diff --no-index foo bar'),
Command(script='git diff foo'),
Command(script='git diff foo bar baz')])
def test_not_match(command):
assert not match(command)
@pytest.mark.parametrize('command, new_command', [
(Command('git diff foo bar'), 'git diff --no-index foo bar')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -0,0 +1,31 @@
import pytest
from thefuck.rules.git_flag_after_filename import match, get_new_command
from tests.utils import Command
command1 = Command('git log README.md -p',
stderr="fatal: bad flag '-p' used after filename")
command2 = Command('git log README.md -p CONTRIBUTING.md',
stderr="fatal: bad flag '-p' used after filename")
command3 = Command('git log -p README.md --name-only',
stderr="fatal: bad flag '--name-only' used after filename")
@pytest.mark.parametrize('command', [
command1, command2, command3])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command', [
Command('git log README.md'),
Command('git log -p README.md')])
def test_not_match(command):
assert not match(command)
@pytest.mark.parametrize('command, result', [
(command1, "git log -p README.md"),
(command2, "git log -p README.md CONTRIBUTING.md"),
(command3, "git log -p --name-only README.md")])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.git_pull_uncommitted_changes import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Cannot pull with rebase: You have unstaged changes.'''
def test_match(stderr):
assert match(Command('git pull', stderr=stderr))
assert not match(Command('git pull'))
assert not match(Command('ls', stderr=stderr))
def test_get_new_command(stderr):
assert get_new_command(Command('git pull', stderr=stderr)) \
== "git stash && git pull && git stash pop"

View File

@@ -0,0 +1,19 @@
import pytest
from thefuck.rules.git_pull_uncommitted_changes import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Cannot pull with rebase: Your index contains uncommitted changes.'''
def test_match(stderr):
assert match(Command('git pull', stderr=stderr))
assert not match(Command('git pull'))
assert not match(Command('ls', stderr=stderr))
def test_get_new_command(stderr):
assert get_new_command(Command('git pull', stderr=stderr)) \
== "git stash && git pull && git stash pop"

View File

@@ -14,6 +14,7 @@ To push the current branch and set the remote as upstream, use
def test_match(stderr): def test_match(stderr):
assert match(Command('git push', stderr=stderr))
assert match(Command('git push master', stderr=stderr)) assert match(Command('git push master', stderr=stderr))
assert not match(Command('git push master')) assert not match(Command('git push master'))
assert not match(Command('ls', stderr=stderr)) assert not match(Command('ls', stderr=stderr))
@@ -22,3 +23,11 @@ def test_match(stderr):
def test_get_new_command(stderr): def test_get_new_command(stderr):
assert get_new_command(Command('git push', stderr=stderr))\ assert get_new_command(Command('git push', stderr=stderr))\
== "git push --set-upstream origin master" == "git push --set-upstream origin master"
assert get_new_command(Command('git push -u', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push -u origin', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --set-upstream origin', stderr=stderr))\
== "git push --set-upstream origin master"
assert get_new_command(Command('git push --quiet', stderr=stderr))\
== "git push --set-upstream origin master --quiet"

View File

@@ -0,0 +1,40 @@
import pytest
from thefuck.rules.git_rebase_merge_dir import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return ('\n\nIt seems that there is already a rebase-merge directory, and\n'
'I wonder if you are in the middle of another rebase. If that is the\n'
'case, please try\n'
'\tgit rebase (--continue | --abort | --skip)\n'
'If that is not the case, please\n'
'\trm -fr "/foo/bar/baz/egg/.git/rebase-merge"\n'
'and run me again. I am stopping in case you still have something\n'
'valuable there.\n')
@pytest.mark.parametrize('script', [
('git rebase master'), ('git rebase -skip'), ('git rebase')])
def test_match(stderr, script):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['git rebase master', 'git rebase -abort'])
def test_not_match(script):
assert not match(Command(script=script))
@pytest.mark.parametrize('script, result', [
('git rebase master', [
'git rebase --abort', 'git rebase --skip', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"']),
('git rebase -skip', [
'git rebase --skip', 'git rebase --abort', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"']),
('git rebase', [
'git rebase --skip', 'git rebase --abort', 'git rebase --continue',
'rm -fr "/foo/bar/baz/egg/.git/rebase-merge"'])])
def test_get_new_command(stderr, script, result):
assert get_new_command(Command(script=script, stderr=stderr)) == result

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.rules.git_rm_local_modifications import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr(target):
return ('error: the following file has local modifications:\n {}\n(use '
'--cached to keep the file, or -f to force removal)').format(target)
@pytest.mark.parametrize('script, target', [
('git rm foo', 'foo'),
('git rm foo bar', 'bar')])
def test_match(stderr, script, target):
assert match(Command(script=script, stderr=stderr))
@pytest.mark.parametrize('script', ['git rm foo', 'git rm foo bar', 'git rm'])
def test_not_match(script):
assert not match(Command(script=script, stderr=''))
@pytest.mark.parametrize('script, target, new_command', [
('git rm foo', 'foo', ['git rm --cached foo', 'git rm -f foo']),
('git rm foo bar', 'bar', ['git rm --cached foo bar', 'git rm -f foo bar'])])
def test_get_new_command(stderr, script, target, new_command):
assert get_new_command(Command(script=script, stderr=stderr)) == new_command

View File

@@ -0,0 +1,18 @@
import pytest
from thefuck.rules.git_stash_pop import match, get_new_command
from tests.utils import Command
@pytest.fixture
def stderr():
return '''error: Your local changes to the following files would be overwritten by merge:'''
def test_match(stderr):
assert match(Command('git stash pop', stderr=stderr))
assert not match(Command('git stash'))
def test_get_new_command(stderr):
assert get_new_command(Command('git stash pop', stderr=stderr)) \
== "git add . && git stash pop && git reset ."

View File

@@ -0,0 +1,53 @@
import pytest
from six import BytesIO
from thefuck.rules.ifconfig_device_not_found import match, get_new_command
from tests.utils import Command
stderr = '{}: error fetching interface information: Device not found'
stdout = b'''
wlp2s0 Link encap:Ethernet HWaddr 5c:51:4f:7c:58:5d
inet addr:192.168.0.103 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::be23:69b9:96d2:6d39/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:23581604 errors:0 dropped:0 overruns:0 frame:0
TX packets:17017655 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:16148429061 (16.1 GB) TX bytes:7067533695 (7.0 GB)
'''
@pytest.fixture(autouse=True)
def ifconfig(mocker):
mock = mocker.patch(
'thefuck.rules.ifconfig_device_not_found.subprocess.Popen')
mock.return_value.stdout = BytesIO(stdout)
return mock
@pytest.mark.parametrize('script, stderr', [
('ifconfig wlan0', stderr.format('wlan0')),
('ifconfig -s eth0', stderr.format('eth0')),
])
def test_match(script, stderr):
assert match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, stderr', [
('config wlan0',
'wlan0: error fetching interface information: Device not found'),
('ifconfig eth0', ''),
])
def test_not_match(script, stderr):
assert not match(Command(script, stderr=stderr))
@pytest.mark.parametrize('script, result', [
('ifconfig wlan0', ['ifconfig wlp2s0']),
('ifconfig -s wlan0', ['ifconfig -s wlp2s0']),
])
def test_get_new_comman(script, result):
new_command = get_new_command(
Command(script, stderr=stderr.format('wlan0')))
assert new_command == result

View File

@@ -0,0 +1,12 @@
from thefuck.rules.ls_all import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command(script='ls'))
assert not match(Command(script='ls', stdout='file.py\n'))
def test_get_new_command():
assert get_new_command(Command(script='ls empty_dir')) == 'ls -A empty_dir'
assert get_new_command(Command(script='ls')) == 'ls -A'

View File

@@ -23,7 +23,8 @@ def test_not_match(command):
@pytest.mark.parametrize('command, new_command', [ @pytest.mark.parametrize('command, new_command', [
(Command('man read'), ['man 3 read', 'man 2 read']), (Command('man read'), ['man 3 read', 'man 2 read', 'read --help']),
(Command('man missing', stderr="No manual entry for missing\n"), ['missing --help']),
(Command('man 2 read'), 'man 3 read'), (Command('man 2 read'), 'man 3 read'),
(Command('man 3 read'), 'man 2 read'), (Command('man 3 read'), 'man 2 read'),
(Command('man -s2 read'), 'man -s3 read'), (Command('man -s2 read'), 'man -s3 read'),

View File

@@ -0,0 +1,17 @@
import pytest
from thefuck.rules.remove_trailing_cedilla import match, get_new_command, CEDILLA
from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='wrong' + CEDILLA),
Command(script='wrong with args' + CEDILLA)])
def test_match(command):
assert match(command)
@pytest.mark.parametrize('command, new_command', [
(Command('wrong' + CEDILLA), 'wrong'),
(Command('wrong with args' + CEDILLA), 'wrong with args')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -56,3 +56,8 @@ class TestBash(object):
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines(['ls', 'rm']) history_lines(['ls', 'rm'])
assert list(shell.get_history()) == ['ls', 'rm'] assert list(shell.get_history()) == ['ls', 'rm']
def test_split_command(self, shell):
command = 'git log -p'
command_parts = ['git', 'log', '-p']
assert shell.split_command(command) == command_parts

View File

@@ -76,11 +76,11 @@ class TestFish(object):
def test_app_alias_alter_history(self, settings, shell): def test_app_alias_alter_history(self, settings, shell):
settings.alter_history = True settings.alter_history = True
assert 'history --delete' in shell.app_alias('FUCK') assert 'builtin history delete' in shell.app_alias('FUCK')
assert 'history --merge' in shell.app_alias('FUCK') assert 'builtin history merge' in shell.app_alias('FUCK')
settings.alter_history = False settings.alter_history = False
assert 'history --delete' not in shell.app_alias('FUCK') assert 'builtin history delete' not in shell.app_alias('FUCK')
assert 'history --merge' not in shell.app_alias('FUCK') assert 'builtin history merge' not in shell.app_alias('FUCK')
def test_get_history(self, history_lines, shell): def test_get_history(self, history_lines, shell):
history_lines(['- cmd: ls', ' when: 1432613911', history_lines(['- cmd: ls', ' when: 1432613911',

View File

@@ -1,5 +1,6 @@
import pytest import pytest
import six import six
import os
from mock import Mock from mock import Mock
from thefuck import const from thefuck import const
@@ -101,3 +102,22 @@ class TestInitializeSettingsFile(object):
for setting in const.DEFAULT_SETTINGS.items(): for setting in const.DEFAULT_SETTINGS.items():
assert '# {} = {}\n'.format(*setting) in settings_file_contents assert '# {} = {}\n'.format(*setting) in settings_file_contents
settings_file.close() settings_file.close()
@pytest.mark.parametrize('legacy_dir_exists, xdg_config_home, result', [
(False, '~/.config', '~/.config/thefuck'),
(False, '/user/test/config/', '/user/test/config/thefuck'),
(True, '~/.config', '~/.thefuck'),
(True, '/user/test/config/', '~/.thefuck')])
def test_get_user_dir_path(mocker, environ, settings, legacy_dir_exists,
xdg_config_home, result):
mocker.patch('thefuck.conf.Path.is_dir',
return_value=legacy_dir_exists)
if xdg_config_home is not None:
environ['XDG_CONFIG_HOME'] = xdg_config_home
else:
environ.pop('XDG_CONFIG_HOME', None)
path = settings._get_user_dir_path().as_posix()
assert path == os.path.expanduser(result)

View File

@@ -1,13 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pytest import pytest
try:
from pathlib import Path
pathlib_name = 'pathlib'
except ImportError:
from pathlib2 import Path
pathlib_name = 'pathlib2'
from thefuck import corrector, const from thefuck import corrector, const
from thefuck.system import Path
from tests.utils import Rule, Command, CorrectedCommand from tests.utils import Rule, Command, CorrectedCommand
from thefuck.corrector import get_corrected_commands, organize_commands from thefuck.corrector import get_corrected_commands, organize_commands
@@ -16,7 +11,7 @@ class TestGetRules(object):
@pytest.fixture @pytest.fixture
def glob(self, mocker): def glob(self, mocker):
results = {} results = {}
mocker.patch(pathlib_name + '.Path.glob', mocker.patch('thefuck.system.Path.glob',
new_callable=lambda: lambda *_: results.pop('value', [])) new_callable=lambda: lambda *_: results.pop('value', []))
return lambda value: results.update({'value': value}) return lambda value: results.update({'value': value})

View File

@@ -3,14 +3,11 @@
import os import os
from subprocess import PIPE from subprocess import PIPE
from mock import Mock from mock import Mock
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
import pytest import pytest
from tests.utils import CorrectedCommand, Rule, Command from tests.utils import CorrectedCommand, Rule, Command
from thefuck import const from thefuck import const
from thefuck.exceptions import EmptyCommand from thefuck.exceptions import EmptyCommand
from thefuck.system import Path
class TestCorrectedCommand(object): class TestCorrectedCommand(object):

View File

@@ -6,7 +6,7 @@ from mock import Mock
import six import six
from thefuck.utils import default_settings, \ from thefuck.utils import default_settings, \
memoize, get_closest, get_all_executables, replace_argument, \ memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands, is_app, for_app, cache, compatibility_call, \ get_all_matched_commands, is_app, for_app, cache, \
get_valid_history_without_current get_valid_history_without_current
from tests.utils import Command from tests.utils import Command
@@ -188,56 +188,6 @@ class TestCache(object):
assert shelve == {key: {'etag': '0', 'value': 'test'}} assert shelve == {key: {'etag': '0', 'value': 'test'}}
class TestCompatibilityCall(object):
def test_match(self):
def match(command):
assert command == Command()
return True
assert compatibility_call(match, Command())
def test_old_match(self, settings):
def match(command, _settings):
assert command == Command()
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(match, Command())
def test_get_new_command(self):
def get_new_command(command):
assert command == Command()
return True
assert compatibility_call(get_new_command, Command())
def test_old_get_new_command(self, settings):
def get_new_command(command, _settings):
assert command == Command()
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(get_new_command, Command())
def test_side_effect(self):
def side_effect(command, new_command):
assert command == Command() == new_command
return True
assert compatibility_call(side_effect, Command(), Command())
def test_old_side_effect(self, settings):
def side_effect(command, new_command, _settings):
assert command == Command() == new_command
assert settings == _settings
return True
with pytest.warns(UserWarning):
assert compatibility_call(side_effect, Command(), Command())
class TestGetValidHistoryWithoutCurrent(object): class TestGetValidHistoryWithoutCurrent(object):
@pytest.yield_fixture(autouse=True) @pytest.yield_fixture(autouse=True)
def fail_on_warning(self): def fail_on_warning(self):

View File

@@ -1,12 +1,10 @@
from imp import load_source from imp import load_source
import os import os
import sys import sys
try: from warnings import warn
from pathlib import Path
except ImportError:
from pathlib2 import Path
from six import text_type from six import text_type
from . import const from . import const
from .system import Path
class Settings(dict): class Settings(dict):
@@ -42,15 +40,18 @@ class Settings(dict):
settings_file.write(u'# {} = {}\n'.format(*setting)) settings_file.write(u'# {} = {}\n'.format(*setting))
def _get_user_dir_path(self): def _get_user_dir_path(self):
# for backward compatibility, use `~/.thefuck` if it exists """Returns Path object representing the user config resource"""
legacy_user_dir = Path(os.path.expanduser('~/.thefuck')) xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config')
user_dir = Path(xdg_config_home, 'thefuck').expanduser()
legacy_user_dir = Path('~', '.thefuck').expanduser()
# For backward compatibility use legacy '~/.thefuck' if it exists:
if legacy_user_dir.is_dir(): if legacy_user_dir.is_dir():
warn(u'Config path {} is deprecated. Please move to {}'.format(
legacy_user_dir, user_dir))
return legacy_user_dir return legacy_user_dir
else: else:
default_xdg_config_dir = os.path.expanduser("~/.config") return user_dir
xdg_config_dir = os.getenv("XDG_CONFIG_HOME", default_xdg_config_dir)
return Path(os.path.join(xdg_config_dir, 'thefuck'))
def _setup_user_dir(self): def _setup_user_dir(self):
"""Returns user config dir, create it when it doesn't exist.""" """Returns user config dir, create it when it doesn't exist."""

View File

@@ -1,9 +1,6 @@
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from .conf import settings from .conf import settings
from .types import Rule from .types import Rule
from .system import Path
from . import logs from . import logs

View File

@@ -4,7 +4,6 @@ from .system import init_output
init_output() init_output()
from argparse import ArgumentParser from argparse import ArgumentParser
from warnings import warn
from pprint import pformat from pprint import pformat
import sys import sys
from . import logs, types from . import logs, types
@@ -37,17 +36,13 @@ def fix_command():
sys.exit(1) sys.exit(1)
def print_alias(entry_point=True): def print_alias():
"""Prints alias for current shell.""" """Prints alias for current shell."""
if entry_point: try:
warn('`thefuck-alias` is deprecated, use `thefuck --alias` instead.') alias = sys.argv[2]
position = 1 except IndexError:
else: alias = get_alias()
position = 2
alias = get_alias()
if len(sys.argv) > position:
alias = sys.argv[position]
print(shell.app_alias(alias)) print(shell.app_alias(alias))
@@ -76,8 +71,9 @@ def main():
nargs='*', nargs='*',
help='command that should be fixed') help='command that should be fixed')
known_args = parser.parse_args(sys.argv[1:2]) known_args = parser.parse_args(sys.argv[1:2])
if known_args.alias: if known_args.alias:
print_alias(False) print_alias()
elif known_args.command: elif known_args.command:
fix_command() fix_command()
else: else:

View File

@@ -0,0 +1,10 @@
from thefuck.utils import for_app
@for_app('ag')
def match(command):
return command.stderr.endswith('run ag with -Q\n')
def get_new_command(command):
return command.script.replace('ag', 'ag -Q', 1)

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

@@ -0,0 +1,17 @@
import re
from thefuck.utils import for_app, replace_argument
INVALID_CHOICE = "(?<=Invalid choice: ')(.*)(?=', maybe you meant:)"
OPTIONS = "^\s*\*\s(.*)"
@for_app('aws')
def match(command):
return "usage:" in command.stderr and "maybe you meant:" in command.stderr
def get_new_command(command):
mistake = re.search(INVALID_CHOICE, command.stderr).group(0)
options = re.findall(OPTIONS, command.stderr, flags=re.MULTILINE)
return [replace_argument(command.script, mistake, o) for o in options]

View File

@@ -0,0 +1,15 @@
from thefuck.utils import for_app
@for_app('brew', at_least=2)
def match(command):
return (command.script_parts[1] in ['ln', 'link']
and "brew link --overwrite --dry-run" in command.stderr)
def get_new_command(command):
command_parts = command.script_parts[:]
command_parts[1] = 'link'
command_parts.insert(2, '--overwrite')
command_parts.insert(3, '--dry-run')
return ' '.join(command_parts)

View File

@@ -0,0 +1,14 @@
from thefuck.utils import for_app
@for_app('brew', at_least=2)
def match(command):
return (command.script_parts[1] in ['uninstall', 'rm', 'remove']
and "brew uninstall --force" in command.stdout)
def get_new_command(command):
command_parts = command.script_parts[:]
command_parts[1] = 'uninstall'
command_parts.insert(2, '--force')
return ' '.join(command_parts)

View File

@@ -1,18 +1,27 @@
import re import re
from thefuck.shells import shell from thefuck.shells import shell
from thefuck.specific.git import git_support from thefuck.specific.git import git_support
from thefuck.system import Path
from thefuck.utils import memoize
@memoize
def _get_missing_file(command):
pathspec = re.findall(
r"error: pathspec '([^']*)' "
r'did not match any file\(s\) known to git.', command.stderr)[0]
if Path(pathspec).exists():
return pathspec
@git_support @git_support
def match(command): def match(command):
return 'did not match any file(s) known to git.' in command.stderr return ('did not match any file(s) known to git.' in command.stderr
and _get_missing_file(command))
@git_support @git_support
def get_new_command(command): def get_new_command(command):
missing_file = re.findall( missing_file = _get_missing_file(command)
r"error: pathspec '([^']*)' "
r'did not match any file\(s\) known to git.', command.stderr)[0]
formatme = shell.and_('git add -- {}', '{}') formatme = shell.and_('git add -- {}', '{}')
return formatme.format(missing_file, command.script) return formatme.format(missing_file, command.script)

View File

@@ -0,0 +1,16 @@
import re
from thefuck.utils import replace_command
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('bisect' in command.script_parts and
'usage: git bisect' in command.stderr)
@git_support
def get_new_command(command):
broken = re.findall(r'git bisect ([^ $]*).*', command.script)[0]
usage = re.findall(r'usage: git bisect \[([^\]]+)\]', command.stderr)[0]
return replace_command(command, broken, usage.split('|'))

View File

@@ -0,0 +1,17 @@
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support
@git_support
def match(command):
files = [arg for arg in command.script_parts[2:]
if not arg.startswith('-')]
return ('diff' in command.script
and '--no-index' not in command.script
and not command.stdout
and len(files) == 2)
@git_support
def get_new_command(command):
return replace_argument(command.script, 'diff', 'diff --no-index')

View File

@@ -0,0 +1,30 @@
import re
from thefuck.specific.git import git_support
error_pattern = "fatal: bad flag '(.*?)' used after filename"
@git_support
def match(command):
return re.search(error_pattern, command.stderr)
@git_support
def get_new_command(command):
command_parts = command.script_parts[:]
# find the bad flag
bad_flag = re.search(error_pattern, command.stderr).group(1)
bad_flag_index = command_parts.index(bad_flag)
# find the filename
for index in reversed(range(bad_flag_index)):
if command_parts[index][0] != '-':
filename_index = index
break
# swap them
command_parts[bad_flag_index], command_parts[filename_index] = \
command_parts[filename_index], command_parts[bad_flag_index] # noqa: E122
return u' '.join(command_parts)

View File

@@ -0,0 +1,14 @@
from thefuck.shells import shell
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('pull' in command.script
and ('You have unstaged changes' in command.stderr
or 'contains uncommitted changes' in command.stderr))
@git_support
def get_new_command(command):
return shell.and_('git stash', 'git pull', 'git stash pop')

View File

@@ -1,3 +1,4 @@
from thefuck.utils import replace_argument
from thefuck.specific.git import git_support from thefuck.specific.git import git_support
@@ -7,6 +8,29 @@ def match(command):
and 'set-upstream' in command.stderr) and 'set-upstream' in command.stderr)
def _get_upstream_option_index(command_parts):
if '--set-upstream' in command_parts:
return command_parts.index('--set-upstream')
elif '-u' in command_parts:
return command_parts.index('-u')
else:
return None
@git_support @git_support
def get_new_command(command): def get_new_command(command):
return command.stderr.split('\n')[-3].strip() # If --set-upstream or -u are passed, remove it and its argument. This is
# because the remaining arguments are concatenated onto the command suggested
# by git, which includes --set-upstream and its argument
command_parts = command.script_parts[:]
upstream_option_index = _get_upstream_option_index(command_parts)
if upstream_option_index is not None:
command_parts.pop(upstream_option_index)
# In case of `git push -u` we don't have next argument:
if len(command_parts) > upstream_option_index:
command_parts.pop(upstream_option_index)
push_upstream = command.stderr.split('\n')[-3].strip().partition('git ')[2]
return replace_argument(" ".join(command_parts), 'push', push_upstream)

View File

@@ -0,0 +1,17 @@
from difflib import get_close_matches
from thefuck.specific.git import git_support
@git_support
def match(command):
return (' rebase' in command.script and
'It seems that there is already a rebase-merge directory' in command.stderr and
'I wonder if you are in the middle of another rebase' in command.stderr)
@git_support
def get_new_command(command):
command_list = ['git rebase --continue', 'git rebase --abort', 'git rebase --skip']
rm_cmd = command.stderr.split('\n')[-4]
command_list.append(rm_cmd.strip())
return get_close_matches(command.script, command_list, 4, 0)

View File

@@ -0,0 +1,19 @@
from thefuck.specific.git import git_support
@git_support
def match(command):
return (' rm ' in command.script and
'error: the following file has local modifications' in command.stderr and
'use --cached to keep the file, or -f to force removal' in command.stderr)
@git_support
def get_new_command(command):
command_parts = command.script_parts[:]
index = command_parts.index('rm') + 1
command_parts.insert(index, '--cached')
command_list = [u' '.join(command_parts)]
command_parts[index] = '-f'
command_list.append(u' '.join(command_parts))
return command_list

View File

@@ -10,6 +10,7 @@ def match(command):
@git_support @git_support
def get_new_command(command): def get_new_command(command):
index = command.script_parts.index('rm') + 1 command_parts = command.script_parts[:]
command.script_parts.insert(index, '-r') index = command_parts.index('rm') + 1
return u' '.join(command.script_parts) command_parts.insert(index, '-r')
return u' '.join(command_parts)

View File

@@ -0,0 +1,18 @@
from thefuck.shells import shell
from thefuck.specific.git import git_support
@git_support
def match(command):
return ('stash' in command.script
and 'pop' in command.script
and 'Your local changes to the following files would be overwritten by merge' in command.stderr)
@git_support
def get_new_command(command):
return shell.and_('git add .', 'git stash pop', 'git reset .')
# make it come before the other applicable rules
priority = 900

View File

@@ -0,0 +1,25 @@
import subprocess
from thefuck.utils import for_app, replace_command, eager
import sys
@for_app('ifconfig')
def match(command):
return 'error fetching interface information: Device not found' \
in command.stderr
@eager
def _get_possible_interfaces():
proc = subprocess.Popen(['ifconfig', '-a'], stdout=subprocess.PIPE)
for line in proc.stdout.readlines():
line = line.decode()
if line and line != '\n' and not line.startswith(' '):
yield line.split(' ')[0]
def get_new_command(command):
interface = command.stderr.split(' ')[0][:-1]
possible_interfaces = _get_possible_interfaces()
return replace_command(command, interface, possible_interfaces)

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

@@ -0,0 +1,10 @@
from thefuck.utils import for_app
@for_app('ls')
def match(command):
return command.stdout.strip() == ''
def get_new_command(command):
return ' '.join(['ls', '-A'] + command.script_parts[1:])

View File

@@ -12,10 +12,22 @@ def get_new_command(command):
if '2' in command.script: if '2' in command.script:
return command.script.replace("2", "3") return command.script.replace("2", "3")
last_arg = command.script_parts[-1]
help_command = last_arg + ' --help'
# If there are no man pages for last_arg, suggest `last_arg --help` instead.
# Otherwise, suggest `--help` after suggesting other man page sections.
if command.stderr.strip() == 'No manual entry for ' + last_arg:
return [help_command]
split_cmd2 = command.script_parts split_cmd2 = command.script_parts
split_cmd3 = split_cmd2[:] split_cmd3 = split_cmd2[:]
split_cmd2.insert(1, ' 2 ') split_cmd2.insert(1, ' 2 ')
split_cmd3.insert(1, ' 3 ') split_cmd3.insert(1, ' 3 ')
return ["".join(split_cmd3), "".join(split_cmd2)] return [
"".join(split_cmd3),
"".join(split_cmd2),
help_command,
]

View File

@@ -6,9 +6,8 @@ from thefuck.specific.sudo import sudo_support
@sudo_support @sudo_support
def match(command): def match(command):
toks = command.script_parts return (command.script_parts
return (toks and command.script_parts[0].endswith('.py')
and toks[0].endswith('.py')
and ('Permission denied' in command.stderr or and ('Permission denied' in command.stderr or
'command not found' in command.stderr)) 'command not found' in command.stderr))

View File

@@ -0,0 +1,11 @@
# -*- encoding: utf-8 -*-
CEDILLA = u"ç"
def match(command):
return command.script.endswith(CEDILLA)
def get_new_command(command):
return command.script[:-1]

View File

@@ -19,7 +19,8 @@ patterns = ['permission denied',
'you don\'t have access to the history db.', 'you don\'t have access to the history db.',
'authentication is required', 'authentication is required',
'edspermissionerror', 'edspermissionerror',
'you don\'t have write permissions'] 'you don\'t have write permissions',
'use `sudo`']
def match(command): def match(command):

View File

@@ -17,6 +17,6 @@ def match(command):
@sudo_support @sudo_support
def get_new_command(command): def get_new_command(command):
cmd = command.script_parts cmd = command.script_parts[:]
cmd[-1], cmd[-2] = cmd[-2], cmd[-1] cmd[-1], cmd[-2] = cmd[-2], cmd[-1]
return ' '.join(cmd) return ' '.join(cmd)

View File

@@ -1,9 +1,5 @@
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from thefuck.utils import for_app, replace_command, eager, memoize from thefuck.utils import for_app, replace_command, eager, memoize
from thefuck.system import Path
@memoize @memoize

View File

@@ -22,7 +22,7 @@ shells = {'bash': Bash,
def _get_shell(): def _get_shell():
proc = Process(os.getpid()) proc = Process(os.getpid())
while proc is not None: while proc is not None and proc.pid > 0:
try: try:
name = proc.name() name = proc.name()
except TypeError: except TypeError:

View File

@@ -14,7 +14,7 @@ class Bash(Generic):
" eval $TF_CMD".format(fuck) " eval $TF_CMD".format(fuck)
if settings.alter_history: if settings.alter_history:
return alias + " && history -s $TF_CMD'" return alias + "; history -s $TF_CMD'"
else: else:
return alias + "'" return alias + "'"

View File

@@ -20,8 +20,9 @@ class Fish(Generic):
def app_alias(self, fuck): def app_alias(self, fuck):
if settings.alter_history: if settings.alter_history:
alter_history = (' history --delete $fucked_up_command\n' alter_history = (' builtin history delete --exact'
' history --merge ^ /dev/null\n') ' --case-sensitive -- $fucked_up_command\n'
' builtin history merge ^ /dev/null\n')
else: else:
alter_history = '' alter_history = ''
# It is VERY important to have the variables declared WITHIN the alias # It is VERY important to have the variables declared WITHIN the alias

View File

@@ -65,9 +65,19 @@ class Generic(object):
def split_command(self, command): def split_command(self, command):
"""Split the command using shell-like syntax.""" """Split the command using shell-like syntax."""
encoded = self.encode_utf8(command)
splitted = shlex.split(encoded)
return self.decode_utf8(splitted)
def encode_utf8(self, command):
if six.PY2: if six.PY2:
return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))] return command.encode('utf8')
return shlex.split(command) return command
def decode_utf8(self, command_parts):
if six.PY2:
return [s.decode('utf8') for s in command_parts]
return command_parts
def quote(self, s): def quote(self, s):
"""Return a shell-escaped version of the string s.""" """Return a shell-escaped version of the string s."""

View File

@@ -15,7 +15,7 @@ class Zsh(Generic):
" eval $TF_CMD".format(alias_name) " eval $TF_CMD".format(alias_name)
if settings.alter_history: if settings.alter_history:
return alias + " && print -s $TF_CMD'" return alias + " ; test -n \"$TF_CMD\" && print -s $TF_CMD'"
else: else:
return alias + "'" return alias + "'"

View File

@@ -1,3 +1,4 @@
import os
import sys import sys
import tty import tty
import termios import termios
@@ -33,3 +34,15 @@ def get_key():
return const.KEY_DOWN return const.KEY_DOWN
return ch return ch
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
def _expanduser(self):
return self.__class__(os.path.expanduser(str(self)))
if not hasattr(Path, 'expanduser'):
Path.expanduser = _expanduser

View File

@@ -25,3 +25,15 @@ def get_key():
encoding = sys.stdout.encoding or os.environ.get('PYTHONIOENCODING', 'utf-8') encoding = sys.stdout.encoding or os.environ.get('PYTHONIOENCODING', 'utf-8')
return ch.decode(encoding) return ch.decode(encoding)
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
def _expanduser(self):
return self.__class__(os.path.expanduser(str(self)))
# pathlib's expanduser fails on windows, see http://bugs.python.org/issue19776
Path.expanduser = _expanduser

View File

@@ -9,7 +9,6 @@ from .shells import shell
from .conf import settings from .conf import settings
from .const import DEFAULT_PRIORITY, ALL_ENABLED from .const import DEFAULT_PRIORITY, ALL_ENABLED
from .exceptions import EmptyCommand from .exceptions import EmptyCommand
from .utils import compatibility_call
class Command(object): class Command(object):
@@ -35,7 +34,8 @@ class Command(object):
except Exception: except Exception:
logs.debug(u"Can't split command script {} because:\n {}".format( logs.debug(u"Can't split command script {} because:\n {}".format(
self, sys.exc_info())) self, sys.exc_info()))
self._script_parts = None self._script_parts = []
return self._script_parts return self._script_parts
def __eq__(self, other): def __eq__(self, other):
@@ -225,7 +225,7 @@ class Rule(object):
try: try:
with logs.debug_time(u'Trying rule: {};'.format(self.name)): with logs.debug_time(u'Trying rule: {};'.format(self.name)):
if compatibility_call(self.match, command): if self.match(command):
return True return True
except Exception: except Exception:
logs.rule_failed(self, sys.exc_info()) logs.rule_failed(self, sys.exc_info())
@@ -237,7 +237,7 @@ class Rule(object):
:rtype: Iterable[CorrectedCommand] :rtype: Iterable[CorrectedCommand]
""" """
new_commands = compatibility_call(self.get_new_command, command) new_commands = self.get_new_command(command)
if not isinstance(new_commands, list): if not isinstance(new_commands, list):
new_commands = (new_commands,) new_commands = (new_commands,)
for n, new_command in enumerate(new_commands): for n, new_command in enumerate(new_commands):
@@ -283,7 +283,7 @@ class CorrectedCommand(object):
""" """
if self.side_effect: if self.side_effect:
compatibility_call(self.side_effect, old_cmd, self.script) self.side_effect(old_cmd, self.script)
if settings.alter_history: if settings.alter_history:
shell.put_to_history(self.script) shell.put_to_history(self.script)
# This depends on correct setting of PYTHONIOENCODING by the alias: # This depends on correct setting of PYTHONIOENCODING by the alias:

View File

@@ -4,17 +4,13 @@ import pkg_resources
import re import re
import shelve import shelve
import six import six
from .conf import settings
from contextlib import closing from contextlib import closing
from decorator import decorator from decorator import decorator
from difflib import get_close_matches from difflib import get_close_matches
from functools import wraps from functools import wraps
from inspect import getargspec
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from warnings import warn from warnings import warn
from .conf import settings
from .system import Path
DEVNULL = open(os.devnull, 'w') DEVNULL = open(os.devnull, 'w')
@@ -163,7 +159,7 @@ def is_app(command, *app_names, **kwargs):
if kwargs: if kwargs:
raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys())) raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))
if command.script_parts is not None and len(command.script_parts) > at_least: if len(command.script_parts) > at_least:
return command.script_parts[0] in app_names return command.script_parts[0] in app_names
return False return False
@@ -245,27 +241,6 @@ def cache(*depends_on):
cache.disabled = False cache.disabled = False
def compatibility_call(fn, *args):
"""Special call for compatibility with user-defined old-style rules
with `settings` param.
"""
fn_args_count = len(getargspec(fn).args)
if fn.__name__ in ('match', 'get_new_command') and fn_args_count == 2:
warn("Two arguments `{}` from rule `{}` is deprecated, please "
"remove `settings` argument and use "
"`from thefuck.conf import settings` instead."
.format(fn.__name__, fn.__module__))
args += (settings,)
if fn.__name__ == 'side_effect' and fn_args_count == 3:
warn("Three arguments `side_effect` from rule `{}` is deprecated, "
"please remove `settings` argument and use `from thefuck.conf "
"import settings` instead."
.format(fn.__name__, fn.__module__))
args += (settings,)
return fn(*args)
def get_installation_info(): def get_installation_info():
return pkg_resources.require('thefuck')[0] return pkg_resources.require('thefuck')[0]

View File

@@ -1,5 +1,5 @@
[tox] [tox]
envlist = py27,py33,py34,py35 envlist = py27,py33,py34,py35,py36
[testenv] [testenv]
deps = -rrequirements.txt deps = -rrequirements.txt