1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-02-21 04:18:55 +00:00

Merge branch 'master' into flake8

This commit is contained in:
Joseph Frazier 2017-03-08 12:21:54 -05:00
commit b038ea4541
63 changed files with 1060 additions and 113 deletions

View File

@ -2,6 +2,9 @@ language: python
sudo: false
matrix:
include:
- os: linux
dist: trusty
python: "3.6"
- os: linux
dist: trusty
python: "3.5"
@ -29,7 +32,7 @@ addons:
- python3-commandnotfound
before_install:
- 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 source venv/bin/activate; fi
- pip install -U pip
@ -42,7 +45,7 @@ script:
- flake8
- 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"
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 && $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 --enable-functional; fi
- if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
after_success:
- coveralls
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi

View File

@ -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
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_no_command` – fixes wrongs commands like `cargo buid`;
* `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;
* `fix_alt_space` – replaces Alt+Space with Space character;
* `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_force` &ndash; adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
* `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_exists` &ndash; 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` &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_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_not_command` &ndash; fixes wrong git commands like `git brnch`;
* `git_pull` &ndash; 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` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
* `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_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_staged` &ndash; adds `-f` or `--cached` when you try to `rm` a file with staged changes
* `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_stash` &ndash; stashes you local modifications before rebasing or switching branch;
* `git_stash` &ndash; stashes your local modifications before rebasing or switching branch;
* `git_stash_pop` &ndash; adds your local modifications before popping stash, then resets;
* `git_tag_force` &ndash; adds `--force` to `git tag <tagname>` when the tag already exists;
* `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;
* `gradle_no_task` &ndash; 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` &ndash; prepends `./` when script/binary exists;
* `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `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;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `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_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`;
* `man` &ndash; changes manual section;
* `man_no_space` &ndash; 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` &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;
* `scm_correction` &ndash; corrects wrong scm like `hg log` to `git log`;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `sudo_command_from_user_path` &ndash; runs commands from users `$PATH` with `sudo`;
* `switch_lang` &ndash; switches command from your local layout to en;
* `systemctl` &ndash; correctly orders parameters of confusing `systemctl`;
* `test.py` &ndash; runs `py.test` instead of `test.py`;
@ -235,6 +248,8 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `vagrant_up` &ndash; starts up the vagrant instance;
* `whois` &ndash; fixes `whois` command;
* `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new.
* `yarn_alias` &ndash; fixes aliased `yarn` commands like `yarn ls`;
* `yarn_command_not_found` &ndash; fixes misspelled `yarn` commands;
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.
`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`.
`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:/Python34"
- PYTHON: "C:/Python35"
- PYTHON: "C:/Python36"
init:
- "ECHO %PYTHON%"

View File

@ -29,7 +29,7 @@ elif (3, 0) < version < (3, 3):
' ({}.{} detected).'.format(*version))
sys.exit(-1)
VERSION = '3.11'
VERSION = '3.14'
install_requires = ['psutil', 'colorama', 'six', 'decorator']
extras_require = {':python_version<"3.4"': ['pathlib2'],

View File

@ -3,18 +3,11 @@ from tests.functional.plots import with_confirmation, without_confirmation, \
refuse_with_confirmation, history_changed, history_not_changed, \
select_command_with_arrows, how_to_configure
containers = ((u'thefuck/ubuntu-python3-bash',
u'''FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip''',
containers = ((u'thefuck/python3-bash',
u'FROM python:3',
u'bash'),
(u'thefuck/ubuntu-python2-bash',
u'''FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python python-pip python-dev git
RUN pip2 install -U pip setuptools''',
(u'thefuck/python2-bash',
u'FROM python:2',
u'bash'))

View File

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

View File

@ -2,11 +2,7 @@ import pytest
import time
dockerfile = u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -yy python3 python3-pip python3-dev git
RUN pip3 install -U setuptools
RUN ln -s /usr/bin/pip3 /usr/bin/pip
FROM python:3
RUN adduser --disabled-password --gecos '' test
ENV SEED "{seed}"
WORKDIR /src
@ -42,7 +38,7 @@ def plot(proc, TIMEOUT):
@pytest.mark.functional
@pytest.mark.benchmark(min_rounds=10)
def test_performance(spawnu, TIMEOUT, benchmark):
proc = spawnu(u'thefuck/ubuntu-python3-bash-performance',
proc = spawnu(u'thefuck/python3-bash-performance',
dockerfile, u'bash')
proc.sendline(u'pip install /src')
proc.sendline(u'su test')

View File

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

View File

@ -7,6 +7,8 @@ from tests.utils import Command
(Command(script='vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command(script='sudo vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]),
(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')])])
def test_match(mocker, command, packages):
mocker.patch('thefuck.rules.apt_get.which', return_value=None)

View 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

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

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

@ -23,6 +23,8 @@ def test_match(stderr):
def test_get_new_command(stderr):
assert get_new_command(Command('git push', stderr=stderr))\
== "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))\

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

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

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', [
(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 3 read'), 'man 2 read'),
(Command('man -s2 read'), 'man -s3 read'),

View 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

View 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

View 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

View 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

View File

@ -56,3 +56,8 @@ class TestBash(object):
def test_get_history(self, history_lines, shell):
history_lines(['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):
settings.alter_history = True
assert 'history --delete' in shell.app_alias('FUCK')
assert 'history --merge' in shell.app_alias('FUCK')
assert 'builtin history delete' in shell.app_alias('FUCK')
assert 'builtin history merge' in shell.app_alias('FUCK')
settings.alter_history = False
assert 'history --delete' not in shell.app_alias('FUCK')
assert 'history --merge' not in shell.app_alias('FUCK')
assert 'builtin history delete' not in shell.app_alias('FUCK')
assert 'builtin history merge' not in shell.app_alias('FUCK')
def test_get_history(self, history_lines, shell):
history_lines(['- cmd: ls', ' when: 1432613911',

View File

@ -83,13 +83,14 @@ def how_to_configure_alias(configuration_details):
print("Seems like {bold}fuck{reset} alias isn't configured!".format(
bold=color(colorama.Style.BRIGHT),
reset=color(colorama.Style.RESET_ALL)))
if configuration_details:
content, path = configuration_details
print(
"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),
reset=color(colorama.Style.RESET_ALL),
path=path,
content=content))
**configuration_details))
print('More details - https://github.com/nvbn/thefuck#manual-installation')

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)

View File

@ -29,7 +29,7 @@ def get_package(executable):
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)
return not which(executable) and get_package(executable)
else:

View File

@ -8,7 +8,8 @@ def match(command):
def get_new_command(command):
command.script_parts[1] = 'link'
command.script_parts.insert(2, '--overwrite')
command.script_parts.insert(3, '--dry-run')
return ' '.join(command.script_parts)
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

@ -8,6 +8,7 @@ def match(command):
def get_new_command(command):
command.script_parts[1] = 'uninstall'
command.script_parts.insert(2, '--force')
return ' '.join(command.script_parts)
command_parts = command.script_parts[:]
command_parts[1] = 'uninstall'
command_parts.insert(2, '--force')
return ' '.join(command_parts)

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

View 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')

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

@ -8,23 +8,29 @@ def match(command):
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
def get_new_command(command):
# 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
upstream_option_index = -1
try:
upstream_option_index = command.script_parts.index('--set-upstream')
except ValueError:
pass
try:
upstream_option_index = command.script_parts.index('-u')
except ValueError:
pass
if upstream_option_index is not -1:
command.script_parts.pop(upstream_option_index)
command.script_parts.pop(upstream_option_index)
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.script_parts), 'push', push_upstream)
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
def get_new_command(command):
index = command.script_parts.index('rm') + 1
command.script_parts.insert(index, '-r')
return u' '.join(command.script_parts)
command_parts = command.script_parts[:]
index = command_parts.index('rm') + 1
command_parts.insert(index, '-r')
return u' '.join(command_parts)

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

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,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')

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:
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_cmd3 = split_cmd2[:]
split_cmd2.insert(1, ' 2 ')
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
def match(command):
toks = command.script_parts
return (toks
and toks[0].endswith('.py')
return (command.script_parts
and command.script_parts[0].endswith('.py')
and ('Permission denied' in command.stderr or
'command not found' in command.stderr))

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

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

View File

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

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

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

View File

@ -14,7 +14,7 @@ class Bash(Generic):
" eval $TF_CMD".format(fuck)
if settings.alter_history:
return alias + " && history -s $TF_CMD'"
return alias + "; history -s $TF_CMD'"
else:
return alias + "'"
@ -44,4 +44,9 @@ class Bash(Generic):
config = '~/.bashrc'
else:
config = 'bash config'
return 'eval $(thefuck --alias)', config
return {
'content': 'eval $(thefuck --alias)',
'path': config,
'reload': u'source {}'.format(config),
}

View File

@ -20,8 +20,9 @@ class Fish(Generic):
def app_alias(self, fuck):
if settings.alter_history:
alter_history = (' history --delete $fucked_up_command\n'
' history --merge ^ /dev/null\n')
alter_history = (' builtin history delete --exact'
' --case-sensitive -- $fucked_up_command\n'
' builtin history merge ^ /dev/null\n')
else:
alter_history = ''
# It is VERY important to have the variables declared WITHIN the alias
@ -66,8 +67,11 @@ class Fish(Generic):
return u'; and '.join(commands)
def how_to_configure(self):
return (r"eval (thefuck --alias | tr '\n' ';')",
'~/.config/fish/config.fish')
return {
'content': r"eval (thefuck --alias | tr '\n' ';')",
'path': '~/.config/fish/config.fish',
'reload': 'fish',
}
def put_to_history(self, command):
try:

View File

@ -65,9 +65,19 @@ class Generic(object):
def split_command(self, command):
"""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:
return [s.decode('utf8') for s in shlex.split(command.encode('utf8'))]
return shlex.split(command)
return command.encode('utf8')
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):
"""Return a shell-escaped version of the string s."""

View File

@ -3,11 +3,14 @@ from .generic import Generic
class Powershell(Generic):
def app_alias(self, fuck):
return 'function ' + fuck + ' { \n' \
' $fuck = $(thefuck (Get-History -Count 1).CommandLine);\n' \
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }\n' \
' else { iex "$fuck"; }\n' \
return 'function ' + fuck + ' {\n' \
' $history = (Get-History -Count 1).CommandLine;\n' \
' if (-not [string]::IsNullOrWhiteSpace($history)) {\n' \
' $fuck = $(thefuck $history);\n' \
' if (-not [string]::IsNullOrWhiteSpace($fuck)) {\n' \
' if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }\n' \
' else { iex "$fuck"; }\n' \
' }\n' \
' }\n' \
'}\n'
@ -15,4 +18,8 @@ class Powershell(Generic):
return u' -and '.join('({0})'.format(c) for c in commands)
def how_to_configure(self):
return 'iex "thefuck --alias"', '$profile'
return {
'content': 'iex "thefuck --alias"',
'path': '$profile',
'reload': '& $profile',
}

View File

@ -31,4 +31,8 @@ class Tcsh(Generic):
return u'#+{}\n{}\n'.format(int(time()), command_script)
def how_to_configure(self):
return 'eval `thefuck --alias`', '~/.tcshrc'
return {
'content': 'eval `thefuck --alias`',
'path': '~/.tcshrc',
'reload': 'tcsh',
}

View File

@ -15,7 +15,7 @@ class Zsh(Generic):
" eval $TF_CMD".format(alias_name)
if settings.alter_history:
return alias + " && print -s $TF_CMD'"
return alias + " ; test -n \"$TF_CMD\" && print -s $TF_CMD'"
else:
return alias + "'"
@ -45,4 +45,8 @@ class Zsh(Generic):
return ''
def how_to_configure(self):
return 'eval $(thefuck --alias)', '~/.zshrc'
return {
'content': 'eval $(thefuck --alias)',
'path': '~/.zshrc',
'reload': 'source ~/.zshrc',
}

View File

@ -34,7 +34,8 @@ class Command(object):
except Exception:
logs.debug(u"Can't split command script {} because:\n {}".format(
self, sys.exc_info()))
self._script_parts = None
self._script_parts = []
return self._script_parts
def __eq__(self, other):

View File

@ -12,9 +12,10 @@ def read_actions():
while True:
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
elif key in (const.KEY_DOWN, 'j'):
elif key in (const.KEY_DOWN, 'j', 'n'):
yield const.ACTION_NEXT
elif key in (const.KEY_CTRL_C, 'q'):
yield const.ACTION_ABORT

View File

@ -159,7 +159,7 @@ def is_app(command, *app_names, **kwargs):
if kwargs:
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 False
@ -264,7 +264,7 @@ def get_valid_history_without_current(command):
from thefuck.shells import shell
history = shell.get_history()
tf_alias = get_alias()
executables = get_all_executables()
executables = set(get_all_executables())
return [line for line in _not_corrected(history, tf_alias)
if not line.startswith(tf_alias) and not line == command.script
and line.split(' ')[0] in executables]

View File

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