mirror of
https://github.com/nvbn/thefuck.git
synced 2025-02-22 04:48:57 +00:00
Merge branch 'master' into flake8
This commit is contained in:
commit
b038ea4541
11
.travis.yml
11
.travis.yml
@ -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
|
||||||
@ -42,7 +45,7 @@ script:
|
|||||||
- flake8
|
- flake8
|
||||||
- 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
|
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
|
||||||
|
23
README.md
23
README.md
@ -144,7 +144,8 @@ sudo -H 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:
|
||||||
|
|
||||||
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`
|
* `ag_literal` – adds `-Q` to `ag` when suggested;
|
||||||
|
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
|
||||||
* `cargo` – runs `cargo build` instead of `cargo`;
|
* `cargo` – runs `cargo build` instead of `cargo`;
|
||||||
* `cargo_no_command` – fixes wrongs commands like `cargo buid`;
|
* `cargo_no_command` – fixes wrongs commands like `cargo buid`;
|
||||||
* `cd_correction` – spellchecks and correct failed cd commands;
|
* `cd_correction` – spellchecks and correct failed cd commands;
|
||||||
@ -163,7 +164,9 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `fab_command_not_found` – fix misspelled fabric commands;
|
* `fab_command_not_found` – fix misspelled fabric commands;
|
||||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||||
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
||||||
|
* `gem_unknown_command` – fixes wrong `gem` commands;
|
||||||
* `git_add` – fixes *"pathspec 'foo' did not match any file(s) known to git."*;
|
* `git_add` – fixes *"pathspec 'foo' did not match any file(s) known to git."*;
|
||||||
|
* `git_add_force` – adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
|
||||||
* `git_bisect_usage` – fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
|
* `git_bisect_usage` – fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
|
||||||
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
||||||
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
||||||
@ -172,6 +175,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
||||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||||
|
* `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename`
|
||||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
||||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
||||||
* `git_pull` – sets upstream before executing previous `git pull`;
|
* `git_pull` – sets upstream before executing previous `git pull`;
|
||||||
@ -180,9 +184,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||||
* `git_rebase_no_changes` – runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
|
* `git_rebase_no_changes` – runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
|
||||||
|
* `git_rm_local_modifications` – adds `-f` or `--cached` when you try to `rm` a locally modified file;
|
||||||
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
||||||
|
* `git_rm_staged` – adds `-f` or `--cached` when you try to `rm` a file with staged changes
|
||||||
|
* `git_rebase_merge_dir` – offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
|
||||||
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
||||||
* `git_stash` – stashes you local modifications before rebasing or switching branch;
|
* `git_stash` – stashes your local modifications before rebasing or switching branch;
|
||||||
|
* `git_stash_pop` – adds your local modifications before popping stash, then resets;
|
||||||
|
* `git_tag_force` – adds `--force` to `git tag <tagname>` when the tag already exists;
|
||||||
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
||||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
||||||
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
||||||
@ -194,11 +203,13 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||||
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
||||||
* `history` – tries to replace command with most similar command from history;
|
* `history` – tries to replace command with most similar command from history;
|
||||||
|
* `ifconfig_device_not_found` – fixes wrong device names like `wlan0` to `wlp2s0`;
|
||||||
* `java` – removes `.java` extension when running Java programs;
|
* `java` – removes `.java` extension when running Java programs;
|
||||||
* `javac` – appends missing `.java` when compiling Java files;
|
* `javac` – appends missing `.java` when compiling Java files;
|
||||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||||
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
||||||
* `ln_s_order` – fixes `ln -s` arguments order;
|
* `ln_s_order` – fixes `ln -s` arguments order;
|
||||||
|
* `ls_all` – adds `-A` to `ls` when output is empty;
|
||||||
* `ls_lah` – adds `-lah` to `ls`;
|
* `ls_lah` – adds `-lah` to `ls`;
|
||||||
* `man` – changes manual section;
|
* `man` – changes manual section;
|
||||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||||
@ -220,10 +231,12 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
||||||
|
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
||||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||||
* `sl_ls` – changes `sl` to `ls`;
|
* `sl_ls` – changes `sl` to `ls`;
|
||||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
||||||
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
|
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
|
||||||
|
* `sudo_command_from_user_path` – runs commands from users `$PATH` with `sudo`;
|
||||||
* `switch_lang` – switches command from your local layout to en;
|
* `switch_lang` – switches command from your local layout to en;
|
||||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||||
* `test.py` – runs `py.test` instead of `test.py`;
|
* `test.py` – runs `py.test` instead of `test.py`;
|
||||||
@ -235,6 +248,8 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `vagrant_up` – starts up the vagrant instance;
|
* `vagrant_up` – starts up the vagrant instance;
|
||||||
* `whois` – fixes `whois` command;
|
* `whois` – fixes `whois` command;
|
||||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
||||||
|
* `yarn_alias` – fixes aliased `yarn` commands like `yarn ls`;
|
||||||
|
* `yarn_command_not_found` – fixes misspelled `yarn` commands;
|
||||||
|
|
||||||
Enabled by default only on specific platforms:
|
Enabled by default only on specific platforms:
|
||||||
|
|
||||||
@ -272,7 +287,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)).
|
||||||
|
@ -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%"
|
||||||
|
2
setup.py
2
setup.py
@ -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'],
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
25
tests/rules/test_ag_literal.py
Normal file
25
tests/rules/test_ag_literal.py
Normal 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
|
@ -7,6 +7,8 @@ from tests.utils import Command
|
|||||||
(Command(script='vim', stderr='vim: command not found'),
|
(Command(script='vim', stderr='vim: command not found'),
|
||||||
[('vim', 'main'), ('vim-tiny', 'main')]),
|
[('vim', 'main'), ('vim-tiny', 'main')]),
|
||||||
(Command(script='sudo vim', stderr='vim: command not found'),
|
(Command(script='sudo vim', stderr='vim: command not found'),
|
||||||
|
[('vim', 'main'), ('vim-tiny', 'main')]),
|
||||||
|
(Command(script='vim', stderr="The program 'vim' is currently not installed. You can install it by typing: sudo apt install vim"),
|
||||||
[('vim', 'main'), ('vim-tiny', 'main')])])
|
[('vim', 'main'), ('vim-tiny', 'main')])])
|
||||||
def test_match(mocker, command, packages):
|
def test_match(mocker, command, packages):
|
||||||
mocker.patch('thefuck.rules.apt_get.which', return_value=None)
|
mocker.patch('thefuck.rules.apt_get.which', return_value=None)
|
||||||
|
82
tests/rules/test_gem_unknown_command.py
Normal file
82
tests/rules/test_gem_unknown_command.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import pytest
|
||||||
|
from six import BytesIO
|
||||||
|
from thefuck.rules.gem_unknown_command import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
stderr = '''
|
||||||
|
ERROR: While executing gem ... (Gem::CommandLineError)
|
||||||
|
Unknown command {}
|
||||||
|
'''
|
||||||
|
|
||||||
|
gem_help_commands_stdout = b'''
|
||||||
|
GEM commands are:
|
||||||
|
|
||||||
|
build Build a gem from a gemspec
|
||||||
|
cert Manage RubyGems certificates and signing settings
|
||||||
|
check Check a gem repository for added or missing files
|
||||||
|
cleanup Clean up old versions of installed gems
|
||||||
|
contents Display the contents of the installed gems
|
||||||
|
dependency Show the dependencies of an installed gem
|
||||||
|
environment Display information about the RubyGems environment
|
||||||
|
fetch Download a gem and place it in the current directory
|
||||||
|
generate_index Generates the index files for a gem server directory
|
||||||
|
help Provide help on the 'gem' command
|
||||||
|
install Install a gem into the local repository
|
||||||
|
list Display local gems whose name matches REGEXP
|
||||||
|
lock Generate a lockdown list of gems
|
||||||
|
mirror Mirror all gem files (requires rubygems-mirror)
|
||||||
|
open Open gem sources in editor
|
||||||
|
outdated Display all gems that need updates
|
||||||
|
owner Manage gem owners of a gem on the push server
|
||||||
|
pristine Restores installed gems to pristine condition from files
|
||||||
|
located in the gem cache
|
||||||
|
push Push a gem up to the gem server
|
||||||
|
query Query gem information in local or remote repositories
|
||||||
|
rdoc Generates RDoc for pre-installed gems
|
||||||
|
search Display remote gems whose name matches REGEXP
|
||||||
|
server Documentation and gem repository HTTP server
|
||||||
|
sources Manage the sources and cache file RubyGems uses to search
|
||||||
|
for gems
|
||||||
|
specification Display gem specification (in yaml)
|
||||||
|
stale List gems along with access times
|
||||||
|
uninstall Uninstall gems from the local repository
|
||||||
|
unpack Unpack an installed gem to the current directory
|
||||||
|
update Update installed gems to the latest version
|
||||||
|
which Find the location of a library file you can require
|
||||||
|
yank Remove a pushed gem from the index
|
||||||
|
|
||||||
|
For help on a particular command, use 'gem help COMMAND'.
|
||||||
|
|
||||||
|
Commands may be abbreviated, so long as they are unambiguous.
|
||||||
|
e.g. 'gem i rake' is short for 'gem install rake'.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def gem_help_commands(mocker):
|
||||||
|
patch = mocker.patch('subprocess.Popen')
|
||||||
|
patch.return_value.stdout = BytesIO(gem_help_commands_stdout)
|
||||||
|
return patch
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, command', [
|
||||||
|
('gem isntall jekyll', 'isntall'),
|
||||||
|
('gem last --local', 'last')])
|
||||||
|
def test_match(script, command):
|
||||||
|
assert match(Command(script, stderr=stderr.format(command)))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr', [
|
||||||
|
('gem install jekyll', ''),
|
||||||
|
('git log', stderr.format('log'))])
|
||||||
|
def test_not_match(script, stderr):
|
||||||
|
assert not match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, result', [
|
||||||
|
('gem isntall jekyll', stderr.format('isntall'), 'gem install jekyll'),
|
||||||
|
('gem last --local', stderr.format('last'), 'gem list --local')])
|
||||||
|
def test_get_new_command(script, stderr, result):
|
||||||
|
new_command = get_new_command(Command(script, stderr=stderr))
|
||||||
|
assert new_command[0] == result
|
22
tests/rules/test_git_add_force.py
Normal file
22
tests/rules/test_git_add_force.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_add_force import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr():
|
||||||
|
return ('The following paths are ignored by one of your .gitignore files:\n'
|
||||||
|
'dist/app.js\n'
|
||||||
|
'dist/background.js\n'
|
||||||
|
'dist/options.js\n'
|
||||||
|
'Use -f if you really want to add them.\n')
|
||||||
|
|
||||||
|
|
||||||
|
def test_match(stderr):
|
||||||
|
assert match(Command('git add dist/*.js', stderr=stderr))
|
||||||
|
assert not match(Command('git add dist/*.js'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_new_command(stderr):
|
||||||
|
assert get_new_command(Command('git add dist/*.js', stderr=stderr)) \
|
||||||
|
== "git add --force dist/*.js"
|
31
tests/rules/test_git_flag_after_filename.py
Normal file
31
tests/rules/test_git_flag_after_filename.py
Normal 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
|
@ -23,6 +23,8 @@ 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))\
|
assert get_new_command(Command('git push -u origin', stderr=stderr))\
|
||||||
== "git push --set-upstream origin master"
|
== "git push --set-upstream origin master"
|
||||||
assert get_new_command(Command('git push --set-upstream origin', stderr=stderr))\
|
assert get_new_command(Command('git push --set-upstream origin', stderr=stderr))\
|
||||||
|
40
tests/rules/test_git_rebase_merge_dir.py
Normal file
40
tests/rules/test_git_rebase_merge_dir.py
Normal 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
|
28
tests/rules/test_git_rm_local_modifications.py
Normal file
28
tests/rules/test_git_rm_local_modifications.py
Normal 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
|
28
tests/rules/test_git_rm_staged.py
Normal file
28
tests/rules/test_git_rm_staged.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_rm_staged import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr(target):
|
||||||
|
return ('error: the following file has changes staged in the index:\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
|
18
tests/rules/test_git_stash_pop.py
Normal file
18
tests/rules/test_git_stash_pop.py
Normal 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 ."
|
18
tests/rules/test_git_tag_force.py
Normal file
18
tests/rules/test_git_tag_force.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_tag_force import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def stderr():
|
||||||
|
return '''fatal: tag 'alert' already exists'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_match(stderr):
|
||||||
|
assert match(Command('git tag alert', stderr=stderr))
|
||||||
|
assert not match(Command('git tag alert'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_new_command(stderr):
|
||||||
|
assert get_new_command(Command('git tag alert', stderr=stderr)) \
|
||||||
|
== "git tag --force alert"
|
53
tests/rules/test_ifconfig_device_not_found.py
Normal file
53
tests/rules/test_ifconfig_device_not_found.py
Normal 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
|
12
tests/rules/test_ls_all.py
Normal file
12
tests/rules/test_ls_all.py
Normal 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'
|
@ -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'),
|
||||||
|
46
tests/rules/test_scm_correction.py
Normal file
46
tests/rules/test_scm_correction.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.scm_correction import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def get_actual_scm_mock(mocker):
|
||||||
|
return mocker.patch('thefuck.rules.scm_correction._get_actual_scm',
|
||||||
|
return_value=None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, actual_scm', [
|
||||||
|
('git log', 'fatal: Not a git repository '
|
||||||
|
'(or any of the parent directories): .git',
|
||||||
|
'hg'),
|
||||||
|
('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
|
||||||
|
"(.hg not found)!",
|
||||||
|
'git')])
|
||||||
|
def test_match(get_actual_scm_mock, script, stderr, actual_scm):
|
||||||
|
get_actual_scm_mock.return_value = actual_scm
|
||||||
|
assert match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, actual_scm', [
|
||||||
|
('git log', '', 'hg'),
|
||||||
|
('git log', 'fatal: Not a git repository '
|
||||||
|
'(or any of the parent directories): .git',
|
||||||
|
None),
|
||||||
|
('hg log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
|
||||||
|
"(.hg not found)!",
|
||||||
|
None),
|
||||||
|
('not-scm log', "abort: no repository found in '/home/nvbn/exp/thefuck' "
|
||||||
|
"(.hg not found)!",
|
||||||
|
'git')])
|
||||||
|
def test_not_match(get_actual_scm_mock, script, stderr, actual_scm):
|
||||||
|
get_actual_scm_mock.return_value = actual_scm
|
||||||
|
assert not match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, actual_scm, result', [
|
||||||
|
('git log', 'hg', 'hg log'),
|
||||||
|
('hg log', 'git', 'git log')])
|
||||||
|
def test_get_new_command(get_actual_scm_mock, script, actual_scm, result):
|
||||||
|
get_actual_scm_mock.return_value = actual_scm
|
||||||
|
new_command = get_new_command(Command(script))
|
||||||
|
assert new_command == result
|
39
tests/rules/test_sudo_command_from_user_path.py
Normal file
39
tests/rules/test_sudo_command_from_user_path.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.sudo_command_from_user_path import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
stderr = 'sudo: {}: command not found'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def which(mocker):
|
||||||
|
return mocker.patch('thefuck.rules.sudo_command_from_user_path.which',
|
||||||
|
return_value='/usr/bin/app')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr', [
|
||||||
|
('sudo npm install -g react-native-cli', stderr.format('npm')),
|
||||||
|
('sudo -u app appcfg update .', stderr.format('appcfg'))])
|
||||||
|
def test_match(script, stderr):
|
||||||
|
assert match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, which_result', [
|
||||||
|
('npm --version', stderr.format('npm'), '/usr/bin/npm'),
|
||||||
|
('sudo npm --version', '', '/usr/bin/npm'),
|
||||||
|
('sudo npm --version', stderr.format('npm'), None)])
|
||||||
|
def test_not_match(which, script, stderr, which_result):
|
||||||
|
which.return_value = which_result
|
||||||
|
assert not match(Command(script, stderr=stderr))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, stderr, result', [
|
||||||
|
('sudo npm install -g react-native-cli',
|
||||||
|
stderr.format('npm'),
|
||||||
|
'sudo env "PATH=$PATH" npm install -g react-native-cli'),
|
||||||
|
('sudo -u app appcfg update .',
|
||||||
|
stderr.format('appcfg'),
|
||||||
|
'sudo -u app env "PATH=$PATH" appcfg update .')])
|
||||||
|
def test_get_new_command(script, stderr, result):
|
||||||
|
assert get_new_command(Command(script, stderr=stderr)) == result
|
22
tests/rules/test_yarn_alias.py
Normal file
22
tests/rules/test_yarn_alias.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.yarn_alias import match, get_new_command
|
||||||
|
from tests.utils import Command
|
||||||
|
|
||||||
|
|
||||||
|
stderr_remove = 'error Did you mean `yarn remove`?'
|
||||||
|
|
||||||
|
stderr_list = 'error Did you mean `yarn list`?'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command(script='yarn rm', stderr=stderr_remove),
|
||||||
|
Command(script='yarn ls', stderr=stderr_list)])
|
||||||
|
def test_match(command):
|
||||||
|
assert match(command)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command, new_command', [
|
||||||
|
(Command('yarn rm', stderr=stderr_remove), 'yarn remove'),
|
||||||
|
(Command('yarn ls', stderr=stderr_list), 'yarn list')])
|
||||||
|
def test_get_new_command(command, new_command):
|
||||||
|
assert get_new_command(command) == new_command
|
111
tests/rules/test_yarn_command_not_found.py
Normal file
111
tests/rules/test_yarn_command_not_found.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
import pytest
|
||||||
|
from tests.utils import Command
|
||||||
|
from thefuck.rules.yarn_command_not_found import match, get_new_command
|
||||||
|
|
||||||
|
stderr = '''
|
||||||
|
error Command "{}" not found.
|
||||||
|
'''.format
|
||||||
|
|
||||||
|
yarn_help_stdout = b'''
|
||||||
|
|
||||||
|
Usage: yarn [command] [flags]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-h, --help output usage information
|
||||||
|
-V, --version output the version number
|
||||||
|
--verbose output verbose messages on internal operations
|
||||||
|
--offline trigger an error if any required dependencies are not available in local cache
|
||||||
|
--prefer-offline use network only if dependencies are not available in local cache
|
||||||
|
--strict-semver
|
||||||
|
--json
|
||||||
|
--ignore-scripts don't run lifecycle scripts
|
||||||
|
--har save HAR output of network traffic
|
||||||
|
--ignore-platform ignore platform checks
|
||||||
|
--ignore-engines ignore engines check
|
||||||
|
--ignore-optional ignore optional dependencies
|
||||||
|
--force ignore all caches
|
||||||
|
--no-bin-links don't generate bin links when setting up packages
|
||||||
|
--flat only allow one version of a package
|
||||||
|
--prod, --production [prod]
|
||||||
|
--no-lockfile don't read or generate a lockfile
|
||||||
|
--pure-lockfile don't generate a lockfile
|
||||||
|
--frozen-lockfile don't generate a lockfile and fail if an update is needed
|
||||||
|
--link-duplicates create hardlinks to the repeated modules in node_modules
|
||||||
|
--global-folder <path>
|
||||||
|
--modules-folder <path> rather than installing modules into the node_modules folder relative to the cwd, output them here
|
||||||
|
--cache-folder <path> specify a custom folder to store the yarn cache
|
||||||
|
--mutex <type>[:specifier] use a mutex to ensure only one yarn instance is executing
|
||||||
|
--no-emoji disable emoji in output
|
||||||
|
--proxy <host>
|
||||||
|
--https-proxy <host>
|
||||||
|
--no-progress disable progress bar
|
||||||
|
--network-concurrency <number> maximum number of concurrent network requests
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
- access
|
||||||
|
- add
|
||||||
|
- bin
|
||||||
|
- cache
|
||||||
|
- check
|
||||||
|
- clean
|
||||||
|
- config
|
||||||
|
- generate-lock-entry
|
||||||
|
- global
|
||||||
|
- import
|
||||||
|
- info
|
||||||
|
- init
|
||||||
|
- install
|
||||||
|
- licenses
|
||||||
|
- link
|
||||||
|
- list
|
||||||
|
- login
|
||||||
|
- logout
|
||||||
|
- outdated
|
||||||
|
- owner
|
||||||
|
- pack
|
||||||
|
- publish
|
||||||
|
- remove
|
||||||
|
- run
|
||||||
|
- tag
|
||||||
|
- team
|
||||||
|
- unlink
|
||||||
|
- upgrade
|
||||||
|
- upgrade-interactive
|
||||||
|
- version
|
||||||
|
- versions
|
||||||
|
- why
|
||||||
|
|
||||||
|
Run `yarn help COMMAND` for more information on specific commands.
|
||||||
|
Visit https://yarnpkg.com/en/docs/cli/ to learn more about Yarn.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def yarn_help(mocker):
|
||||||
|
patch = mocker.patch('thefuck.rules.yarn_command_not_found.Popen')
|
||||||
|
patch.return_value.stdout = BytesIO(yarn_help_stdout)
|
||||||
|
return patch
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('yarn whyy webpack', stderr=stderr('whyy'))])
|
||||||
|
def test_match(command):
|
||||||
|
assert match(command)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('npm nuild', stderr=stderr('nuild')),
|
||||||
|
Command('yarn install')])
|
||||||
|
def test_not_match(command):
|
||||||
|
assert not match(command)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command, result', [
|
||||||
|
(Command('yarn whyy webpack', stderr=stderr('whyy')), 'yarn why webpack')])
|
||||||
|
def test_get_new_command(command, result):
|
||||||
|
assert get_new_command(command)[0] == result
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -83,13 +83,14 @@ def how_to_configure_alias(configuration_details):
|
|||||||
print("Seems like {bold}fuck{reset} alias isn't configured!".format(
|
print("Seems like {bold}fuck{reset} alias isn't configured!".format(
|
||||||
bold=color(colorama.Style.BRIGHT),
|
bold=color(colorama.Style.BRIGHT),
|
||||||
reset=color(colorama.Style.RESET_ALL)))
|
reset=color(colorama.Style.RESET_ALL)))
|
||||||
|
|
||||||
if configuration_details:
|
if configuration_details:
|
||||||
content, path = configuration_details
|
|
||||||
print(
|
print(
|
||||||
"Please put {bold}{content}{reset} in your "
|
"Please put {bold}{content}{reset} in your "
|
||||||
"{bold}{path}{reset}.".format(
|
"{bold}{path}{reset} and apply "
|
||||||
|
"changes with {bold}{reload}{reset} or restart your shell.".format(
|
||||||
bold=color(colorama.Style.BRIGHT),
|
bold=color(colorama.Style.BRIGHT),
|
||||||
reset=color(colorama.Style.RESET_ALL),
|
reset=color(colorama.Style.RESET_ALL),
|
||||||
path=path,
|
**configuration_details))
|
||||||
content=content))
|
|
||||||
print('More details - https://github.com/nvbn/thefuck#manual-installation')
|
print('More details - https://github.com/nvbn/thefuck#manual-installation')
|
||||||
|
10
thefuck/rules/ag_literal.py
Normal file
10
thefuck/rules/ag_literal.py
Normal 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)
|
@ -29,7 +29,7 @@ def get_package(executable):
|
|||||||
|
|
||||||
|
|
||||||
def match(command):
|
def match(command):
|
||||||
if 'not found' in command.stderr:
|
if 'not found' in command.stderr or 'not installed' in command.stderr:
|
||||||
executable = _get_executable(command)
|
executable = _get_executable(command)
|
||||||
return not which(executable) and get_package(executable)
|
return not which(executable) and get_package(executable)
|
||||||
else:
|
else:
|
||||||
|
@ -8,7 +8,8 @@ def match(command):
|
|||||||
|
|
||||||
|
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
command.script_parts[1] = 'link'
|
command_parts = command.script_parts[:]
|
||||||
command.script_parts.insert(2, '--overwrite')
|
command_parts[1] = 'link'
|
||||||
command.script_parts.insert(3, '--dry-run')
|
command_parts.insert(2, '--overwrite')
|
||||||
return ' '.join(command.script_parts)
|
command_parts.insert(3, '--dry-run')
|
||||||
|
return ' '.join(command_parts)
|
||||||
|
@ -8,6 +8,7 @@ def match(command):
|
|||||||
|
|
||||||
|
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
command.script_parts[1] = 'uninstall'
|
command_parts = command.script_parts[:]
|
||||||
command.script_parts.insert(2, '--force')
|
command_parts[1] = 'uninstall'
|
||||||
return ' '.join(command.script_parts)
|
command_parts.insert(2, '--force')
|
||||||
|
return ' '.join(command_parts)
|
||||||
|
32
thefuck/rules/gem_unknown_command.py
Normal file
32
thefuck/rules/gem_unknown_command.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from thefuck.utils import for_app, eager, replace_command
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('gem')
|
||||||
|
def match(command):
|
||||||
|
return ('ERROR: While executing gem ... (Gem::CommandLineError)'
|
||||||
|
in command.stderr
|
||||||
|
and 'Unknown command' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_unknown_command(command):
|
||||||
|
return re.findall(r'Unknown command (.*)$', command.stderr)[0]
|
||||||
|
|
||||||
|
|
||||||
|
@eager
|
||||||
|
def _get_all_commands():
|
||||||
|
proc = subprocess.Popen(['gem', 'help', 'commands'],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
for line in proc.stdout.readlines():
|
||||||
|
line = line.decode()
|
||||||
|
|
||||||
|
if line.startswith(' '):
|
||||||
|
yield line.strip().split(' ')[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
unknown_command = _get_unknown_command(command)
|
||||||
|
all_commands = _get_all_commands()
|
||||||
|
return replace_command(command, unknown_command, all_commands)
|
13
thefuck/rules/git_add_force.py
Normal file
13
thefuck/rules/git_add_force.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from thefuck.utils import replace_argument
|
||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('add' in command.script_parts
|
||||||
|
and 'Use -f if you really want to add them.' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return replace_argument(command.script, 'add', 'add --force')
|
30
thefuck/rules/git_flag_after_filename.py
Normal file
30
thefuck/rules/git_flag_after_filename.py
Normal 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)
|
@ -8,23 +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):
|
||||||
# If --set-upstream or -u are passed, remove it and its argument. This is
|
# If --set-upstream or -u are passed, remove it and its argument. This is
|
||||||
# because the remaining arguments are concatenated onto the command suggested
|
# because the remaining arguments are concatenated onto the command suggested
|
||||||
# by git, which includes --set-upstream and its argument
|
# by git, which includes --set-upstream and its argument
|
||||||
upstream_option_index = -1
|
command_parts = command.script_parts[:]
|
||||||
try:
|
upstream_option_index = _get_upstream_option_index(command_parts)
|
||||||
upstream_option_index = command.script_parts.index('--set-upstream')
|
|
||||||
except ValueError:
|
if upstream_option_index is not None:
|
||||||
pass
|
command_parts.pop(upstream_option_index)
|
||||||
try:
|
|
||||||
upstream_option_index = command.script_parts.index('-u')
|
# In case of `git push -u` we don't have next argument:
|
||||||
except ValueError:
|
if len(command_parts) > upstream_option_index:
|
||||||
pass
|
command_parts.pop(upstream_option_index)
|
||||||
if upstream_option_index is not -1:
|
|
||||||
command.script_parts.pop(upstream_option_index)
|
|
||||||
command.script_parts.pop(upstream_option_index)
|
|
||||||
|
|
||||||
push_upstream = command.stderr.split('\n')[-3].strip().partition('git ')[2]
|
push_upstream = command.stderr.split('\n')[-3].strip().partition('git ')[2]
|
||||||
return replace_argument(" ".join(command.script_parts), 'push', push_upstream)
|
return replace_argument(" ".join(command_parts), 'push', push_upstream)
|
||||||
|
17
thefuck/rules/git_rebase_merge_dir.py
Normal file
17
thefuck/rules/git_rebase_merge_dir.py
Normal 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)
|
19
thefuck/rules/git_rm_local_modifications.py
Normal file
19
thefuck/rules/git_rm_local_modifications.py
Normal 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
|
@ -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)
|
||||||
|
19
thefuck/rules/git_rm_staged.py
Normal file
19
thefuck/rules/git_rm_staged.py
Normal 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 changes staged in the index' 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
|
18
thefuck/rules/git_stash_pop.py
Normal file
18
thefuck/rules/git_stash_pop.py
Normal 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
|
13
thefuck/rules/git_tag_force.py
Normal file
13
thefuck/rules/git_tag_force.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from thefuck.utils import replace_argument
|
||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('tag' in command.script_parts
|
||||||
|
and 'already exists' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return replace_argument(command.script, 'tag', 'tag --force')
|
25
thefuck/rules/ifconfig_device_not_found.py
Normal file
25
thefuck/rules/ifconfig_device_not_found.py
Normal 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
10
thefuck/rules/ls_all.py
Normal 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:])
|
@ -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,
|
||||||
|
]
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
32
thefuck/rules/scm_correction.py
Normal file
32
thefuck/rules/scm_correction.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from thefuck.utils import for_app, memoize
|
||||||
|
from thefuck.system import Path
|
||||||
|
|
||||||
|
path_to_scm = {
|
||||||
|
'.git': 'git',
|
||||||
|
'.hg': 'hg',
|
||||||
|
}
|
||||||
|
|
||||||
|
wrong_scm_patterns = {
|
||||||
|
'git': 'fatal: Not a git repository',
|
||||||
|
'hg': 'abort: no repository found',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@memoize
|
||||||
|
def _get_actual_scm():
|
||||||
|
for path, scm in path_to_scm.items():
|
||||||
|
if Path(path).is_dir():
|
||||||
|
return scm
|
||||||
|
|
||||||
|
|
||||||
|
@for_app(*wrong_scm_patterns.keys())
|
||||||
|
def match(command):
|
||||||
|
scm = command.script_parts[0]
|
||||||
|
pattern = wrong_scm_patterns[scm]
|
||||||
|
|
||||||
|
return pattern in command.stderr and _get_actual_scm()
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
scm = _get_actual_scm()
|
||||||
|
return u' '.join([scm] + command.script_parts[1:])
|
21
thefuck/rules/sudo_command_from_user_path.py
Normal file
21
thefuck/rules/sudo_command_from_user_path.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import re
|
||||||
|
from thefuck.utils import for_app, which, replace_argument
|
||||||
|
|
||||||
|
|
||||||
|
def _get_command_name(command):
|
||||||
|
found = re.findall(r'sudo: (.*): command not found', command.stderr)
|
||||||
|
if found:
|
||||||
|
return found[0]
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('sudo')
|
||||||
|
def match(command):
|
||||||
|
if 'command not found' in command.stderr:
|
||||||
|
command_name = _get_command_name(command)
|
||||||
|
return which(command_name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
command_name = _get_command_name(command)
|
||||||
|
return replace_argument(command.script, command_name,
|
||||||
|
u'env "PATH=$PATH" {}'.format(command_name))
|
@ -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)
|
||||||
|
14
thefuck/rules/yarn_alias.py
Normal file
14
thefuck/rules/yarn_alias.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import re
|
||||||
|
from thefuck.utils import replace_argument, for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('yarn', at_least=1)
|
||||||
|
def match(command):
|
||||||
|
return ('Did you mean' in command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
broken = command.script_parts[1]
|
||||||
|
fix = re.findall(r'Did you mean `yarn ([^`]*)`', command.stderr)[0]
|
||||||
|
|
||||||
|
return replace_argument(command.script, broken, fix)
|
31
thefuck/rules/yarn_command_not_found.py
Normal file
31
thefuck/rules/yarn_command_not_found.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import re
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from thefuck.utils import for_app, eager, replace_command
|
||||||
|
|
||||||
|
regex = re.compile(r'error Command "(.*)" not found.')
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('yarn')
|
||||||
|
def match(command):
|
||||||
|
return regex.findall(command.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
@eager
|
||||||
|
def _get_all_tasks():
|
||||||
|
proc = Popen(['yarn', '--help'], stdout=PIPE)
|
||||||
|
should_yield = False
|
||||||
|
for line in proc.stdout.readlines():
|
||||||
|
line = line.decode().strip()
|
||||||
|
|
||||||
|
if 'Commands:' in line:
|
||||||
|
should_yield = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if should_yield and '- ' in line:
|
||||||
|
yield line.split(' ')[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
misspelled_task = regex.findall(command.stderr)[0]
|
||||||
|
tasks = _get_all_tasks()
|
||||||
|
return replace_command(command, misspelled_task, tasks)
|
@ -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 + "'"
|
||||||
|
|
||||||
@ -44,4 +44,9 @@ class Bash(Generic):
|
|||||||
config = '~/.bashrc'
|
config = '~/.bashrc'
|
||||||
else:
|
else:
|
||||||
config = 'bash config'
|
config = 'bash config'
|
||||||
return 'eval $(thefuck --alias)', config
|
|
||||||
|
return {
|
||||||
|
'content': 'eval $(thefuck --alias)',
|
||||||
|
'path': config,
|
||||||
|
'reload': u'source {}'.format(config),
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
@ -66,8 +67,11 @@ class Fish(Generic):
|
|||||||
return u'; and '.join(commands)
|
return u'; and '.join(commands)
|
||||||
|
|
||||||
def how_to_configure(self):
|
def how_to_configure(self):
|
||||||
return (r"eval (thefuck --alias | tr '\n' ';')",
|
return {
|
||||||
'~/.config/fish/config.fish')
|
'content': r"eval (thefuck --alias | tr '\n' ';')",
|
||||||
|
'path': '~/.config/fish/config.fish',
|
||||||
|
'reload': 'fish',
|
||||||
|
}
|
||||||
|
|
||||||
def put_to_history(self, command):
|
def put_to_history(self, command):
|
||||||
try:
|
try:
|
||||||
|
@ -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."""
|
||||||
|
@ -3,11 +3,14 @@ from .generic import Generic
|
|||||||
|
|
||||||
class Powershell(Generic):
|
class Powershell(Generic):
|
||||||
def app_alias(self, fuck):
|
def app_alias(self, fuck):
|
||||||
return 'function ' + fuck + ' { \n' \
|
return 'function ' + fuck + ' {\n' \
|
||||||
' $fuck = $(thefuck (Get-History -Count 1).CommandLine);\n' \
|
' $history = (Get-History -Count 1).CommandLine;\n' \
|
||||||
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
|
' if (-not [string]::IsNullOrWhiteSpace($history)) {\n' \
|
||||||
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }\n' \
|
' $fuck = $(thefuck $history);\n' \
|
||||||
' else { iex "$fuck"; }\n' \
|
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
|
||||||
|
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }\n' \
|
||||||
|
' else { iex "$fuck"; }\n' \
|
||||||
|
' }\n' \
|
||||||
' }\n' \
|
' }\n' \
|
||||||
'}\n'
|
'}\n'
|
||||||
|
|
||||||
@ -15,4 +18,8 @@ class Powershell(Generic):
|
|||||||
return u' -and '.join('({0})'.format(c) for c in commands)
|
return u' -and '.join('({0})'.format(c) for c in commands)
|
||||||
|
|
||||||
def how_to_configure(self):
|
def how_to_configure(self):
|
||||||
return 'iex "thefuck --alias"', '$profile'
|
return {
|
||||||
|
'content': 'iex "thefuck --alias"',
|
||||||
|
'path': '$profile',
|
||||||
|
'reload': '& $profile',
|
||||||
|
}
|
||||||
|
@ -31,4 +31,8 @@ class Tcsh(Generic):
|
|||||||
return u'#+{}\n{}\n'.format(int(time()), command_script)
|
return u'#+{}\n{}\n'.format(int(time()), command_script)
|
||||||
|
|
||||||
def how_to_configure(self):
|
def how_to_configure(self):
|
||||||
return 'eval `thefuck --alias`', '~/.tcshrc'
|
return {
|
||||||
|
'content': 'eval `thefuck --alias`',
|
||||||
|
'path': '~/.tcshrc',
|
||||||
|
'reload': 'tcsh',
|
||||||
|
}
|
||||||
|
@ -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 + "'"
|
||||||
|
|
||||||
@ -45,4 +45,8 @@ class Zsh(Generic):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
def how_to_configure(self):
|
def how_to_configure(self):
|
||||||
return 'eval $(thefuck --alias)', '~/.zshrc'
|
return {
|
||||||
|
'content': 'eval $(thefuck --alias)',
|
||||||
|
'path': '~/.zshrc',
|
||||||
|
'reload': 'source ~/.zshrc',
|
||||||
|
}
|
||||||
|
@ -34,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):
|
||||||
|
@ -12,9 +12,10 @@ def read_actions():
|
|||||||
while True:
|
while True:
|
||||||
key = get_key()
|
key = get_key()
|
||||||
|
|
||||||
if key in (const.KEY_UP, 'k'):
|
# Handle arrows, j/k (qwerty), and n/e (colemak)
|
||||||
|
if key in (const.KEY_UP, 'k', 'e'):
|
||||||
yield const.ACTION_PREVIOUS
|
yield const.ACTION_PREVIOUS
|
||||||
elif key in (const.KEY_DOWN, 'j'):
|
elif key in (const.KEY_DOWN, 'j', 'n'):
|
||||||
yield const.ACTION_NEXT
|
yield const.ACTION_NEXT
|
||||||
elif key in (const.KEY_CTRL_C, 'q'):
|
elif key in (const.KEY_CTRL_C, 'q'):
|
||||||
yield const.ACTION_ABORT
|
yield const.ACTION_ABORT
|
||||||
|
@ -159,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
|
||||||
@ -264,7 +264,7 @@ def get_valid_history_without_current(command):
|
|||||||
from thefuck.shells import shell
|
from thefuck.shells import shell
|
||||||
history = shell.get_history()
|
history = shell.get_history()
|
||||||
tf_alias = get_alias()
|
tf_alias = get_alias()
|
||||||
executables = get_all_executables()
|
executables = set(get_all_executables())
|
||||||
return [line for line in _not_corrected(history, tf_alias)
|
return [line for line in _not_corrected(history, tf_alias)
|
||||||
if not line.startswith(tf_alias) and not line == command.script
|
if not line.startswith(tf_alias) and not line == command.script
|
||||||
and line.split(' ')[0] in executables]
|
and line.split(' ')[0] in executables]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user