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

Compare commits

...

85 Commits
2.5.4 ... 2.8

Author SHA1 Message Date
nvbn
5ab2cf646e Bump to 2.8 2015-08-24 20:31:28 +03:00
Vladimir Iakovlev
c5b4628c5c Update README.md 2015-08-24 20:27:11 +03:00
nvbn
d4cec3e850 Update readme 2015-08-24 20:25:59 +03:00
nvbn
564638c171 Add experimental installation script 2015-08-24 20:25:03 +03:00
nvbn
c6171a85e9 Improve readme test 2015-08-24 11:25:49 +03:00
Vladimir Iakovlev
ee610032b8 Merge pull request #345 from mcarton/slow
Fix slowness problems
2015-08-24 11:19:48 +03:00
mcarton
6754ebe20d Fix some spelling mistakes 2015-08-23 21:27:38 +02:00
mcarton
15d19fdf9c Fix slowness problems
The `get_aliases()` function was also sometimes called through
`shell.get_aliases()`.
Since the `history` rule also uses aliases, the shell was always
executed twice to get the aliases list.
2015-08-23 21:22:54 +02:00
Vladimir Iakovlev
967e30d914 Merge pull request #344 from trolley/typo-fix
Fix typo in alias warning
2015-08-23 16:39:31 +03:00
Mark Trolley
38ee31ebcb Fix typo in alias warning
Fixes a small typo in the deprecated alias warning.
2015-08-22 10:01:24 -04:00
Vladimir Iakovlev
7315958ea9 Merge pull request #343 from mlk/hdfs-rm-rm_X_Is_a_directory_add_minus_r
hdfs -rm -r /directory and hdfs -mkdir -p /directory/sub support
2015-08-21 18:34:19 +03:00
Vladimir Iakovlev
cce25b1ea4 Merge pull request #342 from mlk/322_vagrant
Issue: 322 Runs vagrant up when required
2015-08-21 18:33:42 +03:00
Michael Lee
1a57ef03c8 Removed additional assert 2015-08-21 16:06:12 +01:00
Michael Lee
298c04f89c Support for hdfs dfs -mkdir -p /directory/subdirectory 2015-08-21 16:05:49 +01:00
Michael Lee
42a8b4f639 Support for hdfs dfs -rm /directory 2015-08-21 15:48:54 +01:00
Michael Lee
336d8b7b4b Style change 2015-08-20 11:37:49 +01:00
Michael Lee
feb3eee2a0 Support for either starting only the machine requested, or starting all machines 2015-08-20 10:06:41 +01:00
Michael Lee
7cb0388ed0 Not matched unit tests, code style. 2015-08-20 09:41:01 +01:00
Michael Lee
004c0d06eb starts up vagrant if not already running 2015-08-19 17:12:54 +01:00
Vladimir Iakovlev
abbbd1f8eb Merge pull request #339 from mcarton/fix-338
Fix #338
2015-08-19 17:19:38 +03:00
Vladimir Iakovlev
700d9ac7e9 Merge pull request #337 from mcarton/cleanup
Some cleanup and fixes
2015-08-19 17:19:08 +03:00
Vladimir Iakovlev
8037a17b73 Merge pull request #333 from mlk/master
basic support for the hdfs dfs <command> when the command misses the …
2015-08-19 17:16:54 +03:00
mcarton
49917ce6b4 Fix #338 2015-08-19 11:00:27 +02:00
mcarton
c5e1139879 Fix some issues reported by pylint 2015-08-17 17:19:16 +02:00
mcarton
1becd92b12 Fix the open rule
It was simply wrong with `xdg-`, `gnome-` and `kde-open`.
2015-08-17 16:22:05 +02:00
mcarton
bc6b107066 Fix README and add a test so it won't happen again 2015-08-17 16:07:24 +02:00
mcarton
9b30ae0424 Handle columns it the fix_file rule 2015-08-17 16:07:24 +02:00
mcarton
88831c424f Fix the @wrap_settings annotation
It seems much more useful if it only adds settings that are not already
set.
2015-08-17 16:07:24 +02:00
mcarton
4a2f869c6d Add support for stdout in the fix_file rule
At least `pep8` and `py.test` consider errors as normal and print them
on stdout.
2015-08-17 16:07:24 +02:00
mcarton
7f0f9a966f Fix some pep8 issues, mostly spaces
Before:
    4       E101 indentation contains mixed spaces and tabs
    20      E122 continuation line missing indentation or outdented
    1       E124 closing bracket does not match visual indentation
    12      E127 continuation line over-indented for visual indent
    22      E128 continuation line under-indented for visual indent
    2       E211 whitespace before '('
    12      E302 expected 2 blank lines, found 1
    1       E303 too many blank lines (3)
    4       E402 module level import not at top of file
    123     E501 line too long (81 > 79 characters)
    2       E731 do not assign a lambda expression, use a def
    3       W191 indentation contains tabs
    20      W291 trailing whitespace
    3       W293 blank line contains whitespace
    2       W391 blank line at end of file
    69      W503 line break before binary operator

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

View File

@@ -12,6 +12,8 @@ addons:
- zsh
- fish
- tcsh
- pandoc
- git
env:
- FUNCTIONAL=true BARE=true
install:

View File

@@ -1,4 +1,4 @@
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg)](https://travis-ci.org/nvbn/thefuck)
# The Fuck [![Build Status](https://travis-ci.org/nvbn/thefuck.svg?branch=master)](https://travis-ci.org/nvbn/thefuck)
Magnificent app which corrects your previous console command,
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
@@ -14,7 +14,7 @@ E: Could not open lock file /var/lib/dpkg/lock - open (13: Permission denied)
E: Unable to lock the administration directory (/var/lib/dpkg/), are you root?
➜ fuck
sudo apt-get install vim [enter/ctrl+c]
sudo apt-get install vim [enter/↑/↓/ctrl+c]
[sudo] password for nvbn:
Reading package lists... Done
...
@@ -29,7 +29,7 @@ To push the current branch and set the remote as upstream, use
➜ fuck
git push --set-upstream origin master [enter/ctrl+c]
git push --set-upstream origin master [enter/↑/↓/ctrl+c]
Counting objects: 9, done.
...
```
@@ -42,7 +42,7 @@ No command 'puthon' found, did you mean:
zsh: command not found: puthon
➜ fuck
python [enter/ctrl+c]
python [enter/↑/↓/ctrl+c]
Python 3.4.2 (default, Oct 8 2014, 13:08:17)
...
```
@@ -55,7 +55,7 @@ Did you mean this?
branch
➜ fuck
git branch [enter/ctrl+c]
git branch [enter/↑/↓/ctrl+c]
* master
```
@@ -67,7 +67,7 @@ Did you mean this?
repl
➜ fuck
lein repl [enter/ctrl+c]
lein repl [enter/↑/↓/ctrl+c]
nREPL server started on port 54848 on host 127.0.0.1 - nrepl://127.0.0.1:54848
REPL-y 0.3.1
...
@@ -94,7 +94,15 @@ Reading package lists... Done
- pip
- python-dev
## Installation
## Installation [*experimental*]
On Ubuntu and OS X you can install `The Fuck` with installation script:
```bash
wget -O - https://raw.githubusercontent.com/nvbn/thefuck/master/install.sh | sh -
```
## Manual installation
Install `The Fuck` with `pip`:
@@ -107,9 +115,9 @@ sudo pip install thefuck
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
```bash
eval "$(thefuck-alias)"
eval "$(thefuck --alias)"
# You can use whatever you want as an alias, like for Mondays:
eval "$(thefuck-alias FUCK)"
eval "$(thefuck --alias FUCK)"
```
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
@@ -146,6 +154,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `docker_not_command` &ndash; fixes wrong docker commands like `docker tags`;
* `dry` &ndash; fixes repetitions like `git git push`;
* `fix_alt_space` &ndash; replaces Alt+Space with Space character;
* `fix_file` &ndash; opens a file with an error in your `$EDITOR`;
* `git_add` &ndash; fixes *"Did you forget to 'git add'?"*;
* `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
* `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
@@ -162,7 +171,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `grep_recursive` &ndash; adds `-r` when you trying to `grep` directory;
* `gulp_not_task` &ndash; fixes misspelled gulp tasks;
* `has_exists_script` &ndash; prepends `./` when script/binary exists;
* `heroku_no_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
* `history` &ndash; tries to replace command with most similar command from history;
* `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files;
@@ -184,11 +193,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
* `sl_ls` &ndash; changes `sl` to `ls`;
* `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
* `switch_layout` &ndash; switches command from your local layout to en;
* `switch_lang` &ndash; switches command from your local layout to en;
* `systemctl` &ndash; correctly orders parameters of confusing `systemctl`;
* `test.py` &ndash; runs `py.test` instead of `test.py`;
* `tsuru_login` &ndash; runs `tsuru login` if not authenticated or session expired;
* `tsuru_not_command` &ndash; fixes wrong `tsuru` commands like `tsuru shell`;
* `tmux` &ndash; fixes `tmux` commands;
* `unknown_command` &ndash; fixes hadoop hdfs-style "unknown command" for example adds missing '-' to the command on `hdfs dfs ls`;
* `vagrant_up` &ndash; starts up the vagrant instance;
* `whois` &ndash; fixes `whois` command.
Enabled by default only on specific platforms:
@@ -197,7 +209,8 @@ Enabled by default only on specific platforms:
* `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
* `brew_upgrade` &ndash; appends `--all` to `brew upgrade` as per Homebrew's new behaviour;
* `pacman` &ndash; installs app with `pacman` or `yaourt` if it is not installed.
* `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yaourt` if available).
* `pacman_not_found` &ndash; fix package name with `pacman` or `yaourt`;
Bundled, but not enabled by default:
@@ -211,10 +224,14 @@ in `~/.thefuck/rules`. The rule should contain two functions:
```python
match(command: Command, settings: Settings) -> bool
get_new_command(command: Command, settings: Settings) -> str
get_new_command(command: Command, settings: Settings) -> str | list[str]
```
Also the rule can contain an optional function `side_effect(command: Command, settings: Settings) -> None`
Also the rule can contain an optional function
```python
side_effect(old_command: Command, fixed_command: str, settings: Settings) -> None
```
and optional `enabled_by_default`, `requires_output` and `priority` variables.
`Command` has three attributes: `script`, `stdout` and `stderr`.

35
install.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/sh
# Install os dependencies:
if [ -f $(which apt-get) ]; then
sudo apt-get install python-pip
else
if [ -f $(which brew) ]; then
brew install python
fi
fi
# thefuck requires fresh versions of setuptools and pip:
sudo pip install -U pip setuptools
sudo pip install -U thefuck
# Setup aliases:
if [ -f ~/.bashrc ]; then
echo 'eval $(thefuck --alias)' >> ~/.bashrc
fi
if [ -f ~/.bash_profile ]; then
echo 'eval $(thefuck --alias)' >> ~/.bash_profile
fi
if [ -f ~/.zshrc ]; then
echo 'eval $(thefuck --alias)' >> ~/.zshrc
fi
if [ -f ~/.config/fish/config.fish ]; then
thefuck --alias >> ~/.config/fish/config.fish
fi
if [ -f ~/.tcshrc ]; then
echo 'eval `thefuck --alias`' >> ~/.tcshrc
fi

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python
from subprocess import call
import os
import re
@@ -28,4 +29,7 @@ call('git commit -am "Bump to {}"'.format(version), shell=True)
call('git tag {}'.format(version), shell=True)
call('git push', shell=True)
call('git push --tags', shell=True)
call('python setup.py sdist bdist_wheel upload', shell=True)
env = os.environ
env['CONVERT_README'] = 'true'
call('python setup.py sdist bdist_wheel upload', shell=True, env=env)

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
import sys
import os
try:
if os.environ.get('CONVERT_README'):
import pypandoc
long_description = pypandoc.convert('README.md', 'rst')
except:
long_description = open('README.md').read()
else:
long_description = ''
version = sys.version_info[:2]
if version < (2, 7):
@@ -19,7 +20,7 @@ elif (3, 0) < version < (3, 3):
' ({}.{} detected).'.format(*version))
sys.exit(-1)
VERSION = '2.5.4'
VERSION = '2.8'
install_requires = ['psutil', 'colorama', 'six']
extras_require = {':python_version<"3.4"': ['pathlib']}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import os
from contextlib import contextmanager
import subprocess
import shutil
from tempfile import mkdtemp
@@ -15,16 +14,17 @@ enabled = os.environ.get('FUNCTIONAL')
def build_container(tag, dockerfile):
tmpdir = mkdtemp()
with Path(tmpdir).joinpath('Dockerfile').open('w') as file:
file.write(dockerfile)
if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir],
cwd=root) != 0:
raise Exception("Can't build a container")
shutil.rmtree(tmpdir)
try:
with Path(tmpdir).joinpath('Dockerfile').open('w') as file:
file.write(dockerfile)
if subprocess.call(['docker', 'build', '--tag={}'.format(tag), tmpdir],
cwd=root) != 0:
raise Exception("Can't build a container")
finally:
shutil.rmtree(tmpdir)
@contextmanager
def spawn(tag, dockerfile, cmd):
def spawn(request, tag, dockerfile, cmd):
if bare:
proc = pexpect.spawnu(cmd)
else:
@@ -33,13 +33,12 @@ def spawn(tag, dockerfile, cmd):
proc = pexpect.spawnu('docker run --volume {}:/src --tty=true '
'--interactive=true {} {}'.format(root, tag, cmd))
proc.sendline('pip install /src')
proc.sendline('cd /')
proc.logfile = sys.stdout
try:
yield proc
finally:
proc.terminate(force=bare)
request.addfinalizer(proc.terminate)
return proc
def images(*items):

View File

@@ -16,6 +16,8 @@ def test_match(command):
@pytest.mark.parametrize('command, return_value', [
(Command(script='vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command(script='sudo vim', stderr='vim: command not found'),
[('vim', 'main'), ('vim-tiny', 'main')])])
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
@patch.multiple(apt_get, create=True, apt_get='apt_get')
@@ -38,7 +40,9 @@ def test_not_match(command):
reason='Skip if python-commandnotfound is not available')
@pytest.mark.parametrize('command, new_command', [
(Command('vim'), 'sudo apt-get install vim && vim'),
(Command('convert'), 'sudo apt-get install imagemagick && convert')])
(Command('convert'), 'sudo apt-get install imagemagick && convert'),
(Command('sudo vim'), 'sudo apt-get install vim && sudo vim'),
(Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command
@@ -47,6 +51,11 @@ def test_get_new_command(command, new_command):
(Command('vim'), 'sudo apt-get install vim && vim',
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command('convert'), 'sudo apt-get install imagemagick && convert',
[('imagemagick', 'main'),
('graphicsmagick-imagemagick-compat', 'universe')]),
(Command('sudo vim'), 'sudo apt-get install vim && sudo vim',
[('vim', 'main'), ('vim-tiny', 'main')]),
(Command('sudo convert'), 'sudo apt-get install imagemagick && sudo convert',
[('imagemagick', 'main'),
('graphicsmagick-imagemagick-compat', 'universe')])])
@patch('thefuck.rules.apt_get.CommandNotFound', create=True)
@@ -55,5 +64,3 @@ def test_get_new_command_mocked(cmdnf_mock, command, new_command, return_value):
get_packages = Mock(return_value=return_value)
cmdnf_mock.CommandNotFound.return_value = Mock(getPackages=get_packages)
assert get_new_command(command, None) == new_command
assert cmdnf_mock.CommandNotFound.called
assert get_packages.called

View File

@@ -22,7 +22,7 @@ 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),
None) == 'brew list'
None) == ['brew list', 'brew install', 'brew uninstall']
assert get_new_command(Command('brew instaa', stderr=brew_unknown_cmd2),
None) == 'brew install'
None) == ['brew install', 'brew uninstall', 'brew list']

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ from thefuck import shells
from thefuck.rules.git_branch_list import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('git branch list'), None)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ def is_not_task():
Did you mean this?
repl
jar
'''
@@ -19,4 +20,4 @@ def test_match(is_not_task):
def test_get_new_command(is_not_task):
assert get_new_command(Mock(script='lein rpl --help', stderr=is_not_task),
None) == 'lein repl --help'
None) == ['lein repl --help', 'lein jar --help']

View File

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

View File

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

View File

@@ -3,20 +3,29 @@ from thefuck.rules.mkdir_p import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('mkdir foo/bar/baz',
stderr='mkdir: foo/bar: No such file or directory'),
None)
@pytest.mark.parametrize('command', [
Command('mkdir foo/bar/baz', stderr='mkdir: foo/bar: No such file or directory'),
Command('./bin/hdfs dfs -mkdir foo/bar/baz', stderr='mkdir: `foo/bar/baz\': No such file or directory'),
Command('hdfs dfs -mkdir foo/bar/baz', stderr='mkdir: `foo/bar/baz\': No such file or directory')
])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command('mkdir foo/bar/baz'),
Command('mkdir foo/bar/baz', stderr='foo bar baz'),
Command('hdfs dfs -mkdir foo/bar/baz'),
Command('./bin/hdfs dfs -mkdir foo/bar/baz'),
Command()])
def test_not_match(command):
assert not match(command, None)
def test_get_new_command():
assert get_new_command(Command('mkdir foo/bar/baz'), None)\
== 'mkdir -p foo/bar/baz'
@pytest.mark.parametrize('command, new_command', [
(Command('mkdir foo/bar/baz'), 'mkdir -p foo/bar/baz'),
(Command('hdfs dfs -mkdir foo/bar/baz'), 'hdfs dfs -mkdir -p foo/bar/baz'),
(Command('./bin/hdfs dfs -mkdir foo/bar/baz'), './bin/hdfs dfs -mkdir -p foo/bar/baz')])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

View File

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

View File

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

View File

@@ -7,17 +7,14 @@ from tests.utils import Command
pacman_cmd = getattr(pacman, 'pacman', 'pacman')
PKGFILE_OUTPUT_CONVERT = '''
extra/imagemagick 6.9.1.0-1\t/usr/bin/convert
'''
PKGFILE_OUTPUT_SUDO = 'core/sudo 1.8.13-13/usr/bin/sudo'
PKGFILE_OUTPUT_CONVERT = 'extra/imagemagick 6.9.1.0-1\t/usr/bin/convert'
PKGFILE_OUTPUT_VIM = '''
extra/gvim 7.4.712-1 \t/usr/bin/vim
PKGFILE_OUTPUT_VIM = '''extra/gvim 7.4.712-1 \t/usr/bin/vim
extra/gvim-python3 7.4.712-1\t/usr/bin/vim
extra/vim 7.4.712-1 \t/usr/bin/vim
extra/vim-minimal 7.4.712-1 \t/usr/bin/vim
extra/vim-python3 7.4.712-1 \t/usr/bin/vim
'''
extra/vim-python3 7.4.712-1 \t/usr/bin/vim'''
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
@@ -32,7 +29,7 @@ def test_match(command):
@pytest.mark.parametrize('command, return_value', [
(Command(script='vim', stderr='vim: command not found'), PKGFILE_OUTPUT_VIM),
(Command(script='sudo vim', stderr='sudo: vim: command not found'), PKGFILE_OUTPUT_VIM)])
@patch('thefuck.rules.pacman.subprocess')
@patch('thefuck.archlinux.subprocess')
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
def test_match_mocked(subp_mock, command, return_value):
subp_mock.check_output.return_value = return_value
@@ -46,23 +43,39 @@ def test_not_match(command):
assert not match(command, None)
sudo_vim_possibilities = ['{} -S extra/gvim && sudo vim',
'{} -S extra/gvim-python3 && sudo vim',
'{} -S extra/vim && sudo vim',
'{} -S extra/vim-minimal && sudo vim',
'{} -S extra/vim-python3 && sudo vim']
sudo_vim_possibilities = [s.format(pacman_cmd) for s in sudo_vim_possibilities]
vim_possibilities = ['{} -S extra/gvim && vim',
'{} -S extra/gvim-python3 && vim',
'{} -S extra/vim && vim',
'{} -S extra/vim-minimal && vim',
'{} -S extra/vim-python3 && vim']
vim_possibilities = [s.format(pacman_cmd) for s in vim_possibilities]
@pytest.mark.skipif(not getattr(pacman, 'enabled_by_default', True),
reason='Skip if pacman is not available')
@pytest.mark.parametrize('command, new_command', [
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd)),
(Command('sudo vim'), '{} -S extra/gvim && sudo vim'.format(pacman_cmd)),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd)),
(Command('sudo convert'), '{} -S extra/imagemagick && sudo convert'.format(pacman_cmd))])
(Command('vim'), vim_possibilities),
(Command('sudo vim'), sudo_vim_possibilities),
(Command('convert'), ['{} -S extra/imagemagick && convert'.format(pacman_cmd)]),
(Command('sudo convert'), ['{} -S extra/imagemagick && sudo convert'.format(pacman_cmd)])])
def test_get_new_command(command, new_command, mocker):
assert get_new_command(command, None) == new_command
@pytest.mark.parametrize('command, new_command, return_value', [
(Command('vim'), '{} -S extra/gvim && vim'.format(pacman_cmd), PKGFILE_OUTPUT_VIM),
(Command('sudo vim'), '{} -S extra/gvim && sudo vim'.format(pacman_cmd), PKGFILE_OUTPUT_VIM),
(Command('convert'), '{} -S extra/imagemagick && convert'.format(pacman_cmd), PKGFILE_OUTPUT_CONVERT),
(Command('sudo convert'), '{} -S extra/imagemagick && sudo convert'.format(pacman_cmd), PKGFILE_OUTPUT_CONVERT)])
@patch('thefuck.rules.pacman.subprocess')
(Command('vim'), vim_possibilities, PKGFILE_OUTPUT_VIM),
(Command('sudo vim'), sudo_vim_possibilities, PKGFILE_OUTPUT_VIM),
(Command('convert'), ['{} -S extra/imagemagick && convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT),
(Command('sudo'), ['{} -S core/sudo && sudo'.format(pacman_cmd)], PKGFILE_OUTPUT_SUDO),
(Command('sudo convert'), ['{} -S extra/imagemagick && sudo convert'.format(pacman_cmd)], PKGFILE_OUTPUT_CONVERT)])
@patch('thefuck.archlinux.subprocess')
@patch.multiple(pacman, create=True, pacman=pacman_cmd)
def test_get_new_command_mocked(subp_mock, command, new_command, return_value):
subp_mock.check_output.return_value = return_value

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ from thefuck.rules.systemctl import match, get_new_command
from tests.utils import Command
def test_match():
assert match(Command('systemctl nginx start', stderr='Unknown operation \'nginx\'.'), None)
assert match(Command('sudo systemctl nginx start', stderr='Unknown operation \'nginx\'.'), None)
@@ -13,6 +12,7 @@ def test_match():
assert not match(Command('systemctl nginx', stderr='Unknown operation \'nginx\'.'), None)
assert not match(Command('systemctl start wtf', stderr='Failed to start wtf.service: Unit wtf.service failed to load: No such file or directory.'), None)
def test_get_new_command():
assert get_new_command(Command('systemctl nginx start'), None) == "systemctl start nginx"
assert get_new_command(Command('sudo systemctl nginx start'), None) == "sudo systemctl start nginx"

View File

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

View File

@@ -0,0 +1,90 @@
import pytest
from tests.utils import Command
from thefuck.rules.tsuru_not_command import match, get_new_command
@pytest.mark.parametrize('command', [
Command('tsuru log', stderr=(
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-log\n'
'\tlogin\n'
'\tlogout\n'
)),
Command('tsuru app-l', stderr=(
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-list\n'
'\tapp-log\n'
)),
Command('tsuru user-list', stderr=(
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tteam-user-list\n'
)),
Command('tsuru targetlist', stderr=(
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\ttarget-list\n'
)),
])
def test_match(command):
assert match(command, None)
@pytest.mark.parametrize('command', [
Command('tsuru tchururu', stderr=(
'tsuru: "tchururu" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
)),
Command('tsuru version', stderr='tsuru version 0.16.0.'),
Command('tsuru help', stderr=(
'tsuru version 0.16.0.\n'
'\nUsage: tsuru command [args]\n'
)),
Command('tsuru platform-list', stderr=(
'- java\n'
'- logstashgiro\n'
'- newnode\n'
'- nodejs\n'
'- php\n'
'- python\n'
'- python3\n'
'- ruby\n'
'- ruby20\n'
'- static\n'
)),
Command('tsuru env-get', stderr='Error: App thefuck not found.'),
])
def test_not_match(command):
assert not match(command, None)
@pytest.mark.parametrize('command, new_commands', [
(Command('tsuru log', stderr=(
'tsuru: "log" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-log\n'
'\tlogin\n'
'\tlogout\n'
)), ['tsuru login', 'tsuru logout', 'tsuru app-log']),
(Command('tsuru app-l', stderr=(
'tsuru: "app-l" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tapp-list\n'
'\tapp-log\n'
)), ['tsuru app-log', 'tsuru app-list']),
(Command('tsuru user-list', stderr=(
'tsuru: "user-list" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\tteam-user-list\n'
)), ['tsuru team-user-list']),
(Command('tsuru targetlist', stderr=(
'tsuru: "targetlist" is not a tsuru command. See "tsuru help".\n'
'\nDid you mean?\n'
'\ttarget-list\n'
)), ['tsuru target-list']),
])
def test_get_new_command(command, new_commands):
assert get_new_command(command, None) == new_commands

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ from tests.utils import Command
@pytest.mark.parametrize('command', [
Command(script='whois https://en.wikipedia.org/wiki/Main_Page'),
Command(script='whois https://en.wikipedia.org/'),
Command(script='whois en.wikipedia.org')])
Command(script='whois meta.unix.stackexchange.com')])
def test_match(command):
assert match(command, None)
@@ -15,9 +15,12 @@ def test_not_match():
assert not match(Command(script='whois'), None)
# `whois com` actually makes sense
@pytest.mark.parametrize('command, new_command', [
(Command('whois https://en.wikipedia.org/wiki/Main_Page'), 'whois en.wikipedia.org'),
(Command('whois https://en.wikipedia.org/'), 'whois en.wikipedia.org'),
(Command('whois en.wikipedia.org'), 'whois wikipedia.org')])
(Command('whois meta.unix.stackexchange.com'), ['whois unix.stackexchange.com',
'whois stackexchange.com',
'whois com'])])
def test_get_new_command(command, new_command):
assert get_new_command(command, None) == new_command

97
tests/test_corrector.py Normal file
View File

@@ -0,0 +1,97 @@
import pytest
from pathlib import PosixPath, Path
from mock import Mock
from thefuck import corrector, conf, types
from tests.utils import Rule, Command, CorrectedCommand
from thefuck.corrector import make_corrected_commands, get_corrected_commands, remove_duplicates
def test_load_rule(mocker):
match = object()
get_new_command = object()
load_source = mocker.patch(
'thefuck.corrector.load_source',
return_value=Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True,
priority=900,
requires_output=True))
assert corrector.load_rule(Path('/rules/bash.py'), settings=Mock(priority={})) \
== Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py')
class TestGetRules(object):
@pytest.fixture(autouse=True)
def glob(self, mocker):
return mocker.patch('thefuck.corrector.Path.glob', return_value=[])
def _compare_names(self, rules, names):
return [r.name for r in rules] == names
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
(types.RulesNamesList(['bash']), ['bash', 'bash'])])
def test_get(self, monkeypatch, glob, conf_rules, rules):
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
monkeypatch.setattr('thefuck.corrector.load_source',
lambda x, _: Rule(x))
assert self._compare_names(
corrector.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
rules)
class TestGetMatchedRules(object):
def test_no_match(self):
assert list(corrector.get_matched_rules(
Command('ls'), [Rule('', lambda *_: False)],
Mock(no_colors=True))) == []
def test_match(self):
rule = Rule('', lambda x, _: x.script == 'cd ..')
assert list(corrector.get_matched_rules(
Command('cd ..'), [rule], Mock(no_colors=True))) == [rule]
def test_when_rule_failed(self, capsys):
all(corrector.get_matched_rules(
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')),
requires_output=False)],
Mock(no_colors=True, debug=False)))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
class TestGetCorrectedCommands(object):
def test_with_rule_returns_list(self):
rule = Rule(get_new_command=lambda x, _: [x.script + '!', x.script + '@'],
priority=100)
assert list(make_corrected_commands(Command(script='test'), [rule], None)) \
== [CorrectedCommand(script='test!', priority=100),
CorrectedCommand(script='test@', priority=200)]
def test_with_rule_returns_command(self):
rule = Rule(get_new_command=lambda x, _: x.script + '!',
priority=100)
assert list(make_corrected_commands(Command(script='test'), [rule], None)) \
== [CorrectedCommand(script='test!', priority=100)]
def test_remove_duplicates():
side_effect = lambda *_: None
assert set(remove_duplicates([CorrectedCommand('ls', priority=100),
CorrectedCommand('ls', priority=200),
CorrectedCommand('ls', side_effect, 300)])) \
== {CorrectedCommand('ls', priority=100),
CorrectedCommand('ls', side_effect, 300)}
def test_get_corrected_commands(mocker):
command = Command('test', 'test', 'test')
rules = [Rule(match=lambda *_: False),
Rule(match=lambda *_: True,
get_new_command=lambda x, _: x.script + '!', priority=100),
Rule(match=lambda *_: True,
get_new_command=lambda x, _: [x.script + '@', x.script + ';'],
priority=60)]
mocker.patch('thefuck.corrector.get_rules', return_value=rules)
assert [cmd.script for cmd in get_corrected_commands(command, None, Mock(debug=False))] \
== ['test@', 'test!', 'test;']

View File

@@ -1,61 +1,8 @@
import pytest
from subprocess import PIPE
from pathlib import PosixPath, Path
from mock import Mock
from thefuck import main, conf, types
from tests.utils import Rule, Command
def test_load_rule(mocker):
match = object()
get_new_command = object()
load_source = mocker.patch(
'thefuck.main.load_source',
return_value=Mock(match=match,
get_new_command=get_new_command,
enabled_by_default=True,
priority=900,
requires_output=True))
assert main.load_rule(Path('/rules/bash.py')) \
== Rule('bash', match, get_new_command, priority=900)
load_source.assert_called_once_with('bash', '/rules/bash.py')
class TestGetRules(object):
@pytest.fixture(autouse=True)
def glob(self, mocker):
return mocker.patch('thefuck.main.Path.glob', return_value=[])
def _compare_names(self, rules, names):
return [r.name for r in rules] == names
@pytest.mark.parametrize('conf_rules, rules', [
(conf.DEFAULT_RULES, ['bash', 'lisp', 'bash', 'lisp']),
(types.RulesNamesList(['bash']), ['bash', 'bash'])])
def test_get(self, monkeypatch, glob, conf_rules, rules):
glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')]
monkeypatch.setattr('thefuck.main.load_source',
lambda x, _: Rule(x))
assert self._compare_names(
main.get_rules(Path('~'), Mock(rules=conf_rules, priority={})),
rules)
@pytest.mark.parametrize('priority, unordered, ordered', [
({},
[Rule('bash', priority=100), Rule('python', priority=5)],
['python', 'bash']),
({},
[Rule('lisp', priority=9999), Rule('c', priority=conf.DEFAULT_PRIORITY)],
['c', 'lisp']),
({'python': 9999},
[Rule('bash', priority=100), Rule('python', priority=5)],
['bash', 'python'])])
def test_ordered_by_priority(self, monkeypatch, priority, unordered, ordered):
monkeypatch.setattr('thefuck.main._get_loaded_rules',
lambda *_: unordered)
assert self._compare_names(
main.get_rules(Path('~'), Mock(priority=priority)),
ordered)
from thefuck import main
from tests.utils import Command
class TestGetCommand(object):
@@ -79,7 +26,7 @@ class TestGetCommand(object):
def test_get_command_calls(self, Popen):
assert main.get_command(Mock(env={}),
['thefuck', 'apt-get', 'search', 'vim']) \
['thefuck', 'apt-get', 'search', 'vim']) \
== Command('apt-get search vim', 'stdout', 'stderr')
Popen.assert_called_once_with('apt-get search vim',
shell=True,
@@ -88,6 +35,8 @@ class TestGetCommand(object):
env={})
@pytest.mark.parametrize('args, result', [
(['thefuck', ''], None),
(['thefuck', '', ''], None),
(['thefuck', 'ls', '-la'], 'ls -la'),
(['thefuck', 'ls'], 'ls')])
def test_get_command_script(self, args, result):
@@ -95,80 +44,3 @@ class TestGetCommand(object):
assert main.get_command(Mock(env={}), args).script == result
else:
assert main.get_command(Mock(env={}), args) is None
class TestGetMatchedRule(object):
def test_no_match(self):
assert main.get_matched_rule(
Command('ls'), [Rule('', lambda *_: False)],
Mock(no_colors=True)) is None
def test_match(self):
rule = Rule('', lambda x, _: x.script == 'cd ..')
assert main.get_matched_rule(
Command('cd ..'), [rule], Mock(no_colors=True)) == rule
def test_when_rule_failed(self, capsys):
main.get_matched_rule(
Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))],
Mock(no_colors=True, debug=False))
assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:'
class TestRunRule(object):
@pytest.fixture(autouse=True)
def confirm(self, mocker):
return mocker.patch('thefuck.main.confirm', return_value=True)
def test_run_rule(self, capsys):
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
Command(), None)
assert capsys.readouterr() == ('new-command\n', '')
def test_run_rule_with_side_effect(self, capsys):
side_effect = Mock()
settings = Mock(debug=False)
command = Command()
main.run_rule(Rule(get_new_command=lambda *_: 'new-command',
side_effect=side_effect),
command, settings)
assert capsys.readouterr() == ('new-command\n', '')
side_effect.assert_called_once_with(command, settings)
def test_when_not_comfirmed(self, capsys, confirm):
confirm.return_value = False
main.run_rule(Rule(get_new_command=lambda *_: 'new-command'),
Command(), None)
assert capsys.readouterr() == ('', '')
class TestConfirm(object):
@pytest.fixture
def stdin(self, mocker):
return mocker.patch('sys.stdin.read', return_value='\n')
def test_when_not_required(self, capsys):
assert main.confirm('command', None, Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command\n')
def test_with_side_effect_and_without_confirmation(self, capsys):
assert main.confirm('command', Mock(), Mock(require_confirmation=False))
assert capsys.readouterr() == ('', 'command (+side effect)\n')
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed(self, capsys, stdin):
assert main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]')
# `stdin` fixture should be applied after `capsys`
def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin):
assert main.confirm('command', Mock(), Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command (+side effect) [enter/ctrl+c]')
def test_when_confirmation_required_and_aborted(self, capsys, stdin):
stdin.side_effect = KeyboardInterrupt
assert not main.confirm('command', None, Mock(require_confirmation=True,
no_colors=True))
assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n')

17
tests/test_readme.py Normal file
View File

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

View File

@@ -11,6 +11,7 @@ def test_rules_names_list():
def test_update_settings():
settings = Settings({'key': 'val'})
new_settings = settings.update(key='new-val')
assert new_settings.key == 'new-val'
new_settings = settings.update(key='new-val', unset='unset-value')
assert new_settings.key == 'val'
assert new_settings.unset == 'unset-value'
assert settings.key == 'val'

124
tests/test_ui.py Normal file
View File

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

View File

@@ -1,14 +1,16 @@
import pytest
from mock import Mock
from thefuck.utils import git_support, sudo_support, wrap_settings,\
memoize, get_closest, get_all_executables, replace_argument
memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands
from thefuck.types import Settings
from tests.utils import Command
@pytest.mark.parametrize('override, old, new', [
({'key': 'val'}, {}, {'key': 'val'}),
({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})])
({'key': 'new-val'}, {'key': 'val'}, {'key': 'val'}),
({'key': 'new-val', 'unset': 'unset'}, {'key': 'val'}, {'key': 'val', 'unset': 'unset'})])
def test_wrap_settings(override, old, new):
fn = lambda _, settings: settings
assert wrap_settings(override)(fn)(None, Settings(old)) == new
@@ -17,6 +19,7 @@ def test_wrap_settings(override, old, new):
@pytest.mark.parametrize('return_value, command, called, result', [
('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'),
('ls -lah', 'ls', 'ls', 'ls -lah'),
(['ls -lah'], 'sudo ls', 'ls', ['sudo ls -lah']),
(True, 'sudo ls', 'ls', True),
(True, 'ls', 'ls', True),
(False, 'sudo ls', 'ls', False),
@@ -99,3 +102,32 @@ def test_get_all_callables():
(('git brnch', 'brnch', 'branch'), 'git branch')])
def test_replace_argument(args, result):
assert replace_argument(*args) == result
@pytest.mark.parametrize('stderr, result', [
(("git: 'cone' is not a git command. See 'git --help'.\n"
'\n'
'Did you mean one of these?\n'
'\tclone'), ['clone']),
(("git: 're' is not a git command. See 'git --help'.\n"
'\n'
'Did you mean one of these?\n'
'\trebase\n'
'\treset\n'
'\tgrep\n'
'\trm'), ['rebase', 'reset', 'grep', 'rm']),
(('tsuru: "target" is not a tsuru command. See "tsuru help".\n'
'\n'
'Did you mean one of these?\n'
'\tservice-add\n'
'\tservice-bind\n'
'\tservice-doc\n'
'\tservice-info\n'
'\tservice-list\n'
'\tservice-remove\n'
'\tservice-status\n'
'\tservice-unbind'), ['service-add', 'service-bind', 'service-doc',
'service-info', 'service-list', 'service-remove',
'service-status', 'service-unbind'])])
def test_get_all_matched_commands(stderr, result):
assert list(get_all_matched_commands(stderr)) == result

View File

@@ -15,3 +15,7 @@ def Rule(name='', match=lambda *_: True,
return types.Rule(name, match, get_new_command,
enabled_by_default, side_effect,
priority, requires_output)
def CorrectedCommand(script='', side_effect=None, priority=DEFAULT_PRIORITY):
return types.CorrectedCommand(script, side_effect, priority)

41
thefuck/archlinux.py Normal file
View File

@@ -0,0 +1,41 @@
""" This file provide some utility functions for Arch Linux specific rules."""
import thefuck.utils
import subprocess
@thefuck.utils.memoize
def get_pkgfile(command):
""" Gets the packages that provide the given command using `pkgfile`.
If the command is of the form `sudo foo`, searches for the `foo` command
instead.
"""
try:
command = command.strip()
if command.startswith('sudo '):
command = command[5:]
command = command.split(" ")[0]
packages = subprocess.check_output(
['pkgfile', '-b', '-v', command],
universal_newlines=True, stderr=thefuck.utils.DEVNULL
).splitlines()
return [package.split()[0] for package in packages]
except subprocess.CalledProcessError:
return None
def archlinux_env():
if thefuck.utils.which('yaourt'):
pacman = 'yaourt'
elif thefuck.utils.which('pacman'):
pacman = 'sudo pacman'
else:
return False, None
enabled_by_default = thefuck.utils.which('pkgfile')
return enabled_by_default, pacman

87
thefuck/corrector.py Normal file
View File

@@ -0,0 +1,87 @@
import sys
from imp import load_source
from pathlib import Path
from . import conf, types, logs
from .utils import eager
def load_rule(rule, settings):
"""Imports rule module and returns it."""
name = rule.name[:-3]
rule_module = load_source(name, str(rule))
priority = getattr(rule_module, 'priority', conf.DEFAULT_PRIORITY)
return types.Rule(name, rule_module.match,
rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True),
getattr(rule_module, 'side_effect', None),
settings.priority.get(name, priority),
getattr(rule_module, 'requires_output', True))
def get_loaded_rules(rules, settings):
"""Yields all available rules."""
for rule in rules:
if rule.name != '__init__.py':
loaded_rule = load_rule(rule, settings)
if loaded_rule in settings.rules:
yield loaded_rule
@eager
def get_rules(user_dir, settings):
"""Returns all enabled rules."""
bundled = Path(__file__).parent \
.joinpath('rules') \
.glob('*.py')
user = user_dir.joinpath('rules').glob('*.py')
return get_loaded_rules(sorted(bundled) + sorted(user), settings)
@eager
def get_matched_rules(command, rules, settings):
"""Returns first matched rule for command."""
script_only = command.stdout is None and command.stderr is None
for rule in rules:
if script_only and rule.requires_output:
continue
try:
with logs.debug_time(u'Trying rule: {};'.format(rule.name),
settings):
if rule.match(command, settings):
yield rule
except Exception:
logs.rule_failed(rule, sys.exc_info(), settings)
def make_corrected_commands(command, rules, settings):
for rule in rules:
new_commands = rule.get_new_command(command, settings)
if not isinstance(new_commands, list):
new_commands = [new_commands]
for n, new_command in enumerate(new_commands):
yield types.CorrectedCommand(script=new_command,
side_effect=rule.side_effect,
priority=(n + 1) * rule.priority)
def remove_duplicates(corrected_commands):
commands = {(command.script, command.side_effect): command
for command in sorted(corrected_commands,
key=lambda command: -command.priority)}
return commands.values()
def get_corrected_commands(command, user_dir, settings):
rules = get_rules(user_dir, settings)
logs.debug(
u'Loaded rules: {}'.format(', '.join(rule.name for rule in rules)),
settings)
matched = get_matched_rules(command, rules, settings)
logs.debug(
u'Matched rules: {}'.format(', '.join(rule.name for rule in matched)),
settings)
corrected_commands = make_corrected_commands(command, matched, settings)
return sorted(remove_duplicates(corrected_commands),
key=lambda corrected_command: corrected_command.priority)

View File

@@ -1,3 +1,5 @@
# -*- encoding: utf-8 -*-
from contextlib import contextmanager
from datetime import datetime
import sys
@@ -28,27 +30,6 @@ def rule_failed(rule, exc_info, settings):
exception('Rule {}'.format(rule.name), exc_info, settings)
def show_command(new_command, side_effect, settings):
sys.stderr.write('{bold}{command}{reset}{side_effect}\n'.format(
command=new_command,
side_effect=' (+side effect)' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
def confirm_command(new_command, side_effect, settings):
sys.stderr.write(
'{bold}{command}{reset}{side_effect} '
'[{green}enter{reset}/{red}ctrl+c{reset}]'.format(
command=new_command,
side_effect=' (+side effect)' if side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
green=color(colorama.Fore.GREEN, settings),
red=color(colorama.Fore.RED, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
sys.stderr.flush()
def failed(msg, settings):
sys.stderr.write('{red}{msg}{reset}\n'.format(
msg=msg,
@@ -56,6 +37,33 @@ def failed(msg, settings):
reset=color(colorama.Style.RESET_ALL, settings)))
def show_corrected_command(corrected_command, settings):
sys.stderr.write('{bold}{script}{reset}{side_effect}\n'.format(
script=corrected_command.script,
side_effect=' (+side effect)' if corrected_command.side_effect else '',
bold=color(colorama.Style.BRIGHT, settings),
reset=color(colorama.Style.RESET_ALL, settings)))
def confirm_text(corrected_command, multiple_cmds, settings):
if multiple_cmds:
arrows = '{blue}{reset}/{blue}{reset}/'
else:
arrows = ''
sys.stderr.write(
('{clear}{bold}{script}{reset}{side_effect} '
'[{green}enter{reset}/' + arrows + '{red}ctrl+c{reset}]').format(
script=corrected_command.script,
side_effect=' (+side effect)' if corrected_command.side_effect else '',
clear='\033[1K\r',
bold=color(colorama.Style.BRIGHT, settings),
green=color(colorama.Fore.GREEN, settings),
red=color(colorama.Fore.RED, settings),
reset=color(colorama.Style.RESET_ALL, settings),
blue=color(colorama.Fore.BLUE, settings)))
def debug(msg, settings):
if settings.debug:
sys.stderr.write(u'{blue}{bold}DEBUG:{reset} {msg}\n'.format(
@@ -71,4 +79,4 @@ def debug_time(msg, settings):
try:
yield
finally:
debug('{} took: {}'.format(msg, datetime.now() - started), settings)
debug(u'{} took: {}'.format(msg, datetime.now() - started), settings)

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import os
import re
import subprocess
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import get_closest, replace_command
BREW_CMD_PATH = '/Library/Homebrew/cmd'
TAP_PATH = '/Library/Taps'
@@ -77,10 +77,6 @@ if brew_path_prefix:
pass
def _get_similar_command(command):
return get_closest(command, brew_commands)
def match(command, settings):
is_proper_command = ('brew' in command.script and
'Unknown command' in command.stderr)
@@ -89,7 +85,7 @@ def match(command, settings):
if is_proper_command:
broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
command.stderr)[0]
has_possible_commands = bool(_get_similar_command(broken_cmd))
has_possible_commands = bool(get_closest(broken_cmd, brew_commands))
return has_possible_commands
@@ -97,6 +93,4 @@ def match(command, settings):
def get_new_command(command, settings):
broken_cmd = re.findall(r'Error: Unknown command: ([a-z]+)',
command.stderr)[0]
new_cmd = _get_similar_command(broken_cmd)
return replace_argument(command.script, broken_cmd, new_cmd)
return replace_command(command, broken_cmd, brew_commands)

View File

@@ -1,14 +1,14 @@
# Appends --all to the brew upgrade command
#
#
# Example:
# > brew upgrade
# Warning: brew upgrade with no arguments will change behaviour soon!
# It currently upgrades all formula but this will soon change to require '--all'.
#
#
def match(command, settings):
return (command.script == 'brew upgrade')
return command.script == 'brew upgrade'
def get_new_command(command, settings):
return command.script + ' --all'

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env python
__author__ = "mmussomele"
"""Attempts to spellcheck and correct failed cd commands"""
import os
@@ -8,6 +5,8 @@ from difflib import get_close_matches
from thefuck.utils import sudo_support
from thefuck.rules import cd_mkdir
__author__ = "mmussomele"
MAX_ALLOWED_DIFF = 0.6
@@ -28,8 +27,8 @@ def match(command, settings):
def get_new_command(command, settings):
"""
Attempt to rebuild the path string by spellchecking the directories.
If it fails (i.e. no directories are a close enough match), then it
defaults to the rules of cd_mkdir.
If it fails (i.e. no directories are a close enough match), then it
defaults to the rules of cd_mkdir.
Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6
"""
dest = command.script.split()[1].split(os.sep)

View File

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

View File

@@ -13,12 +13,12 @@ def _is_tar_extract(cmd):
def _tar_file(cmd):
tar_extentions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
tar_extensions = ('.tar', '.tar.Z', '.tar.bz2', '.tar.gz', '.tar.lz',
'.tar.lzma', '.tar.xz', '.taz', '.tb2', '.tbz', '.tbz2',
'.tgz', '.tlz', '.txz', '.tz')
for c in cmd.split():
for ext in tar_extentions:
for ext in tar_extensions:
if c.endswith(ext):
return (c, c[0:len(c)-len(ext)])
@@ -35,7 +35,7 @@ def get_new_command(command, settings):
.format(dir=_tar_file(command.script)[1], cmd=command.script)
def side_effect(command, settings):
with tarfile.TarFile(_tar_file(command.script)[0]) as archive:
def side_effect(old_cmd, command, settings):
with tarfile.TarFile(_tar_file(old_cmd.script)[0]) as archive:
for file in archive.getnames():
os.remove(file)

View File

@@ -30,8 +30,8 @@ def get_new_command(command, settings):
return '{} -d {}'.format(command.script, _zip_file(command)[:-4])
def side_effect(command, settings):
with zipfile.ZipFile(_zip_file(command), 'r') as archive:
def side_effect(old_cmd, command, settings):
with zipfile.ZipFile(_zip_file(old_cmd), 'r') as archive:
for file in archive.namelist():
os.remove(file)

View File

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

77
thefuck/rules/fix_file.py Normal file
View File

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

View File

@@ -12,7 +12,7 @@ def match(command, settings):
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
r"did not match any file\(s\) known to git.", command.stderr)[0]
formatme = shells.and_('git add -- {}', '{}')
return formatme.format(missing_file, command.script)

View File

@@ -27,7 +27,7 @@ def get_branches():
def get_new_command(command, settings):
missing_file = re.findall(
r"error: pathspec '([^']*)' "
"did not match any file\(s\) known to git.", command.stderr)[0]
r"did not match any file\(s\) known to git.", command.stderr)[0]
closest_branch = utils.get_closest(missing_file, get_branches(),
fallback_to_first=False)
if closest_branch:

View File

@@ -1,5 +1,6 @@
import re
from thefuck.utils import get_closest, git_support, replace_argument
from thefuck.utils import (git_support,
get_all_matched_commands, replace_command)
@git_support
@@ -8,20 +9,9 @@ def match(command, settings):
and 'Did you mean' in command.stderr)
def _get_all_git_matched_commands(stderr):
should_yield = False
for line in stderr.split('\n'):
if 'Did you mean' in line:
should_yield = True
elif should_yield and line:
yield line.strip()
@git_support
def get_new_command(command, settings):
broken_cmd = re.findall(r"git: '([^']*)' is not a git command",
command.stderr)[0]
new_cmd = get_closest(broken_cmd,
_get_all_git_matched_commands(command.stderr))
return replace_argument(command.script, broken_cmd, new_cmd)
matched = get_all_matched_commands(command.stderr)
return replace_command(command, broken_cmd, matched)

View File

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

View File

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

View File

@@ -11,4 +11,3 @@ def match(command, settings):
@sudo_support
def get_new_command(command, settings):
return u'./{}'.format(command.script)

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
# > javac foo
# error: Class names, 'foo', are only accepted if annotation
# processing is explicitly requested
def match(command, settings):
return (command.script.startswith('javac ')

View File

@@ -1,5 +1,6 @@
import re
from thefuck.utils import sudo_support, replace_argument
from thefuck.utils import sudo_support,\
replace_command, get_all_matched_commands
@sudo_support
@@ -13,6 +14,5 @@ def match(command, settings):
def get_new_command(command, settings):
broken_cmd = re.findall(r"'([^']*)' is not a task",
command.stderr)[0]
new_cmd = re.findall(r'Did you mean this\?\n\s*([^\n]*)',
command.stderr)[0]
return replace_argument(command.script, broken_cmd, new_cmd)
new_cmds = get_all_matched_commands(command.stderr, 'Did you mean this?')
return replace_command(command, broken_cmd, new_cmds)

View File

@@ -1,7 +1,7 @@
def match(command, settings):
return (command.script == 'ls'
or command.script.startswith('ls ')
and not ('ls -' in command.script))
and 'ls -' not in command.script)
def get_new_command(command, settings):

View File

@@ -8,6 +8,10 @@ def get_new_command(command, settings):
if '2' in command.script:
return command.script.replace("2", "3")
split_cmd = command.script.split()
split_cmd.insert(1, ' 3 ')
return "".join(split_cmd)
split_cmd2 = command.script.split()
split_cmd3 = split_cmd2[:]
split_cmd2.insert(1, ' 2 ')
split_cmd3.insert(1, ' 3 ')
return ["".join(split_cmd3), "".join(split_cmd2)]

View File

@@ -2,7 +2,7 @@ import re
from thefuck.utils import get_closest
def extract_possisiblities(command):
def extract_possibilities(command):
possib = re.findall(r'\n\(did you mean one of ([^\?]+)\?\)', command.stderr)
if possib:
return possib[0].split(', ')
@@ -24,6 +24,6 @@ def match(command, settings):
def get_new_command(command, settings):
script = command.script.split(' ')
possisiblities = extract_possisiblities(command)
script[1] = get_closest(script[1], possisiblities)
possibilities = extract_possibilities(command)
script[1] = get_closest(script[1], possibilities)
return ' '.join(script)

View File

@@ -10,4 +10,4 @@ def match(command, settings):
@sudo_support
def get_new_command(command, settings):
return re.sub('^mkdir (.*)', 'mkdir -p \\1', command.script)
return re.sub('\\bmkdir (.*)', 'mkdir -p \\1', command.script)

View File

@@ -1,5 +1,5 @@
from difflib import get_close_matches
from thefuck.utils import sudo_support, get_all_executables, get_closest
from thefuck.utils import sudo_support, get_all_executables
@sudo_support
@@ -12,8 +12,9 @@ def match(command, settings):
@sudo_support
def get_new_command(command, settings):
old_command = command.script.split(' ')[0]
new_command = get_closest(old_command, get_all_executables())
return ' '.join([new_command] + command.script.split(' ')[1:])
new_cmds = get_close_matches(old_command, get_all_executables(), cutoff=0.1)
return [' '.join([new_command] + command.script.split(' ')[1:])
for new_command in new_cmds]
priority = 3000

View File

@@ -23,4 +23,4 @@ def match(command, settings):
def get_new_command(command, settings):
return 'open http://' + command.script[5:]
return command.script.replace('open ', 'open http://')

View File

@@ -1,43 +1,16 @@
import subprocess
from thefuck.utils import DEVNULL, which
from thefuck.archlinux import archlinux_env, get_pkgfile
from thefuck import shells
from thefuck.utils import memoize
@memoize
def __get_pkgfile(command):
try:
command = command.script
if command.startswith('sudo'):
command = command[5:]
command = command.split(" ")[0]
return subprocess.check_output(
['pkgfile', '-b', '-v', command],
universal_newlines=True, stderr=DEVNULL
).split()
except subprocess.CalledProcessError:
return None
def match(command, settings):
return 'not found' in command.stderr and __get_pkgfile(command)
return 'not found' in command.stderr and get_pkgfile(command.script)
def get_new_command(command, settings):
package = __get_pkgfile(command)[0]
packages = get_pkgfile(command.script)
formatme = shells.and_('{} -S {}', '{}')
return formatme.format(pacman, package, command.script)
return [formatme.format(pacman, package, command.script)
for package in packages]
if not which('pkgfile'):
enabled_by_default = False
elif which('yaourt'):
pacman = 'yaourt'
elif which('pacman'):
pacman = 'sudo pacman'
else:
enabled_by_default = False
enabled_by_default, pacman = archlinux_env()

View File

@@ -0,0 +1,24 @@
""" Fixes wrong package names with pacman or yaourt.
For example the `llc` program is in package `llvm` so this:
yaourt -S llc
should be:
yaourt -S llvm
"""
from thefuck.utils import replace_command
from thefuck.archlinux import archlinux_env, get_pkgfile
def match(command, settings):
return (command.script.startswith(('pacman', 'sudo pacman', 'yaourt'))
and 'error: target not found:' in command.stderr)
def get_new_command(command, settings):
pgr = command.script.split()[-1]
return replace_command(command, pgr, get_pkgfile(pgr))
enabled_by_default, _ = archlinux_env()

View File

@@ -1,14 +1,12 @@
# Fixes careless " and ' usage
#
#
# Example:
# > git commit -m 'My Message"
#
#
#
def match(command, settings):
return ('\'' in command.script
and '\"' in command.script)
return '\'' in command.script and '\"' in command.script
def get_new_command(command, settings):
return command.script.replace ('\'', '\"')
return command.script.replace('\'', '\"')

View File

@@ -10,4 +10,7 @@ def match(command, settings):
@sudo_support
def get_new_command(command, settings):
return re.sub('^rm (.*)', 'rm -rf \\1', command.script)
arguments = '-rf'
if 'hdfs' in command.script:
arguments = '-r'
return re.sub('\\brm (.*)', 'rm ' + arguments + ' \\1', command.script)

View File

@@ -26,8 +26,8 @@ def get_new_command(command, settings):
return command.script
def side_effect(command, settings):
offending = offending_pattern.findall(command.stderr)
def side_effect(old_cmd, command, settings):
offending = offending_pattern.findall(old_cmd.stderr)
for filepath, lineno in offending:
with open(filepath, 'r') as fh:
lines = fh.readlines()

View File

@@ -9,6 +9,7 @@ patterns = ['permission denied',
'This operation requires root.',
'requested operation requires superuser privilege',
'must be run as root',
'must run as root',
'must be superuser',
'must be root',
'need to be root',
@@ -27,4 +28,7 @@ def match(command, settings):
def get_new_command(command, settings):
return u'sudo {}'.format(command.script)
if '>' in command.script:
return u'sudo sh -c "{}"'.format(command.script.replace('"', '\\"'))
else:
return u'sudo {}'.format(command.script)

View File

@@ -1,4 +1,4 @@
from thefuck.utils import get_closest, replace_argument
from thefuck.utils import replace_command
import re
@@ -15,6 +15,4 @@ def get_new_command(command, settings):
old_cmd = cmd.group(1)
suggestions = [cmd.strip() for cmd in cmd.group(2).split(',')]
new_cmd = get_closest(old_cmd, suggestions)
return replace_argument(command.script, old_cmd, new_cmd)
return replace_command(command, old_cmd, suggestions)

View File

@@ -0,0 +1,15 @@
import re
from thefuck.utils import get_all_matched_commands, replace_command
def match(command, settings):
return (command.script.startswith('tsuru ')
and ' is not a tsuru command. See "tsuru help".' in command.stderr
and '\nDid you mean?\n\t' in command.stderr)
def get_new_command(command, settings):
broken_cmd = re.findall(r'tsuru: "([^"]*)" is not a tsuru command',
command.stderr)[0]
return replace_command(command, broken_cmd,
get_all_matched_commands(command.stderr))

View File

@@ -0,0 +1,13 @@
import re
from thefuck.utils import (replace_command, get_all_matched_commands)
def match(command, settings):
return (re.search(r"([^:]*): Unknown command.*", command.stderr) != None
and re.search(r"Did you mean ([^?]*)?", command.stderr) != None)
def get_new_command(command, settings):
broken_cmd = re.findall(r"([^:]*): Unknown command.*", command.stderr)[0]
matched = re.findall(r"Did you mean ([^?]*)?", command.stderr)
return replace_command(command, broken_cmd, matched)

View File

@@ -0,0 +1,18 @@
from thefuck import shells
def match(command, settings):
return command.script.startswith('vagrant ') and 'run `vagrant up`' in command.stderr.lower()
def get_new_command(command, settings):
cmds = command.script.split(' ')
machine = None
if len(cmds) >= 3:
machine = cmds[2]
startAllInstances = shells.and_("vagrant up", command.script)
if machine is None:
return startAllInstances
else:
return [ shells.and_("vagrant up " + machine, command.script), startAllInstances]

View File

@@ -19,7 +19,7 @@ def match(command, settings):
- www.google.fr → subdomain: www, domain: 'google.fr';
- google.co.uk → subdomain: None, domain; 'google.co.uk'.
"""
return 'whois' in command.script and len(command.script.split()) > 1
return 'whois ' in command.script.strip()
def get_new_command(command, settings):
@@ -28,4 +28,5 @@ def get_new_command(command, settings):
if '/' in command.script:
return 'whois ' + urlparse(url).netloc
elif '.' in command.script:
return 'whois ' + '.'.join(urlparse(url).path.split('.')[1:])
path = urlparse(url).path.split('.')
return ['whois ' + '.'.join(path[n:]) for n in range(1, len(path))]

View File

@@ -84,6 +84,7 @@ class Bash(Generic):
value = value[1:-1]
return name, value
@memoize
def get_aliases(self):
proc = Popen('bash -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
@@ -118,15 +119,16 @@ class Fish(Generic):
" set -l exit_code $status\n"
" set -l eval_script"
" (mktemp 2>/dev/null ; or mktemp -t 'thefuck')\n"
" set -l fucked_up_commandd $history[1]\n"
" thefuck $fucked_up_commandd > $eval_script\n"
" set -l fucked_up_command $history[1]\n"
" thefuck $fucked_up_command > $eval_script\n"
" . $eval_script\n"
" rm $eval_script\n"
" if test $exit_code -ne 0\n"
" history --delete $fucked_up_commandd\n"
" history --delete $fucked_up_command\n"
" end\n"
"end").format(fuck)
@memoize
def get_aliases(self):
overridden = self._get_overridden_aliases()
proc = Popen('fish -ic functions', stdout=PIPE, stderr=DEVNULL,
@@ -168,6 +170,7 @@ class Zsh(Generic):
value = value[1:-1]
return name, value
@memoize
def get_aliases(self):
proc = Popen('zsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
@@ -200,6 +203,7 @@ class Tcsh(Generic):
name, value = alias.split("\t", 1)
return name, value
@memoize
def get_aliases(self):
proc = Popen('tcsh -ic alias', stdout=PIPE, stderr=DEVNULL,
shell=True)
@@ -216,7 +220,7 @@ class Tcsh(Generic):
return u'#+{}\n{}\n'.format(int(time()), command_script)
shells = defaultdict(lambda: Generic(), {
shells = defaultdict(Generic, {
'bash': Bash(),
'fish': Fish(),
'zsh': Zsh(),
@@ -224,6 +228,7 @@ shells = defaultdict(lambda: Generic(), {
'tcsh': Tcsh()})
@memoize
def _get_shell():
try:
shell = Process(os.getpid()).parent().name()
@@ -256,7 +261,6 @@ def and_(*commands):
return _get_shell().and_(*commands)
@memoize
def get_aliases():
return list(_get_shell().get_aliases().keys())

View File

@@ -3,6 +3,8 @@ from collections import namedtuple
Command = namedtuple('Command', ('script', 'stdout', 'stderr'))
CorrectedCommand = namedtuple('CorrectedCommand', ('script', 'side_effect', 'priority'))
Rule = namedtuple('Rule', ('name', 'match', 'get_new_command',
'enabled_by_default', 'side_effect',
'priority', 'requires_output'))
@@ -21,7 +23,9 @@ class Settings(dict):
return self.get(item)
def update(self, **kwargs):
"""Returns new settings with new values from `kwargs`."""
conf = dict(self)
conf.update(kwargs)
"""
Returns new settings with values from `kwargs` for unset settings.
"""
conf = dict(kwargs)
conf.update(self)
return Settings(conf)

104
thefuck/ui.py Normal file
View File

@@ -0,0 +1,104 @@
# -*- encoding: utf-8 -*-
import sys
from . import logs
try:
from msvcrt import getch
except ImportError:
def getch():
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
if ch == '\x03': # For compatibility with msvcrt.getch
raise KeyboardInterrupt
return ch
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
SELECT = 0
ABORT = 1
PREVIOUS = 2
NEXT = 3
def read_actions():
"""Yields actions for pressed keys."""
buffer = []
while True:
try:
ch = getch()
except KeyboardInterrupt: # Ctrl+C
yield ABORT
if ch in ('\n', '\r'): # Enter
yield SELECT
buffer.append(ch)
buffer = buffer[-3:]
if buffer == ['\x1b', '[', 'A']: # ↑
yield PREVIOUS
elif buffer == ['\x1b', '[', 'B']: # ↓
yield NEXT
class CommandSelector(object):
def __init__(self, commands):
self._commands = commands
self._index = 0
self._on_change = lambda x: x
def next(self):
self._index = (self._index + 1) % len(self._commands)
self._on_change(self.value)
def previous(self):
self._index = (self._index - 1) % len(self._commands)
self._on_change(self.value)
@property
def value(self):
return self._commands[self._index]
def on_change(self, fn):
self._on_change = fn
fn(self.value)
def select_command(corrected_commands, settings):
"""Returns:
- the first command when confirmation disabled;
- None when ctrl+c pressed;
- selected command.
"""
if not corrected_commands:
logs.failed('No fucks given', settings)
return
selector = CommandSelector(corrected_commands)
if not settings.require_confirmation:
logs.show_corrected_command(selector.value, settings)
return selector.value
multiple_cmds = len(corrected_commands) > 1
selector.on_change(lambda val: logs.confirm_text(val, multiple_cmds, settings))
for action in read_actions():
if action == SELECT:
sys.stderr.write('\n')
return selector.value
elif action == ABORT:
logs.failed('\nAborted', settings)
return
elif action == PREVIOUS:
selector.previous()
elif action == NEXT:
selector.next()

View File

@@ -1,3 +1,4 @@
from .types import Command
from difflib import get_close_matches
from functools import wraps
from pathlib import Path
@@ -6,7 +7,6 @@ import os
import pickle
import re
import six
from .types import Command
DEVNULL = open(os.devnull, 'w')
@@ -69,6 +69,8 @@ def sudo_support(fn):
if result and isinstance(result, six.string_types):
return u'sudo {}'.format(result)
elif isinstance(result, list):
return [u'sudo {}'.format(x) for x in result]
else:
return result
return wrapper
@@ -159,3 +161,27 @@ def replace_argument(script, from_, to):
else:
return script.replace(
u' {} '.format(from_), u' {} '.format(to), 1)
def eager(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return list(fn(*args, **kwargs))
return wrapper
@eager
def get_all_matched_commands(stderr, separator='Did you mean'):
should_yield = False
for line in stderr.split('\n'):
if separator in line:
should_yield = True
elif should_yield and line:
yield line.strip()
def replace_command(command, broken, matched):
"""Helper for *_no_command rules."""
new_cmds = get_close_matches(broken, matched, cutoff=0.1)
return [replace_argument(command.script, broken, new_cmd.strip())
for new_cmd in new_cmds]