mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-01 15:42:06 +00:00
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bbba9a0c8 | ||
|
|
b978c3793e | ||
|
|
8a83b30e73 | ||
|
|
fd20a3f832 | ||
|
|
b6ed499103 | ||
|
|
76600cf40a | ||
|
|
e62666181a | ||
|
|
c88b0792b8 | ||
|
|
06a89427e2 | ||
|
|
3a134f250d | ||
|
|
b54cdf7c49 | ||
|
|
1b05a497e8 | ||
|
|
79602383ec | ||
|
|
84c42168df | ||
|
|
f53d772ac3 | ||
|
|
93d4a4fc3a | ||
|
|
2cb23b1805 | ||
|
|
33f28cf76d | ||
|
|
6322dbd9ed | ||
|
|
fc09818351 | ||
|
|
2788ef1471 | ||
|
|
ef3aabe7c5 | ||
|
|
2af54d036d | ||
|
|
99c10b50ff | ||
|
|
802fcd96fd | ||
|
|
900e83e028 | ||
|
|
d41cbb6810 | ||
|
|
b36cf59b46 | ||
|
|
cfa831c88d | ||
|
|
818d06fb95 | ||
|
|
c3eca8234a | ||
|
|
d47ff8cbf2 | ||
|
|
1a52e98fbd | ||
|
|
53c11d2ef4 | ||
|
|
beda1854cf | ||
|
|
7532c65c62 | ||
|
|
ec37998a10 | ||
|
|
58d5eff6d0 | ||
|
|
d28567bb31 | ||
|
|
b016bb2255 | ||
|
|
bf109ee548 | ||
|
|
1aaaca1220 | ||
|
|
b096560469 | ||
|
|
5b1f3ff816 | ||
|
|
c5f7c89222 | ||
|
|
e61271dae3 | ||
|
|
bddb43b987 | ||
|
|
b22a3ac891 | ||
|
|
f4cc88f6c7 | ||
|
|
5734412d82 | ||
|
|
24588b23e2 | ||
|
|
825f7986c7 | ||
|
|
971c7e1b3f | ||
|
|
8375b78877 | ||
|
|
6f39edc155 | ||
|
|
408cb5fa09 | ||
|
|
2315929875 | ||
|
|
14a9cd85aa | ||
|
|
2379573cf2 | ||
|
|
350be285b8 | ||
|
|
76aa5546df | ||
|
|
5179015b84 | ||
|
|
dee99ed705 | ||
|
|
e101f1fcc9 | ||
|
|
73b884df5f | ||
|
|
9e8b4f594d | ||
|
|
c2b597f22b | ||
|
|
8c783b7405 | ||
|
|
3efa42ec06 | ||
|
|
02bcd8331d | ||
|
|
bd750ff9a3 | ||
|
|
725ef271b1 | ||
|
|
d2f8cebfd8 | ||
|
|
c7d7a6d1d7 | ||
|
|
4b53b1d3e3 | ||
|
|
35ea4dce71 | ||
|
|
2fea1f3846 | ||
|
|
e009f0a05b | ||
|
|
78515c7bbb | ||
|
|
62a845fd94 | ||
|
|
2c7ce91dd5 | ||
|
|
c775937d17 | ||
|
|
aaf01394db | ||
|
|
0b0a2220a0 | ||
|
|
b038ea4541 | ||
|
|
7d3ddfc8d9 | ||
|
|
02f3250d39 | ||
|
|
df5428c5e4 | ||
|
|
ef5ff6210a | ||
|
|
fbb73f262b | ||
|
|
20246e5be6 | ||
|
|
4669a033ee | ||
|
|
7e16a2eb7c | ||
|
|
42ec01dab1 | ||
|
|
91c27e1a62 | ||
|
|
778f2bdf1a | ||
|
|
e893521872 | ||
|
|
bbed17fe07 | ||
|
|
309fe8f6ee | ||
|
|
8a8ade1e6b | ||
|
|
7f9025c7ad | ||
|
|
6f842ab747 | ||
|
|
ae68bcbac1 | ||
|
|
fb07cdfb4a | ||
|
|
55dcf06569 | ||
|
|
28a0150e45 | ||
|
|
430a7135af | ||
|
|
ff2be6c9a3 | ||
|
|
4748776296 | ||
|
|
1885196a11 | ||
|
|
c127040a4c | ||
|
|
ac7b633e28 | ||
|
|
4d0388b53c | ||
|
|
8da4dce5f2 | ||
|
|
ace6e88269 | ||
|
|
a015c0f5e2 | ||
|
|
dbe324bcd8 | ||
|
|
8447b5caa2 | ||
|
|
3a9942061d | ||
|
|
b4fda04acb | ||
|
|
5f6c55d839 | ||
|
|
dda9d55989 | ||
|
|
f0b9c7cb67 | ||
|
|
521eb03d7a | ||
|
|
e0cab4fa1b | ||
|
|
1b30c00546 | ||
|
|
a9d55e9c62 | ||
|
|
432878bd77 | ||
|
|
fb3d8d1e01 | ||
|
|
c4c6f506f4 | ||
|
|
725605cd20 | ||
|
|
797b42cfd7 | ||
|
|
37161832aa | ||
|
|
b221b04d0f | ||
|
|
dcc13bd2d2 | ||
|
|
283eb09c19 | ||
|
|
10d409e6e2 | ||
|
|
93302c74b5 | ||
|
|
22b005cebb | ||
|
|
e9d29726bc |
@@ -42,9 +42,10 @@ install:
|
||||
- python setup.py develop
|
||||
- rm -rf build
|
||||
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.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
|
||||
|
||||
41
README.md
41
README.md
@@ -106,13 +106,13 @@ On Ubuntu you can install `The Fuck` with:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install python3-dev python3-pip
|
||||
sudo -H pip3 install thefuck
|
||||
sudo pip3 install thefuck
|
||||
```
|
||||
|
||||
On other systems you can install `The Fuck` with `pip`:
|
||||
|
||||
```bash
|
||||
sudo -H pip install thefuck
|
||||
pip install thefuck
|
||||
```
|
||||
|
||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
||||
@@ -120,9 +120,9 @@ sudo -H pip install thefuck
|
||||
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
|
||||
|
||||
```bash
|
||||
eval "$(thefuck --alias)"
|
||||
eval $(thefuck --alias)
|
||||
# You can use whatever you want as an alias, like for Mondays:
|
||||
eval "$(thefuck --alias FUCK)"
|
||||
eval $(thefuck --alias FUCK)
|
||||
```
|
||||
|
||||
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
||||
@@ -130,11 +130,16 @@ eval "$(thefuck --alias FUCK)"
|
||||
Changes will be available only in a new shell session.
|
||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
|
||||
|
||||
If you want separate alias for running fixed command without confirmation you can use alias like:
|
||||
|
||||
```bash
|
||||
alias fuck-it='export THEFUCK_REQUIRE_CONFIRMATION=False; fuck; export THEFUCK_REQUIRE_CONFIRMATION=True'
|
||||
```
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
sudo -H pip install thefuck --upgrade
|
||||
pip install thefuck --upgrade
|
||||
```
|
||||
|
||||
**Aliases changed in 1.34.**
|
||||
@@ -164,7 +169,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` – 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_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;
|
||||
@@ -181,13 +188,16 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_pull_uncommitted_changes` – stashes changes before pulling and pops them afterwards;
|
||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||
* `git_push_without_commits` – Creates an initial commit if you forget and only `git add .`, when setting up a new project;
|
||||
* `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_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_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`;
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
||||
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
||||
@@ -199,6 +209,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
||||
* `history` – tries to replace command with most similar command from history;
|
||||
* `hostscli` – tries to fix `hostscli` usage;
|
||||
* `ifconfig_device_not_found` – fixes wrong device names like `wlan0` to `wlp2s0`;
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
@@ -210,6 +221,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `man` – changes manual section;
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
||||
@@ -224,13 +236,16 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `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;
|
||||
* `sl_ls` – changes `sl` to `ls`;
|
||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
||||
* `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;
|
||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||
* `test.py` – runs `py.test` instead of `test.py`;
|
||||
@@ -242,6 +257,10 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `vagrant_up` – starts up the vagrant instance;
|
||||
* `whois` – fixes `whois` command;
|
||||
* `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;
|
||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
||||
* `yarn_help` – makes it easier to open `yarn` documentation;
|
||||
|
||||
Enabled by default only on specific platforms:
|
||||
|
||||
@@ -379,6 +398,12 @@ pip install -r requirements.txt
|
||||
python setup.py develop
|
||||
```
|
||||
|
||||
Run code style checks:
|
||||
|
||||
```bash
|
||||
flake8
|
||||
```
|
||||
|
||||
Run unit tests:
|
||||
|
||||
```bash
|
||||
@@ -404,9 +429,9 @@ Project License can be found [here](LICENSE.md).
|
||||
|
||||
[version-badge]: https://img.shields.io/pypi/v/thefuck.svg?label=version
|
||||
[version-link]: https://pypi.python.org/pypi/thefuck/
|
||||
[travis-badge]: https://img.shields.io/travis/nvbn/thefuck.svg
|
||||
[travis-badge]: https://travis-ci.org/nvbn/thefuck.svg?branch=master
|
||||
[travis-link]: https://travis-ci.org/nvbn/thefuck
|
||||
[appveyor-badge]: https://img.shields.io/appveyor/ci/nvbn/thefuck.svg?label=windows%20build
|
||||
[appveyor-badge]: https://ci.appveyor.com/api/projects/status/1sskj4imj02um0gu/branch/master?svg=true
|
||||
[appveyor-link]: https://ci.appveyor.com/project/nvbn/thefuck
|
||||
[coverage-badge]: https://img.shields.io/coveralls/nvbn/thefuck.svg
|
||||
[coverage-link]: https://coveralls.io/github/nvbn/thefuck
|
||||
|
||||
@@ -20,4 +20,5 @@ install:
|
||||
- "%PYTHON%/Scripts/pip.exe install -U -r requirements.txt"
|
||||
|
||||
test_script:
|
||||
- "%PYTHON%/python.exe -m flake8"
|
||||
- "%PYTHON%/Scripts/py.test.exe -sv"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pip
|
||||
flake8
|
||||
pytest
|
||||
mock
|
||||
pytest-mock
|
||||
|
||||
4
setup.py
4
setup.py
@@ -29,7 +29,7 @@ elif (3, 0) < version < (3, 3):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.13'
|
||||
VERSION = '3.19'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
@@ -51,4 +51,4 @@ setup(name='thefuck',
|
||||
extras_require=extras_require,
|
||||
entry_points={'console_scripts': [
|
||||
'thefuck = thefuck.main:main',
|
||||
'fuck = thefuck.main:how_to_configure_alias']})
|
||||
'fuck = thefuck.not_configured:main']})
|
||||
|
||||
@@ -7,7 +7,7 @@ shells.shell = shells.Generic()
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""Adds `--run-without-docker` argument."""
|
||||
"""Adds `--enable-functional` argument."""
|
||||
group = parser.getgroup("thefuck")
|
||||
group.addoption('--enable-functional', action="store_true", default=False,
|
||||
help="Enable functional tests")
|
||||
|
||||
@@ -81,6 +81,5 @@ def without_confirmation(proc, TIMEOUT):
|
||||
|
||||
|
||||
def how_to_configure(proc, TIMEOUT):
|
||||
proc.sendline(u'unalias fuck')
|
||||
proc.sendline(u'fuck')
|
||||
assert proc.expect([TIMEOUT, u"alias isn't configured"])
|
||||
|
||||
@@ -48,4 +48,5 @@ def test_without_confirmation(proc, TIMEOUT):
|
||||
|
||||
@pytest.mark.functional
|
||||
def test_how_to_configure_alias(proc, TIMEOUT):
|
||||
proc.sendline('unset -f fuck')
|
||||
how_to_configure(proc, TIMEOUT)
|
||||
|
||||
@@ -55,4 +55,5 @@ def test_without_confirmation(proc, TIMEOUT):
|
||||
|
||||
@pytest.mark.functional
|
||||
def test_how_to_configure_alias(proc, TIMEOUT):
|
||||
proc.sendline(u'unfunction fuck')
|
||||
how_to_configure(proc, TIMEOUT)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -21,8 +21,8 @@ def test_match(brew_unknown_cmd):
|
||||
|
||||
|
||||
def test_get_new_command(brew_unknown_cmd, brew_unknown_cmd2):
|
||||
assert get_new_command(Command('brew inst', stderr=brew_unknown_cmd)) \
|
||||
== ['brew list', 'brew install', 'brew uninstall']
|
||||
assert (get_new_command(Command('brew inst', stderr=brew_unknown_cmd))
|
||||
== ['brew list', 'brew install', 'brew uninstall'])
|
||||
|
||||
cmds = get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2))
|
||||
assert 'brew install' in cmds
|
||||
|
||||
@@ -48,9 +48,9 @@ def test_match(composer_not_command, composer_not_command_one_of_this):
|
||||
|
||||
|
||||
def test_get_new_command(composer_not_command, composer_not_command_one_of_this):
|
||||
assert get_new_command(Command('composer udpate',
|
||||
stderr=composer_not_command)) \
|
||||
== 'composer update'
|
||||
assert get_new_command(
|
||||
Command('composer pdate', stderr=composer_not_command_one_of_this)) \
|
||||
== 'composer selfupdate'
|
||||
assert (get_new_command(Command('composer udpate',
|
||||
stderr=composer_not_command))
|
||||
== 'composer update')
|
||||
assert (get_new_command(Command('composer pdate',
|
||||
stderr=composer_not_command_one_of_this))
|
||||
== 'composer selfupdate')
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import pytest
|
||||
import tarfile
|
||||
from thefuck.rules.dirty_untar import match, get_new_command, side_effect, \
|
||||
tar_extensions
|
||||
tar_extensions # noqa: E126
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ def tar_error(tmpdir):
|
||||
|
||||
return fixture
|
||||
|
||||
|
||||
parametrize_extensions = pytest.mark.parametrize('ext', tar_extensions)
|
||||
|
||||
# (filename as typed by the user, unquoted filename, quoted filename as per shells.quote)
|
||||
parametrize_filename = pytest.mark.parametrize('filename, unquoted, quoted', [
|
||||
('foo{}', 'foo{}', 'foo{}'),
|
||||
('foo\ bar{}', 'foo bar{}', "'foo bar{}'"),
|
||||
('"foo bar{}"', 'foo bar{}', "'foo bar{}'")])
|
||||
|
||||
parametrize_script = pytest.mark.parametrize('script, fixed', [
|
||||
|
||||
@@ -64,7 +64,6 @@ def test_side_effect(zip_error, script, filename):
|
||||
@pytest.mark.parametrize('script,fixed,filename', [
|
||||
(u'unzip café', u"unzip café -d 'café'", u'café.zip'),
|
||||
(u'unzip foo', u'unzip foo -d foo', u'foo.zip'),
|
||||
(u"unzip foo\\ bar.zip", u"unzip foo\\ bar.zip -d 'foo bar'", u'foo.zip'),
|
||||
(u"unzip 'foo bar.zip'", u"unzip 'foo bar.zip' -d 'foo bar'", u'foo.zip'),
|
||||
(u'unzip foo.zip', u'unzip foo.zip -d foo', u'foo.zip')])
|
||||
def test_get_new_command(zip_error, script, fixed, filename):
|
||||
|
||||
@@ -37,7 +37,7 @@ south.exceptions.GhostMigrations:
|
||||
! I'm not trusting myself; either fix this yourself by fiddling
|
||||
! with the south_migrationhistory table, or pass --delete-ghost-migrations
|
||||
! to South to have it delete ALL of these records (this may not be good).
|
||||
'''
|
||||
''' # noqa
|
||||
|
||||
|
||||
def test_match(stderr):
|
||||
|
||||
@@ -39,5 +39,5 @@ def test_match(stderr):
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('./manage.py migrate auth')) \
|
||||
== './manage.py migrate auth --merge'
|
||||
assert (get_new_command(Command('./manage.py migrate auth'))
|
||||
== './manage.py migrate auth --merge')
|
||||
|
||||
@@ -18,5 +18,5 @@ def test_match():
|
||||
|
||||
def test_get_new_command():
|
||||
""" Replace the Alt+Space character by a simple space """
|
||||
assert get_new_command(Command(u'ps -ef | grep foo'))\
|
||||
== 'ps -ef | grep foo'
|
||||
assert (get_new_command(Command(u'ps -ef | grep foo'))
|
||||
== 'ps -ef | grep foo')
|
||||
|
||||
@@ -191,7 +191,7 @@ E NameError: name 'mocker' is not defined
|
||||
|
||||
/home/thefuck/tests/rules/test_fix_file.py:218: NameError
|
||||
""", ''),
|
||||
)
|
||||
) # noqa
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
@@ -227,10 +227,6 @@ def test_get_new_command(mocker, monkeypatch, test):
|
||||
mocker.patch('os.path.isfile', return_value=True)
|
||||
monkeypatch.setenv('EDITOR', 'dummy_editor')
|
||||
|
||||
cmd = Command(script=test[0], stdout=test[4], stderr=test[5])
|
||||
#assert (get_new_command(cmd, Settings({})) ==
|
||||
# 'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('test', tests)
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
|
||||
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")
|
||||
@@ -7,7 +7,7 @@ from tests.utils import Command
|
||||
def git_not_command():
|
||||
return """git: 'brnch' is not a git command. See 'git --help'.
|
||||
|
||||
Did you mean this?
|
||||
The most similar command is
|
||||
branch
|
||||
"""
|
||||
|
||||
@@ -16,7 +16,7 @@ branch
|
||||
def git_not_command_one_of_this():
|
||||
return """git: 'st' is not a git command. See 'git --help'.
|
||||
|
||||
Did you mean one of these?
|
||||
The most similar commands are
|
||||
status
|
||||
reset
|
||||
stage
|
||||
@@ -29,7 +29,7 @@ stats
|
||||
def git_not_command_closest():
|
||||
return '''git: 'tags' is not a git command. See 'git --help'.
|
||||
|
||||
Did you mean one of these?
|
||||
The most similar commands are
|
||||
\tstage
|
||||
\ttag
|
||||
'''
|
||||
@@ -49,9 +49,9 @@ def test_match(git_not_command, git_command, git_not_command_one_of_this):
|
||||
|
||||
def test_get_new_command(git_not_command, git_not_command_one_of_this,
|
||||
git_not_command_closest):
|
||||
assert get_new_command(Command('git brnch', stderr=git_not_command)) \
|
||||
== ['git branch']
|
||||
assert get_new_command(Command('git st', stderr=git_not_command_one_of_this)) \
|
||||
== ['git stats', 'git stash', 'git stage']
|
||||
assert get_new_command(Command('git tags', stderr=git_not_command_closest)) \
|
||||
== ['git tag', 'git stage']
|
||||
assert (get_new_command(Command('git brnch', stderr=git_not_command))
|
||||
== ['git branch'])
|
||||
assert (get_new_command(Command('git st', stderr=git_not_command_one_of_this))
|
||||
== ['git stats', 'git stash', 'git stage'])
|
||||
assert (get_new_command(Command('git tags', stderr=git_not_command_closest))
|
||||
== ['git tag', 'git stage'])
|
||||
|
||||
@@ -25,5 +25,5 @@ def test_match(stderr):
|
||||
|
||||
|
||||
def test_get_new_command(stderr):
|
||||
assert get_new_command(Command('git pull', stderr=stderr)) \
|
||||
== "git branch --set-upstream-to=origin/master master && git pull"
|
||||
assert (get_new_command(Command('git pull', stderr=stderr))
|
||||
== "git branch --set-upstream-to=origin/master master && git pull")
|
||||
|
||||
@@ -15,5 +15,5 @@ def test_match(stderr):
|
||||
|
||||
|
||||
def test_get_new_command(stderr):
|
||||
assert get_new_command(Command('git pull', stderr=stderr)) \
|
||||
== "git stash && git pull && git stash pop"
|
||||
assert (get_new_command(Command('git pull', stderr=stderr))
|
||||
== "git stash && git pull && git stash pop")
|
||||
|
||||
@@ -15,5 +15,5 @@ def test_match(stderr):
|
||||
|
||||
|
||||
def test_get_new_command(stderr):
|
||||
assert get_new_command(Command('git pull', stderr=stderr)) \
|
||||
== "git stash && git pull && git stash pop"
|
||||
assert (get_new_command(Command('git pull', stderr=stderr))
|
||||
== "git stash && git pull && git stash pop")
|
||||
|
||||
@@ -39,12 +39,7 @@ To /tmp/bar
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push', stderr=git_err),
|
||||
Command(script='git push nvbn', stderr=git_err),
|
||||
Command(script='git push nvbn master', stderr=git_err)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='git push nvbn master', stderr=git_err),
|
||||
Command(script='git push', stderr=git_err2),
|
||||
Command(script='git push nvbn', stderr=git_err2),
|
||||
Command(script='git push nvbn master', stderr=git_err2)])
|
||||
@@ -68,12 +63,7 @@ def test_not_match(command):
|
||||
(Command(script='git push nvbn', stderr=git_err),
|
||||
'git pull nvbn && git push nvbn'),
|
||||
(Command(script='git push nvbn master', stderr=git_err),
|
||||
'git pull nvbn master && git push nvbn master')])
|
||||
def test_get_new_command(command, output):
|
||||
assert get_new_command(command) == output
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, output', [
|
||||
'git pull nvbn master && git push nvbn master'),
|
||||
(Command(script='git push', stderr=git_err2), 'git pull && git push'),
|
||||
(Command(script='git push nvbn', stderr=git_err2),
|
||||
'git pull nvbn && git push nvbn'),
|
||||
|
||||
27
tests/rules/test_git_push_without_commits.py
Normal file
27
tests/rules/test_git_push_without_commits.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.git_push_without_commits import (
|
||||
fix,
|
||||
get_new_command,
|
||||
match,
|
||||
)
|
||||
|
||||
command = 'git push -u origin master'
|
||||
expected_error = '''
|
||||
error: src refspec master does not match any.
|
||||
error: failed to push some refs to 'git@github.com:User/repo.git'
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [Command(command, stderr=expected_error)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, result', [(
|
||||
Command(command, stderr=expected_error),
|
||||
fix.format(command=command),
|
||||
)])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command) == result
|
||||
@@ -14,11 +14,11 @@ def test_match(command):
|
||||
Command('git remote add origin url'),
|
||||
Command('git remote remove origin'),
|
||||
Command('git remote prune origin'),
|
||||
Command('git remote set-branches origin branch')
|
||||
])
|
||||
Command('git remote set-branches origin branch')])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git remote set-url origin git@github.com:nvbn/thefuck.git'),
|
||||
'git remote add origin git@github.com:nvbn/thefuck.git')])
|
||||
|
||||
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
|
||||
@@ -14,5 +14,5 @@ def test_match(stderr):
|
||||
|
||||
|
||||
def test_get_new_command(stderr):
|
||||
assert get_new_command(Command('git stash pop', stderr=stderr)) \
|
||||
== "git add . && git stash pop && git reset ."
|
||||
assert (get_new_command(Command('git stash pop', stderr=stderr))
|
||||
== "git add --update && 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")
|
||||
@@ -1,34 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.heroku_not_command import match, get_new_command
|
||||
|
||||
|
||||
def suggest_stderr(cmd):
|
||||
return ''' ! `{}` is not a heroku command.
|
||||
! Perhaps you meant `logs`, `pg`.
|
||||
! See `heroku help` for a list of available commands.'''.format(cmd)
|
||||
suggest_stderr = '''
|
||||
▸ log is not a heroku command.
|
||||
▸ Perhaps you meant logs?
|
||||
▸ Run heroku _ to run heroku logs.
|
||||
▸ Run heroku help for a list of available commands.'''
|
||||
|
||||
|
||||
no_suggest_stderr = ''' ! `aaaaa` is not a heroku command.
|
||||
! See `heroku help` for a list of available commands.'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd', ['log', 'pge'])
|
||||
@pytest.mark.parametrize('cmd', ['log'])
|
||||
def test_match(cmd):
|
||||
assert match(
|
||||
Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd)))
|
||||
Command('heroku {}'.format(cmd), stderr=suggest_stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('cat log', suggest_stderr('log')),
|
||||
('heroku aaa', no_suggest_stderr)])
|
||||
('cat log', suggest_stderr)])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd, result', [
|
||||
('log', ['heroku logs', 'heroku pg']),
|
||||
('pge', ['heroku pg', 'heroku logs'])])
|
||||
('log', 'heroku logs')])
|
||||
def test_get_new_command(cmd, result):
|
||||
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr(cmd))
|
||||
command = Command('heroku {}'.format(cmd), stderr=suggest_stderr)
|
||||
assert get_new_command(command) == result
|
||||
|
||||
29
tests/rules/test_hostscli.py
Normal file
29
tests/rules/test_hostscli.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
from thefuck.rules.hostscli import no_website, get_new_command, match
|
||||
from tests.utils import Command
|
||||
|
||||
no_website_long = '''
|
||||
{}:
|
||||
|
||||
No Domain list found for website: a_website_that_does_not_exist
|
||||
|
||||
Please raise a Issue here: https://github.com/dhilipsiva/hostscli/issues/new
|
||||
if you think we should add domains for this website.
|
||||
|
||||
type `hostscli websites` to see a list of websites that you can block/unblock
|
||||
'''.format(no_website)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('hostscli block a_website_that_does_not_exist',
|
||||
stderr=no_website_long)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, result', [(
|
||||
Command('hostscli block a_website_that_does_not_exist',
|
||||
stderr=no_website_long),
|
||||
['hostscli websites'])])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command) == result
|
||||
@@ -19,5 +19,5 @@ def test_match(is_not_task):
|
||||
|
||||
|
||||
def test_get_new_command(is_not_task):
|
||||
assert get_new_command(Command(script='lein rpl --help', stderr=is_not_task)) \
|
||||
== ['lein repl --help', 'lein jar --help']
|
||||
assert (get_new_command(Command(script='lein rpl --help', stderr=is_not_task))
|
||||
== ['lein repl --help', 'lein jar --help'])
|
||||
|
||||
@@ -11,16 +11,6 @@ def file_exists(mocker):
|
||||
get_stderr = "ln: failed to create symbolic link '{}': File exists".format
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('file_exists')
|
||||
@pytest.mark.parametrize('script', [
|
||||
'ln -s dest source',
|
||||
'ln dest -s source',
|
||||
'ln dest source -s'])
|
||||
def test_match(script):
|
||||
stderr = get_stderr('source')
|
||||
assert match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr, exists', [
|
||||
('ln dest source', get_stderr('source'), True),
|
||||
('ls -s dest source', get_stderr('source'), True),
|
||||
@@ -38,4 +28,5 @@ def test_not_match(file_exists, script, stderr, exists):
|
||||
('ln dest source -s', 'ln source -s dest')])
|
||||
def test_match(script, result):
|
||||
stderr = get_stderr('source')
|
||||
assert match(Command(script, stderr=stderr))
|
||||
assert get_new_command(Command(script, stderr=stderr)) == result
|
||||
|
||||
30
tests/rules/test_missing_space_before_subcommand.py
Normal file
30
tests/rules/test_missing_space_before_subcommand.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import pytest
|
||||
from thefuck.rules.missing_space_before_subcommand import (
|
||||
match, get_new_command)
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def all_executables(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.rules.missing_space_before_subcommand.get_all_executables',
|
||||
return_value=['git', 'ls', 'npm'])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', [
|
||||
'gitbranch', 'ls-la', 'npminstall'])
|
||||
def test_match(script):
|
||||
assert match(Command(script))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', ['git branch', 'vimfile'])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('gitbranch', 'git branch'),
|
||||
('ls-la', 'ls -la'),
|
||||
('npminstall webpack', 'npm install webpack')])
|
||||
def test_get_new_command(script, result):
|
||||
assert get_new_command(Command(script)) == result
|
||||
@@ -25,7 +25,7 @@ def test_match(command):
|
||||
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
|
||||
[INFO] Final Memory: 6M/240M
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
"""),
|
||||
"""), # noqa
|
||||
Command(script='mvn --help'),
|
||||
Command(script='mvn -v')
|
||||
])
|
||||
|
||||
@@ -25,7 +25,7 @@ def test_match(command):
|
||||
[INFO] Finished at: Wed Aug 26 13:05:47 BST 2015
|
||||
[INFO] Final Memory: 6M/240M
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
"""),
|
||||
"""), # noqa
|
||||
Command(script='mvn --help'),
|
||||
Command(script='mvn -v')
|
||||
])
|
||||
|
||||
43
tests/rules/test_path_from_history.py
Normal file
43
tests/rules/test_path_from_history.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import pytest
|
||||
from thefuck.rules.path_from_history import match, get_new_command
|
||||
from tests.utils import Command
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def history(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.rules.path_from_history.get_valid_history_without_current',
|
||||
return_value=['cd /opt/java', 'ls ~/work/project/'])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def path_exists(mocker):
|
||||
path_mock = mocker.patch('thefuck.rules.path_from_history.Path')
|
||||
exists_mock = path_mock.return_value.expanduser.return_value.exists
|
||||
exists_mock.return_value = True
|
||||
return exists_mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('ls project', 'no such file or directory: project'),
|
||||
('cd project', "can't cd to project"),
|
||||
])
|
||||
def test_match(script, stderr):
|
||||
assert match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr', [
|
||||
('myapp cats', 'no such file or directory: project'),
|
||||
('cd project', ""),
|
||||
])
|
||||
def test_not_match(script, stderr):
|
||||
assert not match(Command(script, stderr=stderr))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, stderr, result', [
|
||||
('ls project', 'no such file or directory: project', 'ls ~/work/project'),
|
||||
('cd java', "can't cd to java", 'cd /opt/java'),
|
||||
])
|
||||
def test_get_new_command(script, stderr, result):
|
||||
new_command = get_new_command(Command(script, stderr=stderr))
|
||||
assert new_command[0] == result
|
||||
@@ -8,5 +8,5 @@ def test_match():
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('./test_sudo.py'))\
|
||||
== 'python ./test_sudo.py'
|
||||
assert (get_new_command(Command('./test_sudo.py'))
|
||||
== 'python ./test_sudo.py')
|
||||
|
||||
@@ -17,5 +17,5 @@ def test_not_match(command):
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command(script='rm -rf /')) \
|
||||
== 'rm -rf / --no-preserve-root'
|
||||
assert (get_new_command(Command(script='rm -rf /'))
|
||||
== 'rm -rf / --no-preserve-root')
|
||||
|
||||
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
|
||||
@@ -18,11 +18,11 @@ def test_match(sed_unterminated_s):
|
||||
|
||||
|
||||
def test_get_new_command(sed_unterminated_s):
|
||||
assert get_new_command(Command('sed -e s/foo/bar', stderr=sed_unterminated_s)) \
|
||||
== 'sed -e s/foo/bar/'
|
||||
assert get_new_command(Command('sed -es/foo/bar', stderr=sed_unterminated_s)) \
|
||||
== 'sed -es/foo/bar/'
|
||||
assert get_new_command(Command(r"sed -e 's/\/foo/bar'", stderr=sed_unterminated_s)) \
|
||||
== r"sed -e 's/\/foo/bar/'"
|
||||
assert get_new_command(Command(r"sed -e s/foo/bar -es/baz/quz", stderr=sed_unterminated_s)) \
|
||||
== r"sed -e s/foo/bar/ -es/baz/quz/"
|
||||
assert (get_new_command(Command('sed -e s/foo/bar', stderr=sed_unterminated_s))
|
||||
== 'sed -e s/foo/bar/')
|
||||
assert (get_new_command(Command('sed -es/foo/bar', stderr=sed_unterminated_s))
|
||||
== 'sed -es/foo/bar/')
|
||||
assert (get_new_command(Command(r"sed -e 's/\/foo/bar'", stderr=sed_unterminated_s))
|
||||
== r"sed -e 's/\/foo/bar/'")
|
||||
assert (get_new_command(Command(r"sed -e s/foo/bar -es/baz/quz", stderr=sed_unterminated_s))
|
||||
== r"sed -e s/foo/bar/ -es/baz/quz/")
|
||||
|
||||
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
|
||||
24
tests/rules/test_yarn_alias.py
Normal file
24
tests/rules/test_yarn_alias.py
Normal file
@@ -0,0 +1,24 @@
|
||||
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_etl = 'error Command "etil" not found. Did you mean "etl"?'
|
||||
stderr_list = 'error Did you mean `yarn list`?'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='yarn rm', stderr=stderr_remove),
|
||||
Command(script='yarn etil', stderr=stderr_etl),
|
||||
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 etil', stderr=stderr_etl), 'yarn etl'),
|
||||
(Command('yarn ls', stderr=stderr_list), 'yarn list')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
118
tests/rules/test_yarn_command_not_found.py
Normal file
118
tests/rules/test_yarn_command_not_found.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# -*- 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.
|
||||
''' # noqa
|
||||
|
||||
|
||||
@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'),
|
||||
(Command('yarn require lodash', stderr=stderr('require')),
|
||||
'yarn add lodash')])
|
||||
def test_get_new_command(command, result):
|
||||
fixed_command = get_new_command(command)
|
||||
if isinstance(fixed_command, list):
|
||||
fixed_command = fixed_command[0]
|
||||
|
||||
assert fixed_command == result
|
||||
32
tests/rules/test_yarn_command_replaced.py
Normal file
32
tests/rules/test_yarn_command_replaced.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import pytest
|
||||
from tests.utils import Command
|
||||
from thefuck.rules.yarn_command_replaced import match, get_new_command
|
||||
|
||||
|
||||
stderr = ('error `install` has been replaced with `add` to add new '
|
||||
'dependencies. Run "yarn add {}" instead.').format
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='yarn install redux', stderr=stderr('redux')),
|
||||
Command(script='yarn install moment', stderr=stderr('moment')),
|
||||
Command(script='yarn install lodash', stderr=stderr('lodash'))])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yarn install')])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('yarn install redux', stderr=stderr('redux')),
|
||||
'yarn add redux'),
|
||||
(Command('yarn install moment', stderr=stderr('moment')),
|
||||
'yarn add moment'),
|
||||
(Command('yarn install lodash', stderr=stderr('lodash')),
|
||||
'yarn add lodash')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
57
tests/rules/test_yarn_help.py
Normal file
57
tests/rules/test_yarn_help.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import pytest
|
||||
from thefuck.rules.yarn_help import match, get_new_command
|
||||
from tests.utils import Command
|
||||
from thefuck.system import open_command
|
||||
|
||||
|
||||
stdout_clean = '''
|
||||
|
||||
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
|
||||
|
||||
Visit https://yarnpkg.com/en/docs/cli/clean for documentation about this command.
|
||||
''' # noqa
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command(script='yarn help clean', stdout=stdout_clean)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('yarn help clean', stdout=stdout_clean),
|
||||
open_command('https://yarnpkg.com/en/docs/cli/clean'))])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
@@ -20,3 +20,11 @@ def history_lines(mocker):
|
||||
.return_value.readlines.return_value = lines
|
||||
|
||||
return aux
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_exists(mocker):
|
||||
path_mock = mocker.patch('thefuck.shells.generic.Path')
|
||||
return path_mock.return_value \
|
||||
.expanduser.return_value \
|
||||
.exists
|
||||
|
||||
@@ -33,6 +33,9 @@ class TestBash(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
@@ -40,18 +43,17 @@ class TestBash(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'fuck () {' in shell.app_alias('fuck')
|
||||
assert 'FUCK () {' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'TF_ALIAS=fuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING=utf-8' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
assert "fuck () {" in alias
|
||||
assert "TF_ALIAS=fuck" in alias
|
||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
@@ -61,3 +63,12 @@ class TestBash(object):
|
||||
command = 'git log -p'
|
||||
command_parts = ['git', 'log', '-p']
|
||||
assert shell.split_command(command) == command_parts
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@@ -55,6 +55,9 @@ class TestFish(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('foo', 'bar') == 'foo; and bar'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('foo', 'bar') == 'foo; or bar'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fish_config': 'fish_config',
|
||||
'fuck': 'fuck',
|
||||
@@ -95,3 +98,12 @@ class TestFish(object):
|
||||
shell.put_to_history(entry)
|
||||
builtins_open.return_value.__enter__.return_value. \
|
||||
write.assert_called_once_with(entry_utf8)
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@@ -18,6 +18,9 @@ class TestGeneric(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {}
|
||||
|
||||
@@ -37,3 +40,6 @@ class TestGeneric(object):
|
||||
def test_split_command(self, shell):
|
||||
assert shell.split_command('ls') == ['ls']
|
||||
assert shell.split_command(u'echo café') == [u'echo', u'café']
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert shell.how_to_configure() is None
|
||||
|
||||
@@ -17,3 +17,6 @@ class TestPowershell(object):
|
||||
assert 'function fuck' in shell.app_alias('fuck')
|
||||
assert 'function FUCK' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@@ -34,6 +34,9 @@ class TestTcsh(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {'fuck': 'eval $(thefuck $(fc -ln -1))',
|
||||
'l': 'ls -CF',
|
||||
@@ -48,3 +51,12 @@ class TestTcsh(object):
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines(['ls', 'rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@@ -32,6 +32,9 @@ class TestZsh(object):
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == 'ls && cd'
|
||||
|
||||
def test_or_(self, shell):
|
||||
assert shell.or_('ls', 'cd') == 'ls || cd'
|
||||
|
||||
def test_get_aliases(self, shell):
|
||||
assert shell.get_aliases() == {
|
||||
'fuck': 'eval $(thefuck $(fc -ln -1 | tail -n 1))',
|
||||
@@ -40,18 +43,27 @@ class TestZsh(object):
|
||||
'll': 'ls -alF'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'alias fuck' in shell.app_alias('fuck')
|
||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||
assert 'fuck () {' in shell.app_alias('fuck')
|
||||
assert 'FUCK () {' in shell.app_alias('FUCK')
|
||||
assert 'thefuck' in shell.app_alias('fuck')
|
||||
assert 'PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_variables_correctly_set(self, shell):
|
||||
alias = shell.app_alias('fuck')
|
||||
assert "alias fuck='TF_CMD=$(TF_ALIAS" in alias
|
||||
assert '$(TF_ALIAS=fuck PYTHONIOENCODING' in alias
|
||||
assert 'PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES' in alias
|
||||
assert 'ALIASES=$(alias) thefuck' in alias
|
||||
assert "fuck () {" in alias
|
||||
assert "TF_ALIAS=fuck" in alias
|
||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||
|
||||
def test_get_history(self, history_lines, shell):
|
||||
history_lines([': 1432613911:0;ls', ': 1432613916:0;rm'])
|
||||
assert list(shell.get_history()) == ['ls', 'rm']
|
||||
|
||||
def test_how_to_configure(self, shell, config_exists):
|
||||
config_exists.return_value = True
|
||||
assert shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_how_to_configure_when_config_not_found(self, shell,
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
29
tests/test_argument_parser.py
Normal file
29
tests/test_argument_parser.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
from thefuck.argument_parser import Parser
|
||||
from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||
|
||||
|
||||
def _args(**override):
|
||||
args = {'alias': None, 'command': [], 'yes': False,
|
||||
'help': False, 'version': False, 'debug': False,
|
||||
'force_command': None, 'repeat': False}
|
||||
args.update(override)
|
||||
return args
|
||||
|
||||
|
||||
@pytest.mark.parametrize('argv, result', [
|
||||
(['thefuck'], _args()),
|
||||
(['thefuck', '-a'], _args(alias='fuck')),
|
||||
(['thefuck', '-a', 'fix'], _args(alias='fix')),
|
||||
(['thefuck', 'git', 'branch', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch'], yes=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y'],
|
||||
_args(command=['git', 'branch', '-a'], yes=True)),
|
||||
(['thefuck', ARGUMENT_PLACEHOLDER, '-v'], _args(version=True)),
|
||||
(['thefuck', ARGUMENT_PLACEHOLDER, '--help'], _args(help=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-y', '-d'],
|
||||
_args(command=['git', 'branch', '-a'], yes=True, debug=True)),
|
||||
(['thefuck', 'git', 'branch', '-a', ARGUMENT_PLACEHOLDER, '-r', '-d'],
|
||||
_args(command=['git', 'branch', '-a'], repeat=True, debug=True))])
|
||||
def test_parse(argv, result):
|
||||
assert vars(Parser().parse(argv)) == result
|
||||
@@ -79,6 +79,13 @@ class TestSettingsFromEnv(object):
|
||||
assert settings.rules == const.DEFAULT_RULES + ['bash', 'lisp']
|
||||
|
||||
|
||||
def test_settings_from_args(settings):
|
||||
settings.init(Mock(yes=True, debug=True, repeat=True))
|
||||
assert not settings.require_confirmation
|
||||
assert settings.debug
|
||||
assert settings.repeat
|
||||
|
||||
|
||||
class TestInitializeSettingsFile(object):
|
||||
def test_ignore_if_exists(self, settings):
|
||||
settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock())
|
||||
|
||||
@@ -47,8 +47,8 @@ def test_get_corrected_commands(mocker):
|
||||
get_new_command=lambda x: [x.script + '@', x.script + ';'],
|
||||
priority=60)]
|
||||
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
|
||||
assert [cmd.script for cmd in get_corrected_commands(command)] \
|
||||
== ['test!', 'test@', 'test;']
|
||||
assert ([cmd.script for cmd in get_corrected_commands(command)]
|
||||
== ['test!', 'test@', 'test;'])
|
||||
|
||||
|
||||
def test_organize_commands():
|
||||
|
||||
125
tests/test_not_configured.py
Normal file
125
tests/test_not_configured.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import pytest
|
||||
from mock import MagicMock
|
||||
from thefuck.shells.generic import ShellConfiguration
|
||||
from thefuck.not_configured import main
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def usage_tracker(mocker):
|
||||
return mocker.patch(
|
||||
'thefuck.not_configured._get_not_configured_usage_tracker_path',
|
||||
new_callable=MagicMock)
|
||||
|
||||
|
||||
def _assert_tracker_updated(usage_tracker, pid):
|
||||
usage_tracker.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value \
|
||||
.write.assert_called_once_with(str(pid))
|
||||
|
||||
|
||||
def _change_tracker(usage_tracker, pid):
|
||||
usage_tracker.return_value.exists.return_value = True
|
||||
usage_tracker.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value \
|
||||
.read.return_value = str(pid)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_pid(mocker):
|
||||
return mocker.patch('thefuck.not_configured._get_shell_pid',
|
||||
new_callable=MagicMock)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell(mocker):
|
||||
shell = mocker.patch('thefuck.not_configured.shell',
|
||||
new_callable=MagicMock)
|
||||
shell.get_history.return_value = []
|
||||
shell.how_to_configure.return_value = ShellConfiguration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path='/tmp/.bashrc',
|
||||
reload='bash',
|
||||
can_configure_automatically=True)
|
||||
return shell
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_config(mocker):
|
||||
path_mock = mocker.patch('thefuck.not_configured.Path',
|
||||
new_callable=MagicMock)
|
||||
return path_mock.return_value \
|
||||
.expanduser.return_value \
|
||||
.open.return_value \
|
||||
.__enter__.return_value
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def logs(mocker):
|
||||
return mocker.patch('thefuck.not_configured.logs',
|
||||
new_callable=MagicMock)
|
||||
|
||||
|
||||
def test_for_generic_shell(shell, logs):
|
||||
shell.how_to_configure.return_value = None
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_first_run(usage_tracker, shell_pid, logs):
|
||||
shell_pid.return_value = 12
|
||||
usage_tracker.return_value.exists.return_value = False
|
||||
main()
|
||||
_assert_tracker_updated(usage_tracker, 12)
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_run_after_other_commands(usage_tracker, shell_pid, shell, logs):
|
||||
shell_pid.return_value = 12
|
||||
shell.get_history.return_value = ['fuck', 'ls']
|
||||
_change_tracker(usage_tracker, 12)
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_on_first_run_from_current_shell(usage_tracker, shell_pid,
|
||||
shell, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 55)
|
||||
main()
|
||||
_assert_tracker_updated(usage_tracker, 12)
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_when_cant_configure_automatically(shell_pid, shell, logs):
|
||||
shell_pid.return_value = 12
|
||||
shell.how_to_configure.return_value = ShellConfiguration(
|
||||
content='eval $(thefuck --alias)',
|
||||
path='/tmp/.bashrc',
|
||||
reload='bash',
|
||||
can_configure_automatically=False)
|
||||
main()
|
||||
logs.how_to_configure_alias.assert_called_once()
|
||||
|
||||
|
||||
def test_when_already_configured(usage_tracker, shell_pid,
|
||||
shell, shell_config, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 12)
|
||||
shell_config.read.return_value = 'eval $(thefuck --alias)'
|
||||
main()
|
||||
logs.already_configured.assert_called_once()
|
||||
|
||||
|
||||
def test_when_successfuly_configured(usage_tracker, shell_pid,
|
||||
shell, shell_config, logs):
|
||||
shell.get_history.return_value = ['fuck']
|
||||
shell_pid.return_value = 12
|
||||
_change_tracker(usage_tracker, 12)
|
||||
shell_config.read.return_value = ''
|
||||
main()
|
||||
shell_config.write.assert_any_call('eval $(thefuck --alias)')
|
||||
logs.configured_successfully.assert_called_once()
|
||||
@@ -13,10 +13,10 @@ from thefuck.system import Path
|
||||
class TestCorrectedCommand(object):
|
||||
|
||||
def test_equality(self):
|
||||
assert CorrectedCommand('ls', None, 100) == \
|
||||
CorrectedCommand('ls', None, 200)
|
||||
assert CorrectedCommand('ls', None, 100) != \
|
||||
CorrectedCommand('ls', lambda *_: _, 100)
|
||||
assert (CorrectedCommand('ls', None, 100) ==
|
||||
CorrectedCommand('ls', None, 200))
|
||||
assert (CorrectedCommand('ls', None, 100) !=
|
||||
CorrectedCommand('ls', lambda *_: _, 100))
|
||||
|
||||
def test_hashable(self):
|
||||
assert {CorrectedCommand('ls', None, 100),
|
||||
@@ -28,6 +28,20 @@ class TestCorrectedCommand(object):
|
||||
assert u'{}'.format(CorrectedCommand(u'echo café', None, 100)) == \
|
||||
u'CorrectedCommand(script=echo café, side_effect=None, priority=100)'
|
||||
|
||||
@pytest.mark.parametrize('script, printed, override_settings', [
|
||||
('git branch', 'git branch', {'repeat': False, 'debug': False}),
|
||||
('git brunch',
|
||||
"git brunch || fuck --repeat --force-command 'git brunch'",
|
||||
{'repeat': True, 'debug': False}),
|
||||
('git brunch',
|
||||
"git brunch || fuck --repeat --debug --force-command 'git brunch'",
|
||||
{'repeat': True, 'debug': True})])
|
||||
def test_run(self, capsys, settings, script, printed, override_settings):
|
||||
settings.update(override_settings)
|
||||
CorrectedCommand(script, None, 1000).run(Command())
|
||||
out, _ = capsys.readouterr()
|
||||
assert out[:-1] == printed
|
||||
|
||||
|
||||
class TestRule(object):
|
||||
def test_from_path(self, mocker):
|
||||
@@ -41,8 +55,8 @@ class TestRule(object):
|
||||
priority=900,
|
||||
requires_output=True))
|
||||
rule_path = os.path.join(os.sep, 'rules', 'bash.py')
|
||||
assert Rule.from_path(Path(rule_path)) \
|
||||
== Rule('bash', match, get_new_command, priority=900)
|
||||
assert (Rule.from_path(Path(rule_path))
|
||||
== Rule('bash', match, get_new_command, priority=900))
|
||||
load_source.assert_called_once_with('bash', rule_path)
|
||||
|
||||
@pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
|
||||
@@ -79,15 +93,15 @@ class TestRule(object):
|
||||
def test_get_corrected_commands_with_rule_returns_list(self):
|
||||
rule = Rule(get_new_command=lambda x: [x.script + '!', x.script + '@'],
|
||||
priority=100)
|
||||
assert list(rule.get_corrected_commands(Command(script='test'))) \
|
||||
assert (list(rule.get_corrected_commands(Command(script='test')))
|
||||
== [CorrectedCommand(script='test!', priority=100),
|
||||
CorrectedCommand(script='test@', priority=200)]
|
||||
CorrectedCommand(script='test@', priority=200)])
|
||||
|
||||
def test_get_corrected_commands_with_rule_returns_command(self):
|
||||
rule = Rule(get_new_command=lambda x: x.script + '!',
|
||||
priority=100)
|
||||
assert list(rule.get_corrected_commands(Command(script='test'))) \
|
||||
== [CorrectedCommand(script='test!', priority=100)]
|
||||
assert (list(rule.get_corrected_commands(Command(script='test')))
|
||||
== [CorrectedCommand(script='test!', priority=100)])
|
||||
|
||||
|
||||
class TestCommand(object):
|
||||
|
||||
@@ -30,11 +30,11 @@ def test_read_actions(patch_get_key):
|
||||
const.KEY_DOWN, 'j',
|
||||
# Ctrl+C:
|
||||
const.KEY_CTRL_C, 'q'])
|
||||
assert list(islice(ui.read_actions(), 8)) \
|
||||
assert (list(islice(ui.read_actions(), 8))
|
||||
== [const.ACTION_SELECT, const.ACTION_SELECT,
|
||||
const.ACTION_PREVIOUS, const.ACTION_PREVIOUS,
|
||||
const.ACTION_NEXT, const.ACTION_NEXT,
|
||||
const.ACTION_ABORT, const.ACTION_ABORT]
|
||||
const.ACTION_ABORT, const.ACTION_ABORT])
|
||||
|
||||
|
||||
def test_command_selector():
|
||||
@@ -74,8 +74,8 @@ class TestSelectCommand(object):
|
||||
def test_without_confirmation_with_side_effects(
|
||||
self, capsys, commands_with_side_effect, settings):
|
||||
settings.require_confirmation = False
|
||||
assert ui.select_command(iter(commands_with_side_effect)) \
|
||||
== commands_with_side_effect[0]
|
||||
assert (ui.select_command(iter(commands_with_side_effect))
|
||||
== commands_with_side_effect[0])
|
||||
assert capsys.readouterr() == ('', 'ls (+side effect)\n')
|
||||
|
||||
def test_with_confirmation(self, capsys, patch_get_key, commands):
|
||||
@@ -91,8 +91,8 @@ class TestSelectCommand(object):
|
||||
def test_with_confirmation_with_side_effct(self, capsys, patch_get_key,
|
||||
commands_with_side_effect):
|
||||
patch_get_key(['\n'])
|
||||
assert ui.select_command(iter(commands_with_side_effect)) \
|
||||
== commands_with_side_effect[0]
|
||||
assert (ui.select_command(iter(commands_with_side_effect))
|
||||
== commands_with_side_effect[0])
|
||||
assert capsys.readouterr() == ('', u'\x1b[1K\rls (+side effect) [enter/↑/↓/ctrl+c]\n')
|
||||
|
||||
def test_with_confirmation_select_second(self, capsys, patch_get_key, commands):
|
||||
|
||||
@@ -18,8 +18,7 @@ from tests.utils import Command
|
||||
def test_default_settings(settings, override, old, new):
|
||||
settings.clear()
|
||||
settings.update(old)
|
||||
fn = lambda _: _
|
||||
default_settings(override)(fn)(None)
|
||||
default_settings(override)(lambda _: _)(None)
|
||||
assert settings == new
|
||||
|
||||
|
||||
|
||||
84
thefuck/argument_parser.py
Normal file
84
thefuck/argument_parser.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import sys
|
||||
from argparse import ArgumentParser, SUPPRESS
|
||||
from .const import ARGUMENT_PLACEHOLDER
|
||||
from .utils import get_alias
|
||||
|
||||
|
||||
class Parser(object):
|
||||
"""Argument parser that can handle arguments with our special
|
||||
placeholder.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._parser = ArgumentParser(prog='thefuck', add_help=False)
|
||||
self._add_arguments()
|
||||
|
||||
def _add_arguments(self):
|
||||
"""Adds arguments to parser."""
|
||||
self._parser.add_argument(
|
||||
'-v', '--version',
|
||||
action='store_true',
|
||||
help="show program's version number and exit")
|
||||
self._parser.add_argument(
|
||||
'-a', '--alias',
|
||||
nargs='?',
|
||||
const=get_alias(),
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
self._parser.add_argument(
|
||||
'-h', '--help',
|
||||
action='store_true',
|
||||
help='show this help message and exit')
|
||||
self._add_conflicting_arguments()
|
||||
self._parser.add_argument(
|
||||
'-d', '--debug',
|
||||
action='store_true',
|
||||
help='enable debug output')
|
||||
self._parser.add_argument(
|
||||
'--force-command',
|
||||
action='store',
|
||||
help=SUPPRESS)
|
||||
self._parser.add_argument(
|
||||
'command',
|
||||
nargs='*',
|
||||
help='command that should be fixed')
|
||||
|
||||
def _add_conflicting_arguments(self):
|
||||
"""It's too dangerous to use `-y` and `-r` together."""
|
||||
group = self._parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
'-y', '--yes',
|
||||
action='store_true',
|
||||
help='execute fixed command without confirmation')
|
||||
group.add_argument(
|
||||
'-r', '--repeat',
|
||||
action='store_true',
|
||||
help='repeat on failure')
|
||||
|
||||
def _prepare_arguments(self, argv):
|
||||
"""Prepares arguments by:
|
||||
|
||||
- removing placeholder and moving arguments after it to beginning,
|
||||
we need this to distinguish arguments from `command` with ours;
|
||||
|
||||
- adding `--` before `command`, so our parse would ignore arguments
|
||||
of `command`.
|
||||
|
||||
"""
|
||||
if ARGUMENT_PLACEHOLDER in argv:
|
||||
index = argv.index(ARGUMENT_PLACEHOLDER)
|
||||
return argv[index + 1:] + ['--'] + argv[:index]
|
||||
elif argv and not argv[0].startswith('-') and argv[0] != '--':
|
||||
return ['--'] + argv
|
||||
else:
|
||||
return argv
|
||||
|
||||
def parse(self, argv):
|
||||
arguments = self._prepare_arguments(argv[1:])
|
||||
return self._parser.parse_args(arguments)
|
||||
|
||||
def print_usage(self):
|
||||
self._parser.print_usage(sys.stderr)
|
||||
|
||||
def print_help(self):
|
||||
self._parser.print_help(sys.stderr)
|
||||
@@ -14,7 +14,7 @@ class Settings(dict):
|
||||
def __setattr__(self, key, value):
|
||||
self[key] = value
|
||||
|
||||
def init(self):
|
||||
def init(self, args=None):
|
||||
"""Fills `settings` with values from `settings.py` and env."""
|
||||
from .logs import exception
|
||||
|
||||
@@ -31,6 +31,8 @@ class Settings(dict):
|
||||
except Exception:
|
||||
exception("Can't load settings from env", sys.exc_info())
|
||||
|
||||
self.update(self._settings_from_args(args))
|
||||
|
||||
def _init_settings_file(self):
|
||||
settings_path = self.user_dir.joinpath('settings.py')
|
||||
if not settings_path.is_file():
|
||||
@@ -109,5 +111,19 @@ class Settings(dict):
|
||||
for env, attr in const.ENV_TO_ATTR.items()
|
||||
if env in os.environ}
|
||||
|
||||
def _settings_from_args(self, args):
|
||||
"""Loads settings from args."""
|
||||
if not args:
|
||||
return {}
|
||||
|
||||
from_args = {}
|
||||
if args.yes:
|
||||
from_args['require_confirmation'] = not args.yes
|
||||
if args.debug:
|
||||
from_args['debug'] = args.debug
|
||||
if args.repeat:
|
||||
from_args['repeat'] = args.repeat
|
||||
return from_args
|
||||
|
||||
|
||||
settings = Settings(const.DEFAULT_SETTINGS)
|
||||
|
||||
@@ -33,7 +33,8 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'alter_history': True,
|
||||
'wait_slow_command': 15,
|
||||
'slow_commands': ['lein', 'react-native', 'gradle',
|
||||
'./gradlew'],
|
||||
'./gradlew', 'vagrant'],
|
||||
'repeat': False,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
@@ -46,7 +47,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_HISTORY_LIMIT': 'history_limit',
|
||||
'THEFUCK_ALTER_HISTORY': 'alter_history',
|
||||
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands'}
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
|
||||
'THEFUCK_REPEAT': 'repeat'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
@@ -59,3 +61,5 @@ SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
|
||||
"""
|
||||
|
||||
ARGUMENT_PLACEHOLDER = 'THEFUCK_ARGUMENT_PLACEHOLDER'
|
||||
|
||||
@@ -80,16 +80,50 @@ def debug_time(msg):
|
||||
|
||||
|
||||
def how_to_configure_alias(configuration_details):
|
||||
print("Seems like {bold}fuck{reset} alias isn't configured!".format(
|
||||
print(u"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(
|
||||
u"Please put {bold}{content}{reset} in your "
|
||||
u"{bold}{path}{reset} and apply "
|
||||
u"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))
|
||||
print('More details - https://github.com/nvbn/thefuck#manual-installation')
|
||||
**configuration_details._asdict()))
|
||||
|
||||
if configuration_details.can_configure_automatically:
|
||||
print(
|
||||
u"Or run {bold}fuck{reset} second time for configuring"
|
||||
u" it automatically.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL)))
|
||||
|
||||
print(u'More details - https://github.com/nvbn/thefuck#manual-installation')
|
||||
|
||||
|
||||
def already_configured(configuration_details):
|
||||
print(
|
||||
u"Seems like {bold}fuck{reset} alias already configured!\n"
|
||||
u"For applying changes run {bold}{reload}{reset}"
|
||||
u" or restart your shell.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
reload=configuration_details.reload))
|
||||
|
||||
|
||||
def configured_successfully(configuration_details):
|
||||
print(
|
||||
u"{bold}fuck{reset} alias configured successfully!\n"
|
||||
u"For applying changes run {bold}{reload}{reset}"
|
||||
u" or restart your shell.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL),
|
||||
reload=configuration_details.reload))
|
||||
|
||||
|
||||
def version(thefuck_version, python_version):
|
||||
sys.stderr.write(
|
||||
u'The Fuck {} using Python {}\n'.format(thefuck_version,
|
||||
python_version))
|
||||
|
||||
@@ -3,26 +3,28 @@ from .system import init_output
|
||||
|
||||
init_output()
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pprint import pformat
|
||||
import sys
|
||||
from . import logs, types
|
||||
from .shells import shell
|
||||
from .conf import settings
|
||||
from .corrector import get_corrected_commands
|
||||
from .exceptions import EmptyCommand
|
||||
from .utils import get_installation_info, get_alias
|
||||
from .ui import select_command
|
||||
from pprint import pformat # noqa: E402
|
||||
import sys # noqa: E402
|
||||
from . import logs, types # noqa: E402
|
||||
from .shells import shell # noqa: E402
|
||||
from .conf import settings # noqa: E402
|
||||
from .corrector import get_corrected_commands # noqa: E402
|
||||
from .exceptions import EmptyCommand # noqa: E402
|
||||
from .ui import select_command # noqa: E402
|
||||
from .argument_parser import Parser # noqa: E402
|
||||
from .utils import get_installation_info # noqa: E402
|
||||
|
||||
|
||||
def fix_command():
|
||||
def fix_command(known_args):
|
||||
"""Fixes previous command. Used when `thefuck` called without arguments."""
|
||||
settings.init()
|
||||
settings.init(known_args)
|
||||
with logs.debug_time('Total'):
|
||||
logs.debug(u'Run with settings: {}'.format(pformat(settings)))
|
||||
raw_command = ([known_args.force_command] if known_args.force_command
|
||||
else known_args.command)
|
||||
|
||||
try:
|
||||
command = types.Command.from_raw_script(sys.argv[1:])
|
||||
command = types.Command.from_raw_script(raw_command)
|
||||
except EmptyCommand:
|
||||
logs.debug('Empty command, nothing to do')
|
||||
return
|
||||
@@ -36,45 +38,18 @@ def fix_command():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def print_alias():
|
||||
"""Prints alias for current shell."""
|
||||
try:
|
||||
alias = sys.argv[2]
|
||||
except IndexError:
|
||||
alias = get_alias()
|
||||
|
||||
print(shell.app_alias(alias))
|
||||
|
||||
|
||||
def how_to_configure_alias():
|
||||
"""Shows useful information about how-to configure alias.
|
||||
|
||||
It'll be only visible when user type fuck and when alias isn't configured.
|
||||
|
||||
"""
|
||||
settings.init()
|
||||
logs.how_to_configure_alias(shell.how_to_configure())
|
||||
|
||||
|
||||
def main():
|
||||
parser = ArgumentParser(prog='thefuck')
|
||||
version = get_installation_info().version
|
||||
parser.add_argument(
|
||||
'-v', '--version',
|
||||
action='version',
|
||||
version='The Fuck {} using Python {}'.format(
|
||||
version, sys.version.split()[0]))
|
||||
parser.add_argument('-a', '--alias',
|
||||
action='store_true',
|
||||
help='[custom-alias-name] prints alias for current shell')
|
||||
parser.add_argument('command',
|
||||
nargs='*',
|
||||
help='command that should be fixed')
|
||||
known_args = parser.parse_args(sys.argv[1:2])
|
||||
parser = Parser()
|
||||
known_args = parser.parse(sys.argv)
|
||||
|
||||
if known_args.alias:
|
||||
print_alias()
|
||||
if known_args.help:
|
||||
parser.print_help()
|
||||
elif known_args.version:
|
||||
logs.version(get_installation_info().version,
|
||||
sys.version.split()[0])
|
||||
elif known_args.command:
|
||||
fix_command()
|
||||
fix_command(known_args)
|
||||
elif known_args.alias:
|
||||
print(shell.app_alias(known_args.alias))
|
||||
else:
|
||||
parser.print_usage()
|
||||
|
||||
87
thefuck/not_configured.py
Normal file
87
thefuck/not_configured.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Initialize output before importing any module, that can use colorama.
|
||||
from .system import init_output
|
||||
|
||||
init_output()
|
||||
|
||||
import os # noqa: E402
|
||||
from psutil import Process # noqa: E402
|
||||
import six # noqa: E402
|
||||
from . import logs # noqa: E402
|
||||
from .shells import shell # noqa: E402
|
||||
from .conf import settings # noqa: E402
|
||||
from .system import Path # noqa: E402
|
||||
from .utils import get_cache_dir # noqa: E402
|
||||
|
||||
|
||||
def _get_shell_pid():
|
||||
"""Returns parent process pid."""
|
||||
proc = Process(os.getpid())
|
||||
|
||||
try:
|
||||
return proc.parent().pid
|
||||
except TypeError:
|
||||
return proc.parent.pid
|
||||
|
||||
|
||||
def _get_not_configured_usage_tracker_path():
|
||||
"""Returns path of special file where we store latest shell pid."""
|
||||
return Path(get_cache_dir()).joinpath('thefuck.last_not_configured_run')
|
||||
|
||||
|
||||
def _record_first_run():
|
||||
"""Records shell pid to tracker file."""
|
||||
with _get_not_configured_usage_tracker_path().open('w') as tracker:
|
||||
tracker.write(six.text_type(_get_shell_pid()))
|
||||
|
||||
|
||||
def _is_second_run():
|
||||
"""Returns `True` when we know that `fuck` called second time."""
|
||||
tracker_path = _get_not_configured_usage_tracker_path()
|
||||
if not tracker_path.exists() or not shell.get_history()[-1] == 'fuck':
|
||||
return False
|
||||
|
||||
current_pid = _get_shell_pid()
|
||||
with tracker_path.open('r') as tracker:
|
||||
return tracker.read() == six.text_type(current_pid)
|
||||
|
||||
|
||||
def _is_already_configured(configuration_details):
|
||||
"""Returns `True` when alias already in shell config."""
|
||||
path = Path(configuration_details.path).expanduser()
|
||||
with path.open('r') as shell_config:
|
||||
return configuration_details.content in shell_config.read()
|
||||
|
||||
|
||||
def _configure(configuration_details):
|
||||
"""Adds alias to shell config."""
|
||||
path = Path(configuration_details.path).expanduser()
|
||||
with path.open('a') as shell_config:
|
||||
shell_config.write(u'\n')
|
||||
shell_config.write(configuration_details.content)
|
||||
shell_config.write(u'\n')
|
||||
|
||||
|
||||
def main():
|
||||
"""Shows useful information about how-to configure alias on a first run
|
||||
and configure automatically on a second.
|
||||
|
||||
It'll be only visible when user type fuck and when alias isn't configured.
|
||||
|
||||
"""
|
||||
settings.init()
|
||||
configuration_details = shell.how_to_configure()
|
||||
if (
|
||||
configuration_details and
|
||||
configuration_details.can_configure_automatically
|
||||
):
|
||||
if _is_already_configured(configuration_details):
|
||||
logs.already_configured(configuration_details)
|
||||
return
|
||||
elif _is_second_run():
|
||||
_configure(configuration_details)
|
||||
logs.configured_successfully(configuration_details)
|
||||
return
|
||||
else:
|
||||
_record_first_run()
|
||||
|
||||
logs.how_to_configure_alias(configuration_details)
|
||||
@@ -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:
|
||||
|
||||
@@ -54,8 +54,8 @@ def _brew_commands():
|
||||
brew_path_prefix = get_brew_path_prefix()
|
||||
if brew_path_prefix:
|
||||
try:
|
||||
return _get_brew_commands(brew_path_prefix) \
|
||||
+ _get_brew_tap_specific_commands(brew_path_prefix)
|
||||
return (_get_brew_commands(brew_path_prefix)
|
||||
+ _get_brew_tap_specific_commands(brew_path_prefix))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ def match(command):
|
||||
def get_new_command(command):
|
||||
return ' '.join(command.script_parts[1:])
|
||||
|
||||
|
||||
# it should be rare enough to actually have to type twice the same word, so
|
||||
# this rule can have a higher priority to come before things like "cd cd foo"
|
||||
priority = 900
|
||||
|
||||
@@ -40,6 +40,8 @@ def _make_pattern(pattern):
|
||||
.replace('{line}', '(?P<line>[0-9]+)') \
|
||||
.replace('{col}', '(?P<col>[0-9]+)')
|
||||
return re.compile(pattern, re.MULTILINE)
|
||||
|
||||
|
||||
patterns = [_make_pattern(p).search for p in patterns]
|
||||
|
||||
|
||||
|
||||
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')
|
||||
@@ -11,6 +11,7 @@ def match(command):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# git's output here is too complicated to be parsed (see the test file)
|
||||
stash_commands = (
|
||||
'apply',
|
||||
|
||||
@@ -6,12 +6,13 @@ from thefuck.specific.git import git_support
|
||||
@git_support
|
||||
def match(command):
|
||||
return (" is not a git command. See 'git --help'." in command.stderr
|
||||
and 'Did you mean' in command.stderr)
|
||||
and ('The most similar command' in command.stderr
|
||||
or 'Did you mean' in command.stderr))
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
|
||||
command.stderr)[0]
|
||||
matched = get_all_matched_commands(command.stderr)
|
||||
matched = get_all_matched_commands(command.stderr, ['The most similar command', 'Did you mean'])
|
||||
return replace_command(command, broken_cmd, matched)
|
||||
|
||||
14
thefuck/rules/git_push_without_commits.py
Normal file
14
thefuck/rules/git_push_without_commits.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import re
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
fix = u'git commit -m "Initial commit." && {command}'
|
||||
refspec_does_not_match = re.compile(r'src refspec \w+ does not match any\.')
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return bool(refspec_does_not_match.search(command.stderr))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return fix.format(command=command.script)
|
||||
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
|
||||
@@ -11,7 +11,7 @@ def match(command):
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
return shell.and_('git add .', 'git stash pop', 'git reset .')
|
||||
return shell.and_('git add --update', 'git stash pop', 'git reset .')
|
||||
|
||||
|
||||
# make it come before the other applicable rules
|
||||
|
||||
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')
|
||||
@@ -1,19 +1,11 @@
|
||||
import re
|
||||
from thefuck.utils import replace_command, for_app
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('heroku')
|
||||
def match(command):
|
||||
return 'is not a heroku command' in command.stderr and \
|
||||
'Perhaps you meant' in command.stderr
|
||||
|
||||
|
||||
def _get_suggests(stderr):
|
||||
for line in stderr.split('\n'):
|
||||
if 'Perhaps you meant' in line:
|
||||
return re.findall(r'`([^`]+)`', line)
|
||||
return 'Run heroku _ to run' in command.stderr
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
wrong = re.findall(r'`(\w+)` is not a heroku command', command.stderr)[0]
|
||||
return replace_command(command, wrong, _get_suggests(command.stderr))
|
||||
return re.findall('Run heroku _ to run ([^.]*)', command.stderr)[0]
|
||||
|
||||
27
thefuck/rules/hostscli.py
Normal file
27
thefuck/rules/hostscli.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import re
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.utils import replace_command, for_app
|
||||
|
||||
no_command = "Error: No such command"
|
||||
no_website = "hostscli.errors.WebsiteImportError"
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('hostscli')
|
||||
def match(command):
|
||||
errors = [no_command, no_website]
|
||||
for error in errors:
|
||||
if error in command.stderr:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command):
|
||||
if no_website in command.stderr:
|
||||
return ['hostscli websites']
|
||||
|
||||
misspelled_command = re.findall(
|
||||
r'Error: No such command ".*"', command.stderr)[0]
|
||||
commands = ['block', 'unblock', 'websites', 'block_all', 'unblock_all']
|
||||
return replace_command(command, misspelled_command, commands)
|
||||
@@ -1,6 +1,6 @@
|
||||
import subprocess
|
||||
from thefuck.utils import for_app, replace_command, eager
|
||||
import sys
|
||||
|
||||
|
||||
@for_app('ifconfig')
|
||||
def match(command):
|
||||
@@ -21,5 +21,3 @@ def get_new_command(command):
|
||||
interface = command.stderr.split(' ')[0][:-1]
|
||||
possible_interfaces = _get_possible_interfaces()
|
||||
return replace_command(command, interface, possible_interfaces)
|
||||
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ def match(command):
|
||||
def get_new_command(command):
|
||||
return u'man {}'.format(command.script[3:])
|
||||
|
||||
|
||||
priority = 2000
|
||||
|
||||
18
thefuck/rules/missing_space_before_subcommand.py
Normal file
18
thefuck/rules/missing_space_before_subcommand.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from thefuck.utils import get_all_executables, memoize
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_executable(script_part):
|
||||
for executable in get_all_executables():
|
||||
if script_part.startswith(executable):
|
||||
return executable
|
||||
|
||||
|
||||
def match(command):
|
||||
return (not command.script_parts[0] in get_all_executables()
|
||||
and _get_executable(command.script_parts[0]))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
executable = _get_executable(command.script_parts[0])
|
||||
return command.script.replace(executable, u'{} '.format(executable), 1)
|
||||
@@ -13,4 +13,5 @@ def get_new_command(command):
|
||||
return [formatme.format(pacman, package, command.script)
|
||||
for package in packages]
|
||||
|
||||
|
||||
enabled_by_default, pacman = archlinux_env()
|
||||
|
||||
53
thefuck/rules/path_from_history.py
Normal file
53
thefuck/rules/path_from_history.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from collections import Counter
|
||||
import re
|
||||
from thefuck.system import Path
|
||||
from thefuck.utils import (get_valid_history_without_current,
|
||||
memoize, replace_argument)
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
patterns = [r'no such file or directory: (.*)$',
|
||||
r"cannot access '(.*)': No such file or directory",
|
||||
r': (.*): No such file or directory',
|
||||
r"can't cd to (.*)$"]
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_destination(command):
|
||||
for pattern in patterns:
|
||||
found = re.findall(pattern, command.stderr)
|
||||
if found:
|
||||
if found[0] in command.script_parts:
|
||||
return found[0]
|
||||
|
||||
|
||||
def match(command):
|
||||
return bool(_get_destination(command))
|
||||
|
||||
|
||||
def _get_all_absolute_paths_from_history(command):
|
||||
counter = Counter()
|
||||
|
||||
for line in get_valid_history_without_current(command):
|
||||
splitted = shell.split_command(line)
|
||||
|
||||
for param in splitted[1:]:
|
||||
if param.startswith('/') or param.startswith('~'):
|
||||
if param.endswith('/'):
|
||||
param = param[:-1]
|
||||
|
||||
counter[param] += 1
|
||||
|
||||
return (path for path, _ in counter.most_common(None))
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
destination = _get_destination(command)
|
||||
paths = _get_all_absolute_paths_from_history(command)
|
||||
|
||||
return [replace_argument(command.script, destination, path)
|
||||
for path in paths if path.endswith(destination)
|
||||
and Path(path).expanduser().exists()]
|
||||
|
||||
|
||||
priority = 800
|
||||
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:])
|
||||
@@ -20,7 +20,9 @@ patterns = ['permission denied',
|
||||
'authentication is required',
|
||||
'edspermissionerror',
|
||||
'you don\'t have write permissions',
|
||||
'use `sudo`']
|
||||
'use `sudo`',
|
||||
'SudoRequiredError',
|
||||
'error: insufficient privileges']
|
||||
|
||||
|
||||
def match(command):
|
||||
|
||||
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))
|
||||
@@ -33,8 +33,8 @@ def match(command):
|
||||
if 'not found' not in command.stderr:
|
||||
return False
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return matched_layout and \
|
||||
_switch_command(command, matched_layout) != get_alias()
|
||||
return (matched_layout and
|
||||
_switch_command(command, matched_layout) != get_alias())
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
|
||||
@@ -3,8 +3,8 @@ from thefuck.utils import replace_command
|
||||
|
||||
|
||||
def match(command):
|
||||
return (re.search(r"([^:]*): Unknown command.*", command.stderr) != None
|
||||
and re.search(r"Did you mean ([^?]*)?", command.stderr) != None)
|
||||
return (re.search(r"([^:]*): Unknown command.*", command.stderr) is not None
|
||||
and re.search(r"Did you mean ([^?]*)?", command.stderr) is not None)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
|
||||
@@ -13,8 +13,9 @@ def get_new_command(command):
|
||||
if len(cmds) >= 3:
|
||||
machine = cmds[2]
|
||||
|
||||
startAllInstances = shell.and_("vagrant up", command.script)
|
||||
start_all_instances = shell.and_(u"vagrant up", command.script)
|
||||
if machine is None:
|
||||
return startAllInstances
|
||||
return start_all_instances
|
||||
else:
|
||||
return [shell.and_("vagrant up " + machine, command.script), startAllInstances]
|
||||
return [shell.and_(u"vagrant up {}".format(machine), command.script),
|
||||
start_all_instances]
|
||||
|
||||
@@ -26,7 +26,7 @@ def get_new_command(command):
|
||||
|
||||
available = _get_all_environments()
|
||||
if available:
|
||||
return replace_command(command, misspelled_env, available) \
|
||||
+ [create_new]
|
||||
return (replace_command(command, misspelled_env, available)
|
||||
+ [create_new])
|
||||
else:
|
||||
return create_new
|
||||
|
||||
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)
|
||||
38
thefuck/rules/yarn_command_not_found.py
Normal file
38
thefuck/rules/yarn_command_not_found.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
from thefuck.utils import for_app, eager, replace_command, replace_argument
|
||||
|
||||
regex = re.compile(r'error Command "(.*)" not found.')
|
||||
|
||||
|
||||
@for_app('yarn')
|
||||
def match(command):
|
||||
return regex.findall(command.stderr)
|
||||
|
||||
|
||||
npm_commands = {'require': 'add'}
|
||||
|
||||
|
||||
@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]
|
||||
if misspelled_task in npm_commands:
|
||||
yarn_command = npm_commands[misspelled_task]
|
||||
return replace_argument(command.script, misspelled_task, yarn_command)
|
||||
else:
|
||||
tasks = _get_all_tasks()
|
||||
return replace_command(command, misspelled_task, tasks)
|
||||
13
thefuck/rules/yarn_command_replaced.py
Normal file
13
thefuck/rules/yarn_command_replaced.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
regex = re.compile(r'Run "(.*)" instead')
|
||||
|
||||
|
||||
@for_app('yarn', at_least=1)
|
||||
def match(command):
|
||||
return regex.findall(command.stderr)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return regex.findall(command.stderr)[0]
|
||||
17
thefuck/rules/yarn_help.py
Normal file
17
thefuck/rules/yarn_help.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.system import open_command
|
||||
|
||||
|
||||
@for_app('yarn', at_least=2)
|
||||
def match(command):
|
||||
return (command.script_parts[1] == 'help'
|
||||
and 'for documentation about this command.' in command.stdout)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
url = re.findall(
|
||||
r'Visit ([^ ]*) for documentation about this command.',
|
||||
command.stdout)[0]
|
||||
|
||||
return open_command(url)
|
||||
@@ -1,22 +1,31 @@
|
||||
import os
|
||||
from ..conf import settings
|
||||
from ..const import ARGUMENT_PLACEHOLDER
|
||||
from ..utils import memoize
|
||||
from .generic import Generic
|
||||
|
||||
|
||||
class Bash(Generic):
|
||||
def app_alias(self, fuck):
|
||||
# It is VERY important to have the variables declared WITHIN the alias
|
||||
alias = "alias {0}='TF_CMD=$(TF_ALIAS={0}" \
|
||||
" PYTHONIOENCODING=utf-8" \
|
||||
" TF_SHELL_ALIASES=$(alias)" \
|
||||
" thefuck $(fc -ln -1)) &&" \
|
||||
" eval $TF_CMD".format(fuck)
|
||||
|
||||
if settings.alter_history:
|
||||
return alias + "; history -s $TF_CMD'"
|
||||
else:
|
||||
return alias + "'"
|
||||
def app_alias(self, alias_name):
|
||||
# It is VERY important to have the variables declared WITHIN the function
|
||||
return '''
|
||||
function {name} () {{
|
||||
TF_PREVIOUS=$(fc -ln -1);
|
||||
TF_PYTHONIOENCODING=$PYTHONIOENCODING;
|
||||
export TF_ALIAS={name};
|
||||
export TF_SHELL_ALIASES=$(alias);
|
||||
export PYTHONIOENCODING=utf-8;
|
||||
TF_CMD=$(
|
||||
thefuck $TF_PREVIOUS {argument_placeholder} $@
|
||||
) && eval $TF_CMD;
|
||||
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
|
||||
{alter_history}
|
||||
}}
|
||||
'''.format(
|
||||
name=alias_name,
|
||||
argument_placeholder=ARGUMENT_PLACEHOLDER,
|
||||
alter_history=('history -s $TF_CMD;'
|
||||
if settings.alter_history else ''))
|
||||
|
||||
def _parse_alias(self, alias):
|
||||
name, value = alias.replace('alias ', '', 1).split('=', 1)
|
||||
@@ -41,7 +50,11 @@ class Bash(Generic):
|
||||
if os.path.join(os.path.expanduser('~'), '.bashrc'):
|
||||
config = '~/.bashrc'
|
||||
elif os.path.join(os.path.expanduser('~'), '.bash_profile'):
|
||||
config = '~/.bashrc'
|
||||
config = '~/.bash_profile'
|
||||
else:
|
||||
config = 'bash config'
|
||||
return 'eval $(thefuck --alias)', config
|
||||
|
||||
return self._create_shell_configuration(
|
||||
content=u'eval $(thefuck --alias)',
|
||||
path=config,
|
||||
reload=u'source {}'.format(config))
|
||||
|
||||
@@ -66,9 +66,14 @@ class Fish(Generic):
|
||||
def and_(self, *commands):
|
||||
return u'; and '.join(commands)
|
||||
|
||||
def or_(self, *commands):
|
||||
return u'; or '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return (r"eval (thefuck --alias | tr '\n' ';')",
|
||||
'~/.config/fish/config.fish')
|
||||
return self._create_shell_configuration(
|
||||
content=u"eval (thefuck --alias | tr '\n' ';')",
|
||||
path='~/.config/fish/config.fish',
|
||||
reload='fish')
|
||||
|
||||
def put_to_history(self, command):
|
||||
try:
|
||||
|
||||
@@ -2,8 +2,14 @@ import io
|
||||
import os
|
||||
import shlex
|
||||
import six
|
||||
from collections import namedtuple
|
||||
from ..utils import memoize
|
||||
from ..conf import settings
|
||||
from ..system import Path
|
||||
|
||||
|
||||
ShellConfiguration = namedtuple('ShellConfiguration', (
|
||||
'content', 'path', 'reload', 'can_configure_automatically'))
|
||||
|
||||
|
||||
class Generic(object):
|
||||
@@ -60,13 +66,21 @@ class Generic(object):
|
||||
def and_(self, *commands):
|
||||
return u' && '.join(commands)
|
||||
|
||||
def or_(self, *commands):
|
||||
return u' || '.join(commands)
|
||||
|
||||
def how_to_configure(self):
|
||||
return
|
||||
|
||||
def split_command(self, command):
|
||||
"""Split the command using shell-like syntax."""
|
||||
encoded = self.encode_utf8(command)
|
||||
splitted = shlex.split(encoded)
|
||||
|
||||
try:
|
||||
splitted = [s.replace("??", "\ ") for s in shlex.split(encoded.replace('\ ', '??'))]
|
||||
except ValueError:
|
||||
splitted = encoded.split(' ')
|
||||
|
||||
return self.decode_utf8(splitted)
|
||||
|
||||
def encode_utf8(self, command):
|
||||
@@ -99,3 +113,22 @@ class Generic(object):
|
||||
all shells support it (Fish).
|
||||
|
||||
"""
|
||||
|
||||
def get_builtin_commands(self):
|
||||
"""Returns shells builtin commands."""
|
||||
return ['alias', 'bg', 'bind', 'break', 'builtin', 'case', 'cd',
|
||||
'command', 'compgen', 'complete', 'continue', 'declare',
|
||||
'dirs', 'disown', 'echo', 'enable', 'eval', 'exec', 'exit',
|
||||
'export', 'fc', 'fg', 'getopts', 'hash', 'help', 'history',
|
||||
'if', 'jobs', 'kill', 'let', 'local', 'logout', 'popd',
|
||||
'printf', 'pushd', 'pwd', 'read', 'readonly', 'return', 'set',
|
||||
'shift', 'shopt', 'source', 'suspend', 'test', 'times', 'trap',
|
||||
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
|
||||
'until', 'wait', 'while']
|
||||
|
||||
def _create_shell_configuration(self, content, path, reload):
|
||||
return ShellConfiguration(
|
||||
content=content,
|
||||
path=path,
|
||||
reload=reload,
|
||||
can_configure_automatically=Path(path).expanduser().exists())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user