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

Compare commits

..

36 Commits
3.26 ... 3.28

Author SHA1 Message Date
Vladimir Iakovlev
8c1591fbe3 Bump to 3.28 2018-11-29 23:43:39 +01:00
kozar
dfd31872a9 #855 - Support Ukrainian layout; Fix matching of similar layouts (#856)
*  #855 - Support Ukrainian layout; Fix matching of similar layouts

* Fix splitting of command line
2018-11-21 19:56:49 +01:00
Vladimir Iakovlev
1eaead4f70 #N/A: Remove performance tests as they are meaningless with the current implementation 2018-11-21 19:44:07 +01:00
Vladimir Iakovlev
5b3350b2dd #N/A: Fix tests with py.test 4 2018-11-21 19:43:01 +01:00
Vladimir Iakovlev
81b05b9f88 #N/A: Fix osx travis-ci build 2018-11-21 19:31:59 +01:00
Simon Eisenmann
b5436a2c47 Remove zsh instant mode log with -f (#854)
In some setups, rm might default to interactive promt. This change adds
the -f parameter to force remove the instant mode log on exit to avoid
an interactive prompt.

```
 ~ 
rm: remove regular file
'/tmp/user/1000/thefuck-script-log-bbb81260140c4b3fa18bf2097f15bd77'?
```
2018-11-02 20:00:56 +01:00
Přemek Vyhnal
b08aec02f5 update install guide for Linux Mint (#852)
on Mint I had to install python3-setuptools package too
2018-10-30 21:49:42 +01:00
Pablo Aguiar
e6be00a63b Comply to new flake8 3.6 (#853)
* #N/A: Ignore W504 line break after binary operator

W504 is now part of flake8 current version 3.6

* #N/A: Fix invalid escape sequences

* #N/A: Remove conflicting path before installing gcc with brew
2018-10-30 20:56:55 +01:00
Vladimir Iakovlev
d226b8f258 #835: Make cache failure non-fatal 2018-10-18 00:35:18 +02:00
Vladimir Iakovlev
f06ebbf2ae #N/A: Use twine for uploading new releases 2018-10-16 21:08:08 +02:00
Nallagatla Manikanta
3e522ba787 Add the pwsh support for thefuck (#844) 2018-10-09 23:20:48 +02:00
Pablo Aguiar
25142f81f8 Some improvements (#846)
* #833: do not require sudo on TravisCI

* #N/A: Add Python dev releases to TravisCI pipeline

Inspired by Brett Cannon's advise [1].

    1: https://snarky.ca/how-to-use-your-project-travis-to-help-test-python-itself/

* #837: try and kill proc and its children

* #N/A: show shell information on `thefuck --version`

* #N/A: omit default arguments to get_close_matches

* #842: add settings var to control number of close matches

* #N/A: remove `n` from the list of `get_closest`'s args
2018-10-08 22:32:30 +02:00
Rafał Zawadzki
5fd4f74701 Added back-ticks for the consistency (#845) 2018-10-08 22:20:17 +02:00
Waldir Pimenta
a0286b402a ISSUE_TEMPLATE.md: sync format of fill-in fields (#841)
Some of the "FILL THIS IN" were wrapped with html comment markup, but most of them weren't. This change makes them all use the same format.
2018-10-02 20:46:58 +02:00
Mateusz Mikuła
bb41f5c4e5 Add snapcraft.yaml (#836) 2018-10-02 20:46:13 +02:00
James Turnbull
926e9ef963 Added a rule to match az binary sub-command misses (#834) 2018-08-16 00:22:24 +02:00
Eugene Duboviy
9d46291944 Add Python 3.7 version support (#833) 2018-08-14 00:22:53 +02:00
Pablo Aguiar
cc6d90963e #367: Support BSD style output in touch rule (#830)
On a Mac, also on NetBSD or OpenBSD, `touch` errs differently:

```
$ uname; touch a/b/c
Darwin
touch: a/b/c: No such file or directory
```

That gets matched by the rule but not fixed by it. Thus the regex
pattern is now a bit more tolerant.
2018-07-29 18:18:42 +02:00
Vladimir Iakovlev
4e755e4799 #827: Make cat_dir rule safer 2018-07-11 23:57:41 +02:00
Scott Colby
1dfd6373ee Stop parsing language-variable cat output to make cat_dir more reliable. (#827)
* Stop parsing language-variable cat output to make cat_dir more reliable.

* Add missing semicolon in readme
2018-07-11 23:47:06 +02:00
Scott Colby
fe0785bc42 Create cat_dir rule for replacing cat with ls (#823)
* Create `cat_dir` rule for replacing `cat` with `ls` when you try to run `cat` on a directory.

* Changed to string methods in response to feedback.

Added a test to make sure lines like 'cat cat' don't become 'ls ls'.

Added trailing '\n's to test cases.
2018-07-10 00:51:42 +02:00
Glen Yu
142ef6e66c added --yeah as an alterative arg to -y and --yes; updated README.md (#822) 2018-07-10 00:50:11 +02:00
Mas0s
59745942b5 added notice to README.md (#821)
zsh's autocorrect function interferes with thefuck, that is now mentioned in README
2018-07-10 00:49:20 +02:00
Chris Mendis
692ee53a33 Avoid masking shell return values in Zsh.app_alias (#820)
I discovered that a common shell script issue (https://github.com/koalaman/shellcheck/wiki/SC2155) is present in the script returned by `Zsh.app_alias()`, amongst other `app_alias` methods in thefuck.

In the case of the zsh `app_alias()` script, this common issue is causing the script to error on some versions of Mac OS X. This is  reported in #718.

Unmasking the value of `TF_SHELL_ALIASES` fixes the errors in Mac OS X, but I also unmasked `TF_HISTORY` for consistency.
2018-07-10 00:48:56 +02:00
Matthieu Guilbert
534782414f git_push: Handle command containing force argument (#818) 2018-07-10 00:48:08 +02:00
Matt Kotsenas
a6bb41e802 Fix Win32 get_key (#819)
PR #711 moved the arrow and cancel key codes to `const.py`. However, the
move also changed the codes from byte arrays to strings, which broke the
use of `msvcrt.getch()` for Windows.

The fix is to use `msvcrt.getwch()` so the key is a Unicode character,
matching the Unix implementation.
2018-07-10 00:46:57 +02:00
Vladimir Iakovlev
86efc6a252 Bump to 3.27 2018-05-22 19:26:55 +02:00
Iulian Onofrei
89207d6d7c Add brew_reinstall rule (#816)
Replaces install with reinstall when a package is already installed.
2018-05-22 19:25:05 +02:00
afwilkin
f6e50bef82 fixed powershell coloring (#805) 2018-05-22 19:03:52 +02:00
Vladimir Iakovlev
81042514c8 Squashed commit of the following:
commit 6919161e77a39b9bd59ca54eac44b956cd3ae1dc
Author: Vladimir Iakovlev <nvbn.rm@gmail.com>
Date:   Tue May 22 19:01:33 2018 +0200

    #810: Fix code style

commit ebbb31a3227ce32ba5288e96c0c16a3d334c45d6
Merge: 2df1a5a a2799ad
Author: Vladimir Iakovlev <nvbn.rm@gmail.com>
Date:   Tue May 22 18:59:56 2018 +0200

    Merge branch 'feature/long-form-help' of https://github.com/jakewarren/thefuck into jakewarren-feature/long-form-help

commit a2799ad098
Author: Jake Warren <jakewarren@users.noreply.github.com>
Date:   Mon May 7 14:12:57 2018 -0500

    Add new `long_form_help` rule
2018-05-22 19:01:51 +02:00
Vladimir Iakovlev
2df1a5a45b #N/A: Fix formatting 2018-05-14 22:44:10 +02:00
Vladimir Iakovlev
72e88d6ba3 #N/A: Add basic shell logger support 2018-05-14 22:16:33 +02:00
Pablo Aguiar
8db3cf6048 Support aliases with equal sign (#808)
* #N/A: Remove `pip` from requirements.txt

* #807: Expect aliases declared with equal sign too

This fixes #807
2018-05-13 15:29:33 +02:00
Iulian Onofrei
68949a5922 Fix spelling (#814) 2018-05-13 15:28:39 +02:00
Pablo Aguiar
216d82b464 #N/A: Remove pip from requirements.txt (#813) 2018-05-13 15:28:00 +02:00
Evan Pratten
97f2d743b3 Update README.md 2018-05-11 11:36:24 +02:00
57 changed files with 615 additions and 157 deletions

View File

@@ -6,17 +6,14 @@ update The Fuck and see if the bug is still there. -->
if not, just open an issue on [GitHub](https://github.com/nvbn/thefuck) with
the following basic information: -->
The output of `thefuck --version` (something like `The Fuck 3.1 using Python 3.5.0`):
FILL THIS IN
Your shell and its version (`bash`, `zsh`, *Windows PowerShell*, etc.):
The output of `thefuck --version` (something like `The Fuck 3.1 using Python
3.5.0 and Bash 4.4.12(1)-release`):
FILL THIS IN
Your system (Debian 7, ArchLinux, Windows, etc.):
<!-- FILL THIS IN -->
FILL THIS IN
How to reproduce the bug:
@@ -32,6 +29,6 @@ If the bug only appears with a specific application, the output of that applicat
Anything else you think is relevant:
<!-- FILL THIS IN -->
FILL THIS IN
<!-- It's only with enough information that we can do something to fix the problem. -->

View File

@@ -2,6 +2,21 @@ language: python
sudo: false
matrix:
include:
- os: linux
dist: xenial
python: "nightly"
- os: linux
dist: xenial
python: "3.8-dev"
- os: linux
dist: xenial
python: "3.7-dev"
- os: linux
dist: xenial
python: "3.7"
- os: linux
dist: trusty
python: "3.6-dev"
- os: linux
dist: trusty
python: "3.6"
@@ -16,6 +31,11 @@ matrix:
python: "2.7"
- os: osx
language: generic
allow_failures:
- python: nightly
- python: 3.8-dev
- python: 3.7-dev
- python: 3.6-dev
services:
- docker
addons:
@@ -24,7 +44,8 @@ addons:
- python-commandnotfound
- python3-commandnotfound
before_install:
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then rm -rf /usr/local/include/c++; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
@@ -39,7 +60,7 @@ script:
- flake8
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
- export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests"
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
- if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
- if [[ $TRAVIS_PYTHON_VERSION != 3.7 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.7 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi

View File

@@ -105,10 +105,10 @@ On OS X, you can install *The Fuck* via [Homebrew][homebrew]:
brew install thefuck
```
On Ubuntu, install *The Fuck* with the following commands:
On Ubuntu / Mint, install *The Fuck* with the following commands:
```bash
sudo apt update
sudo apt install python3-dev python3-pip
sudo apt install python3-dev python3-pip python3-setuptools
sudo pip3 install thefuck
```
@@ -118,6 +118,11 @@ sudo portsnap fetch update
cd /usr/ports/misc/thefuck && sudo make install clean
```
On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/chromebrew) with the following command:
```bash
crew install thefuck
```
On other systems, install *The Fuck* by using `pip`:
```bash
@@ -141,10 +146,10 @@ eval $(thefuck --alias FUCK)
Changes are only available in a new shell session. To make changes immediately
available, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
To run fixed commands without confirmation, use the `-y` option:
To run fixed commands without confirmation, use the `--yeah` option (or just `-y` for short):
```bash
fuck -y
fuck --yeah
```
To fix commands recursively until succeeding, use the `-r` option:
@@ -170,8 +175,10 @@ following rules are enabled by default:
* `adb_unknown_command` &ndash; fixes misspelled commands like `adb logcta`;
* `ag_literal` &ndash; adds `-Q` to `ag` when suggested;
* `aws_cli` &ndash; fixes misspelled commands like `aws dynamdb scan`;
* `az_cli` &ndash; fixes misspelled commands like `az providers`;
* `cargo` &ndash; runs `cargo build` instead of `cargo`;
* `cargo_no_command` &ndash; fixes wrongs commands like `cargo buid`;
* `cat_dir` &ndash; replaces `cat` with `ls` when you try to `cat` a directory;
* `cd_correction` &ndash; spellchecks and correct failed cd commands;
* `cd_mkdir` &ndash; creates directories before cd'ing into them;
* `cd_parent` &ndash; changes `cd..` to `cd ..`;
@@ -226,8 +233,8 @@ following rules are enabled by default:
* `go_run` &ndash; appends `.go` extension when compiling/running Go programs;
* `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task;
* `gradle_wrapper` &ndash; replaces `gradle` with `./gradlew`;
* `grep_arguments_order` &ndash; fixes grep arguments order for situations like `grep -lir . test`;
* `grep_recursive` &ndash; adds `-r` when you trying to `grep` directory;
* `grep_arguments_order` &ndash; fixes `grep` arguments order for situations like `grep -lir . test`;
* `grep_recursive` &ndash; adds `-r` when you try to `grep` directory;
* `grunt_task_not_found` &ndash; fixes misspelled `grunt` commands;
* `gulp_not_task` &ndash; fixes misspelled `gulp` tasks;
* `has_exists_script` &ndash; prepends `./` when script/binary exists;
@@ -239,6 +246,7 @@ following rules are enabled by default:
* `java` &ndash; removes `.java` extension when running Java programs;
* `javac` &ndash; appends missing `.java` when compiling Java files;
* `lein_not_task` &ndash; fixes wrong `lein` tasks like `lein rpl`;
* `long_form_help` &ndash; changes `-h` to `--help` when the short form version is not supported
* `ln_no_hard_link` &ndash; catches hard link creation on directories, suggest symbolic link;
* `ln_s_order` &ndash; fixes `ln -s` arguments order;
* `ls_all` &ndash; adds `-A` to `ls` when output is empty;
@@ -247,7 +255,7 @@ following rules are enabled by default:
* `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
* `mercurial` &ndash; fixes wrong `hg` commands;
* `missing_space_before_subcommand` &ndash; fixes command with missing space like `npminstall`;
* `mkdir_p` &ndash; adds `-p` when you trying to create directory without parent;
* `mkdir_p` &ndash; adds `-p` when you try to create a directory without parent;
* `mvn_no_command` &ndash; adds `clean package` to `mvn`;
* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled lifecycle phases with `mvn`;
* `npm_missing_script` &ndash; fixes `npm` custom script name in `npm run-script <script>`;
@@ -260,13 +268,13 @@ following rules are enabled by default:
* `php_s` &ndash; replaces `-s` by `-S` when trying to run a local php server;
* `port_already_in_use` &ndash; kills process that bound port;
* `prove_recursively` &ndash; adds `-r` when called with directory;
* `python_command` &ndash; prepends `python` when you trying to run not executable/without `./` python script;
* `python_command` &ndash; prepends `python` when you try to run non-executable/without `./` python script;
* `python_execute` &ndash; appends missing `.py` when executing Python files;
* `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args';
* `path_from_history` &ndash; replaces not found path with similar absolute path from history;
* `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands;
* `remove_trailing_cedilla` &ndash; remove trailling cedillas `ç`, a common typo for european keyboard layouts;
* `rm_dir` &ndash; adds `-rf` when you trying to remove directory;
* `rm_dir` &ndash; adds `-rf` when you try to remove a directory;
* `scm_correction` &ndash; corrects wrong scm like `hg log` to `git log`;
* `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
* `sl_ls` &ndash; changes `sl` to `ls`;
@@ -299,6 +307,7 @@ The following rules are enabled by default on specific platforms only:
* `apt_upgrade` &ndash; helps you run `apt upgrade` after `apt list --upgradable`;
* `brew_cask_dependency` &ndash; installs cask dependencies;
* `brew_install` &ndash; fixes formula name for `brew install`;
* `brew_reinstall` &ndash; turns `brew install <formula>` into `brew reinstall <formula>`;
* `brew_link` &ndash; adds `--overwrite --dry-run` if linking fails;
* `brew_uninstall` &ndash; adds `--force` to `brew uninstall` if multiple versions were installed;
* `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
@@ -381,7 +390,8 @@ Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefu
* `history_limit` &ndash; numeric value of how many history commands will be scanned, like `2000`;
* `alter_history` &ndash; push fixed command to history, by default `True`;
* `wait_slow_command` &ndash; max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `slow_commands` &ndash; list of slow commands.
* `slow_commands` &ndash; list of slow commands;
* `num_close_matches` &ndash; maximum number of close matches to suggest, by default `3`.
An example of `settings.py`:
@@ -396,6 +406,7 @@ debug = False
history_limit = 9999
wait_slow_command = 20
slow_commands = ['react-native', 'gradle']
num_close_matches = 5
```
Or via environment variables:
@@ -411,7 +422,8 @@ rule with lower `priority` will be matched first;
* `THEFUCK_HISTORY_LIMIT` &ndash; how many history commands will be scanned, like `2000`;
* `THEFUCK_ALTER_HISTORY` &ndash; push fixed command to history `true/false`;
* `THEFUCK_WAIT_SLOW_COMMAND` &ndash; max amount of time in seconds for getting previous command output if it in `slow_commands` list;
* `THEFUCK_SLOW_COMMANDS` &ndash; list of slow commands, like `lein:gradle`.
* `THEFUCK_SLOW_COMMANDS` &ndash; list of slow commands, like `lein:gradle`;
* `THEFUCK_NUM_CLOSE_MATCHES` &ndash; maximum number of close matches to suggest, like `5`.
For example:
@@ -423,6 +435,7 @@ export THEFUCK_WAIT_COMMAND=10
export THEFUCK_NO_COLORS='false'
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
export THEFUCK_HISTORY_LIMIT='2000'
export THEFUCK_NUM_CLOSE_MATCHES='5'
```
## Third-party packages with rules
@@ -452,7 +465,7 @@ then reading the log.
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
Currently, instant mode only supports Python 3 with bash or zsh.
Currently, instant mode only supports Python 3 with bash or zsh. zsh's autocorrect function also needs to be disabled in order for thefuck to work properly.
To enable instant mode, add `--enable-experimental-instant-mode`
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.

View File

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

View File

@@ -32,4 +32,5 @@ call('git push --tags', shell=True)
env = os.environ
env['CONVERT_README'] = 'true'
call('python setup.py sdist bdist_wheel upload', shell=True, env=env)
call('python setup.py sdist bdist_wheel', shell=True, env=env)
call('twine upload dist/*', shell=True, env=env)

View File

@@ -1,4 +1,3 @@
pip
flake8
pytest
mock
@@ -9,3 +8,4 @@ pexpect
pypandoc
pytest-benchmark
pytest-docker-pexpect
twine

View File

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

25
snapcraft.yaml Normal file
View File

@@ -0,0 +1,25 @@
name: thefuck
version: stable
version-script: git -C parts/thefuck/build describe --abbrev=0 --tags
summary: Magnificent app which corrects your previous console command.
description: |
The Fuck tries to match a rule for the previous command,
creates a new command using the matched rule and runs it.
grade: stable
confinement: classic
apps:
thefuck:
command: bin/thefuck
environment:
PYTHONIOENCODING: utf-8
fuck:
command: bin/fuck
environment:
PYTHONIOENCODING: utf-8
parts:
thefuck:
source: https://github.com/nvbn/thefuck.git
plugin: python

View File

@@ -1,45 +0,0 @@
import pytest
import time
dockerfile = u'''
FROM python:3
RUN adduser --disabled-password --gecos '' test
ENV SEED "{seed}"
WORKDIR /src
USER test
RUN echo 'eval $(thefuck --alias)' > /home/test/.bashrc
RUN echo > /home/test/.bash_history
RUN git config --global user.email "you@example.com"
RUN git config --global user.name "Your Name"
USER root
'''.format(seed=time.time())
def plot(proc, TIMEOUT):
proc.sendline(u'cd /home/test/')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'No fucks given'])
proc.sendline(u'git init')
proc.sendline(u'git add .')
proc.sendline(u'git commit -a -m init')
proc.sendline(u'git brnch')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'git branch'])
proc.send('\n')
assert proc.expect([TIMEOUT, u'master'])
proc.sendline(u'echo test')
proc.sendline(u'echo tst')
proc.sendline(u'fuck')
assert proc.expect([TIMEOUT, u'echo test'])
proc.send('\n')
assert proc.expect([TIMEOUT, u'test'])
@pytest.mark.functional
@pytest.mark.benchmark(min_rounds=10)
def test_performance(spawnu, TIMEOUT, benchmark):
proc = spawnu(u'thefuck/python3-bash-performance',
dockerfile, u'bash')
proc.sendline(u'pip install /src')
proc.sendline(u'su test')
assert benchmark(plot, proc, TIMEOUT) is None

View File

@@ -0,0 +1,58 @@
# -*- encoding: utf-8 -*-
from mock import Mock, patch
from psutil import AccessDenied, TimeoutExpired
from thefuck.output_readers import rerun
class TestRerun(object):
def setup_method(self, test_method):
self.patcher = patch('thefuck.output_readers.rerun.Process')
process_mock = self.patcher.start()
self.proc_mock = process_mock.return_value = Mock()
def teardown_method(self, test_method):
self.patcher.stop()
@patch('thefuck.output_readers.rerun._wait_output', return_value=False)
@patch('thefuck.output_readers.rerun.Popen')
def test_get_output(self, popen_mock, wait_output_mock):
popen_mock.return_value.stdout.read.return_value = b'output'
assert rerun.get_output('', '') is None
wait_output_mock.assert_called_once()
def test_wait_output_is_slow(self, settings):
assert rerun._wait_output(Mock(), True)
self.proc_mock.wait.assert_called_once_with(settings.wait_slow_command)
def test_wait_output_is_not_slow(self, settings):
assert rerun._wait_output(Mock(), False)
self.proc_mock.wait.assert_called_once_with(settings.wait_command)
@patch('thefuck.output_readers.rerun._kill_process')
def test_wait_output_timeout(self, kill_process_mock):
self.proc_mock.wait.side_effect = TimeoutExpired(3)
self.proc_mock.children.return_value = []
assert not rerun._wait_output(Mock(), False)
kill_process_mock.assert_called_once_with(self.proc_mock)
@patch('thefuck.output_readers.rerun._kill_process')
def test_wait_output_timeout_children(self, kill_process_mock):
self.proc_mock.wait.side_effect = TimeoutExpired(3)
self.proc_mock.children.return_value = [Mock()] * 2
assert not rerun._wait_output(Mock(), False)
assert kill_process_mock.call_count == 3
def test_kill_process(self):
proc = Mock()
rerun._kill_process(proc)
proc.kill.assert_called_once_with()
@patch('thefuck.output_readers.rerun.logs')
def test_kill_process_access_denied(self, logs_mock):
proc = Mock()
proc.kill.side_effect = AccessDenied()
rerun._kill_process(proc)
proc.kill.assert_called_once_with()
logs_mock.debug.assert_called_once()

View File

@@ -9,7 +9,7 @@ def output():
'If you meant to search for a literal string, run ag with -Q\n')
@pytest.mark.parametrize('script', ['ag \('])
@pytest.mark.parametrize('script', ['ag \\('])
def test_match(script, output):
assert match(Command(script, output))
@@ -20,6 +20,6 @@ def test_not_match(script):
@pytest.mark.parametrize('script, new_cmd', [
('ag \(', 'ag -Q \(')])
('ag \\(', 'ag -Q \\(')])
def test_get_new_command(script, new_cmd, output):
assert get_new_command((Command(script, output))) == new_cmd

View File

@@ -0,0 +1,44 @@
import pytest
from thefuck.rules.az_cli import match, get_new_command
from thefuck.types import Command
no_suggestions = '''\
az provider: error: the following arguments are required: _subcommand
usage: az provider [-h] {list,show,register,unregister,operation} ...
'''
misspelled_command = '''\
az: 'providers' is not in the 'az' command group. See 'az --help'.
The most similar choice to 'providers' is:
provider
'''
misspelled_subcommand = '''\
az provider: 'lis' is not in the 'az provider' command group. See 'az provider --help'.
The most similar choice to 'lis' is:
list
'''
@pytest.mark.parametrize('command', [
Command('az providers', misspelled_command),
Command('az provider lis', misspelled_subcommand)])
def test_match(command):
assert match(command)
def test_not_match():
assert not match(Command('az provider', no_suggestions))
@pytest.mark.parametrize('command, result', [
(Command('az providers list', misspelled_command), ['az provider list']),
(Command('az provider lis', misspelled_subcommand), ['az provider list'])
])
def test_get_new_command(command, result):
assert get_new_command(command) == result

View File

@@ -0,0 +1,28 @@
import pytest
from thefuck.types import Command
from thefuck.rules.brew_reinstall import get_new_command, match
output = ("Warning: thefuck 9.9 is already installed and up-to-date\nTo "
"reinstall 9.9, run `brew reinstall thefuck`")
def test_match():
command = Command('brew install thefuck', output)
assert match(command)
@pytest.mark.parametrize('script', [
'brew reinstall thefuck',
'brew install foo'])
def test_not_match(script):
assert not match(Command(script, ''))
@pytest.mark.parametrize('script, formula, ', [
('brew install foo', 'foo'),
('brew install bar zap', 'bar zap')])
def test_get_new_command(script, formula):
command = Command(script, output)
new_command = 'brew reinstall {}'.format(formula)
assert get_new_command(command) == new_command

View File

@@ -0,0 +1,39 @@
import pytest
from thefuck.rules.cat_dir import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def isdir(mocker):
return mocker.patch('thefuck.rules.cat_dir'
'.os.path.isdir')
@pytest.mark.parametrize('command', [
Command('cat foo', 'cat: foo: Is a directory\n'),
Command('cat /foo/bar/', 'cat: /foo/bar/: Is a directory\n'),
Command('cat cat/', 'cat: cat/: Is a directory\n'),
])
def test_match(command, isdir):
isdir.return_value = True
assert match(command)
@pytest.mark.parametrize('command', [
Command('cat foo', 'foo bar baz'),
Command('cat foo bar', 'foo bar baz'),
Command('notcat foo bar', 'some output'),
])
def test_not_match(command, isdir):
isdir.return_value = False
assert not match(command)
@pytest.mark.parametrize('command, new_command', [
(Command('cat foo', 'cat: foo: Is a directory\n'), 'ls foo'),
(Command('cat /foo/bar/', 'cat: /foo/bar/: Is a directory\n'), 'ls /foo/bar/'),
(Command('cat cat', 'cat: cat: Is a directory\n'), 'ls cat'),
])
def test_get_new_command(command, new_command):
isdir.return_value = True
assert get_new_command(command) == new_command

View File

@@ -4,7 +4,6 @@ from thefuck.rules.git_checkout import match, get_branches, get_new_command
from thefuck.types import Command
@pytest.fixture
def did_not_match(target, did_you_forget=False):
error = ("error: pathspec '{}' did not match any "
"file(s) known to git.".format(target))

View File

@@ -3,24 +3,22 @@ from thefuck.rules.git_merge import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return 'merge: local - not something we can merge\n\n' \
'Did you mean this?\n\tremote/local'
output = 'merge: local - not something we can merge\n\n' \
'Did you mean this?\n\tremote/local'
def test_match(output):
def test_match():
assert match(Command('git merge test', output))
assert not match(Command('git merge master', ''))
assert not match(Command('ls', output))
@pytest.mark.parametrize('command, new_command', [
(Command('git merge local', output()),
(Command('git merge local', output),
'git merge remote/local'),
(Command('git merge -m "test" local', output()),
(Command('git merge -m "test" local', output),
'git merge -m "test" remote/local'),
(Command('git merge -m "test local" local', output()),
(Command('git merge -m "test local" local', output),
'git merge -m "test local" remote/local')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -3,23 +3,21 @@ from thefuck.rules.git_merge_unrelated import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return 'fatal: refusing to merge unrelated histories'
output = 'fatal: refusing to merge unrelated histories'
def test_match(output):
def test_match():
assert match(Command('git merge test', output))
assert not match(Command('git merge master', ''))
assert not match(Command('ls', output))
@pytest.mark.parametrize('command, new_command', [
(Command('git merge local', output()),
(Command('git merge local', output),
'git merge local --allow-unrelated-histories'),
(Command('git merge -m "test" local', output()),
(Command('git merge -m "test" local', output),
'git merge -m "test" local --allow-unrelated-histories'),
(Command('git merge -m "test local" local', output()),
(Command('git merge -m "test local" local', output),
'git merge -m "test local" local --allow-unrelated-histories')])
def test_get_new_command(command, new_command):
assert get_new_command(command) == new_command

View File

@@ -66,6 +66,10 @@ def test_not_match(output, script, branch_name):
('git -c test=test push --quiet origin', 'master',
'git -c test=test push --set-upstream origin master --quiet'),
('git push', "test's",
"git push --set-upstream origin test\\'s")])
"git push --set-upstream origin test\\'s"),
('git push --force', 'master',
'git push --set-upstream origin master --force'),
('git push --force-with-lease', 'master',
'git push --set-upstream origin master --force-with-lease')])
def test_get_new_command(output, script, branch_name, new_command):
assert get_new_command(Command(script, output)) == new_command

View File

@@ -0,0 +1,22 @@
import pytest
from thefuck.rules.long_form_help import match, get_new_command
from thefuck.types import Command
@pytest.mark.parametrize('output', [
'Try \'grep --help\' for more information.'])
def test_match(output):
assert match(Command('grep -h', output))
def test_not_match():
assert not match(Command('', ''))
@pytest.mark.parametrize('before, after', [
('grep -h', 'grep --help'),
('tar -h', 'tar --help'),
('docker run -h', 'docker run --help'),
('cut -h', 'cut --help')])
def test_get_new_command(before, after):
assert get_new_command(Command(before, '')) == after

View File

@@ -3,25 +3,31 @@ from thefuck.rules.touch import match, get_new_command
from thefuck.types import Command
@pytest.fixture
def output():
return "touch: cannot touch '/a/b/c':" \
" No such file or directory"
def output(is_bsd):
if is_bsd:
return "touch: /a/b/c: No such file or directory"
return "touch: cannot touch '/a/b/c': No such file or directory"
def test_match(output):
command = Command('touch /a/b/c', output)
@pytest.mark.parametrize('script, is_bsd', [
('touch /a/b/c', False),
('touch /a/b/c', True)])
def test_match(script, is_bsd):
command = Command(script, output(is_bsd))
assert match(command)
@pytest.mark.parametrize('command', [
Command('touch /a/b/c', ''),
Command('ls /a/b/c', output())])
Command('ls /a/b/c', output(False))])
def test_not_match(command):
assert not match(command)
def test_get_new_command(output):
command = Command('touch /a/b/c', output)
@pytest.mark.parametrize('script, is_bsd', [
('touch /a/b/c', False),
('touch /a/b/c', True)])
def test_get_new_command(script, is_bsd):
command = Command(script, output(is_bsd))
fixed_command = get_new_command(command)
assert fixed_command == 'mkdir -p /a/b && touch /a/b/c'

View File

@@ -73,3 +73,8 @@ class TestBash(object):
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically
def test_info(self, shell, mocker):
patch = mocker.patch('thefuck.shells.bash.Popen')
patch.return_value.stdout.read.side_effect = [b'3.5.9']
assert shell.info() == 'Bash 3.5.9'

View File

@@ -16,7 +16,8 @@ class TestFish(object):
mock.return_value.stdout.read.side_effect = [(
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
b'man\nmath\npopd\npushd\nruby'),
b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git']
(b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git\n'
b'alias alias_with_equal_sign=echo\ninvalid_alias'), b'func1\nfunc2', b'']
return mock
@pytest.mark.parametrize('key, value', [
@@ -69,7 +70,9 @@ class TestFish(object):
'pushd': 'pushd',
'ruby': 'ruby',
'g': 'git',
'fish_key_reader': '/usr/bin/fish_key_reader'}
'fish_key_reader': '/usr/bin/fish_key_reader',
'alias_with_equal_sign': 'echo'}
assert shell.get_aliases() == {'func1': 'func1', 'func2': 'func2'}
def test_app_alias(self, shell):
assert 'function fuck' in shell.app_alias('fuck')
@@ -109,3 +112,7 @@ class TestFish(object):
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically
def test_info(self, shell, Popen):
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
assert shell.info() == 'Fish Shell 3.5.9'

View File

@@ -68,3 +68,8 @@ class TestZsh(object):
config_exists):
config_exists.return_value = False
assert not shell.how_to_configure().can_configure_automatically
def test_info(self, shell, mocker):
patch = mocker.patch('thefuck.shells.zsh.Popen')
patch.return_value.stdout.read.side_effect = [b'3.5.9']
assert shell.info() == 'ZSH 3.5.9'

View File

@@ -53,7 +53,8 @@ class TestSettingsFromEnv(object):
'THEFUCK_NO_COLORS': 'false',
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
'THEFUCK_WAIT_SLOW_COMMAND': '999',
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'})
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew',
'THEFUCK_NUM_CLOSE_MATCHES': '359'})
settings.init()
assert settings.rules == ['bash', 'lisp']
assert settings.exclude_rules == ['git', 'vim']
@@ -63,6 +64,7 @@ class TestSettingsFromEnv(object):
assert settings.priority == {'bash': 10, 'vim': 15}
assert settings.wait_slow_command == 999
assert settings.slow_commands == ['lein', 'react-native', './gradlew']
assert settings.num_close_matches == 359
def test_from_env_with_DEFAULT(self, os_environ, settings):
os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})

View File

@@ -2,11 +2,11 @@
import pytest
import warnings
from mock import Mock
from mock import Mock, patch
from thefuck.utils import default_settings, \
memoize, get_closest, get_all_executables, replace_argument, \
get_all_matched_commands, is_app, for_app, cache, \
get_valid_history_without_current, _cache
get_valid_history_without_current, _cache, get_close_matches
from thefuck.types import Command
@@ -50,6 +50,18 @@ class TestGetClosest(object):
fallback_to_first=False) is None
class TestGetCloseMatches(object):
@patch('thefuck.utils.difflib_get_close_matches')
def test_call_with_n(self, difflib_mock):
get_close_matches('', [], 1)
assert difflib_mock.call_args[0][2] == 1
@patch('thefuck.utils.difflib_get_close_matches')
def test_call_without_n(self, difflib_mock, settings):
get_close_matches('', [])
assert difflib_mock.call_args[0][2] == settings.get('num_close_matches')
@pytest.fixture
def get_aliases(mocker):
mocker.patch('thefuck.shells.shell.get_aliases',

View File

@@ -55,7 +55,7 @@ class Parser(object):
"""It's too dangerous to use `-y` and `-r` together."""
group = self._parser.add_mutually_exclusive_group()
group.add_argument(
'-y', '--yes',
'-y', '--yes', '--yeah',
action='store_true',
help='execute fixed command without confirmation')
group.add_argument(

View File

@@ -95,7 +95,8 @@ class Settings(dict):
return self._rules_from_env(val)
elif attr == 'priority':
return dict(self._priority_from_env(val))
elif attr in ('wait_command', 'history_limit', 'wait_slow_command'):
elif attr in ('wait_command', 'history_limit', 'wait_slow_command',
'num_close_matches'):
return int(val)
elif attr in ('require_confirmation', 'no_colors', 'debug',
'alter_history', 'instant_mode'):

View File

@@ -42,6 +42,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
'./gradlew', 'vagrant'],
'repeat': False,
'instant_mode': False,
'num_close_matches': 3,
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
@@ -56,7 +57,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
'THEFUCK_REPEAT': 'repeat',
'THEFUCK_INSTANT_MODE': 'instant_mode'}
'THEFUCK_INSTANT_MODE': 'instant_mode',
'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches'}
SETTINGS_HEADER = u"""# The Fuck settings file
#
@@ -81,3 +83,7 @@ LOG_SIZE_IN_BYTES = 1024 * 1024
LOG_SIZE_TO_CLEAN = 10 * 1024
DIFF_WITH_ALIAS = 0.5
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
SHELL_LOGGER_LIMIT = 5

View File

@@ -8,6 +8,7 @@ import sys # noqa: E402
from .. import logs # noqa: E402
from ..argument_parser import Parser # noqa: E402
from ..utils import get_installation_info # noqa: E402
from ..shells import shell # noqa: E402
from .alias import print_alias # noqa: E402
from .fix_command import fix_command # noqa: E402
@@ -20,7 +21,7 @@ def main():
parser.print_help()
elif known_args.version:
logs.version(get_installation_info().version,
sys.version.split()[0])
sys.version.split()[0], shell.info())
elif known_args.command or 'TF_HISTORY' in os.environ:
fix_command(known_args)
elif known_args.alias:

View File

@@ -134,7 +134,8 @@ def configured_successfully(configuration_details):
reload=configuration_details.reload))
def version(thefuck_version, python_version):
def version(thefuck_version, python_version, shell_info):
sys.stderr.write(
u'The Fuck {} using Python {}\n'.format(thefuck_version,
python_version))
u'The Fuck {} using Python {} and {}\n'.format(thefuck_version,
python_version,
shell_info))

View File

@@ -1,5 +1,5 @@
from ..conf import settings
from . import read_log, rerun
from . import read_log, rerun, shell_logger
def get_output(script, expanded):
@@ -12,6 +12,8 @@ def get_output(script, expanded):
:rtype: str
"""
if shell_logger.is_available():
return shell_logger.get_output(script)
if settings.instant_mode:
return read_log.get_output(script)
else:

View File

@@ -1,11 +1,25 @@
import os
import shlex
from subprocess import Popen, PIPE, STDOUT
from psutil import Process, TimeoutExpired
from psutil import AccessDenied, Process, TimeoutExpired
from .. import logs
from ..conf import settings
def _kill_process(proc):
"""Tries to kill the process otherwise just logs a debug message, the
process will be killed when thefuck terminates.
:type proc: Process
"""
try:
proc.kill()
except AccessDenied:
logs.debug(u'Rerun: process PID {} ({}) could not be terminated'.format(
proc.pid, proc.exe()))
def _wait_output(popen, is_slow):
"""Returns `True` if we can get output of the command in the
`settings.wait_command` time.
@@ -23,8 +37,8 @@ def _wait_output(popen, is_slow):
return True
except TimeoutExpired:
for child in proc.children(recursive=True):
child.kill()
proc.kill()
_kill_process(child)
_kill_process(proc)
return False

View File

@@ -0,0 +1,60 @@
import json
import os
import socket
try:
from shutil import get_terminal_size
except ImportError:
from backports.shutil_get_terminal_size import get_terminal_size
import pyte
from .. import const, logs
def _get_socket_path():
return os.environ.get(const.SHELL_LOGGER_SOCKET_ENV)
def is_available():
"""Returns `True` if shell logger socket available.
:rtype: book
"""
path = _get_socket_path()
if not path:
return False
return os.path.exists(path)
def _get_last_n(n):
with socket.socket(socket.AF_UNIX) as client:
client.connect(_get_socket_path())
request = json.dumps({
"type": "list",
"count": n,
}) + '\n'
client.sendall(request.encode('utf-8'))
response = client.makefile().readline()
return json.loads(response)['commands']
def _get_output_lines(output):
lines = output.split('\n')
screen = pyte.Screen(get_terminal_size().columns, len(lines))
stream = pyte.Stream(screen)
stream.feed('\n'.join(lines))
return screen.display
def get_output(script):
"""Gets command output from shell logger."""
with logs.debug_time(u'Read output from external shell logger'):
commands = _get_last_n(const.SHELL_LOGGER_LIMIT)
for command in commands:
if command['command'] == script:
lines = _get_output_lines(command['output'])
output = '\n'.join(lines).strip()
return output
else:
logs.warn("Output isn't available in shell logger")
return None

View File

@@ -3,7 +3,7 @@ import re
from thefuck.utils import for_app, replace_argument
INVALID_CHOICE = "(?<=Invalid choice: ')(.*)(?=', maybe you meant:)"
OPTIONS = "^\s*\*\s(.*)"
OPTIONS = "^\\s*\\*\\s(.*)"
@for_app('aws')

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

@@ -0,0 +1,17 @@
import re
from thefuck.utils import for_app, replace_argument
INVALID_CHOICE = "(?=az)(?:.*): '(.*)' is not in the '.*' command group."
OPTIONS = "^The most similar choice to '.*' is:\n\\s*(.*)$"
@for_app('az')
def match(command):
return "is not in the" in command.output and "command group" in command.output
def get_new_command(command):
mistake = re.search(INVALID_CHOICE, command.output).group(1)
options = re.findall(OPTIONS, command.output, flags=re.MULTILINE)
return [replace_argument(command.script, mistake, o) for o in options]

View File

@@ -20,7 +20,7 @@ def _get_formulas():
def _get_similar_formula(formula_name):
return get_closest(formula_name, _get_formulas(), 1, 0.85)
return get_closest(formula_name, _get_formulas(), cutoff=0.85)
def match(command):

View File

@@ -0,0 +1,19 @@
import re
from thefuck.utils import for_app
warning_regex = re.compile(r'Warning: (?:.(?!is ))+ is already installed and '
r'up-to-date')
message_regex = re.compile(r'To reinstall (?:(?!, ).)+, run `brew reinstall '
r'[^`]+`')
@for_app('brew', at_least=2)
def match(command):
return ('install' in command.script
and warning_regex.search(command.output)
and message_regex.search(command.output))
def get_new_command(command):
return command.script.replace('install', 'reinstall')

14
thefuck/rules/cat_dir.py Normal file
View File

@@ -0,0 +1,14 @@
import os
from thefuck.utils import for_app
@for_app('cat', at_least=1)
def match(command):
return (
command.output.startswith('cat: ') and
os.path.isdir(command.script_parts[1])
)
def get_new_command(command):
return command.script.replace('cat', 'ls', 1)

View File

@@ -2,10 +2,9 @@
import os
import six
from difflib import get_close_matches
from thefuck.specific.sudo import sudo_support
from thefuck.rules import cd_mkdir
from thefuck.utils import for_app
from thefuck.utils import for_app, get_close_matches
__author__ = "mmussomele"

View File

@@ -39,6 +39,6 @@ def get_new_command(command):
while len(command_parts) > push_idx and command_parts[len(command_parts) - 1][0] != '-':
command_parts.pop(len(command_parts) - 1)
arguments = re.findall(r'git push (.*)', command.output)[0].replace("'", r"\'").strip()
arguments = re.findall(r'git push (.*)', command.output)[-1].replace("'", r"\'").strip()
return replace_argument(" ".join(command_parts), 'push',
'push {}'.format(arguments))

View File

@@ -1,4 +1,4 @@
from difflib import get_close_matches
from thefuck.utils import get_close_matches
from thefuck.specific.git import git_support

View File

@@ -8,5 +8,5 @@ def match(command):
def get_new_command(command):
apps = re.findall('([^ ]*) \([^)]*\)', command.output)
apps = re.findall('([^ ]*) \\([^)]*\\)', command.output)
return [command.script + ' --app ' + app for app in apps]

View File

@@ -1,5 +1,5 @@
from difflib import get_close_matches
from thefuck.utils import get_closest, get_valid_history_without_current
from thefuck.utils import get_close_matches, get_closest, \
get_valid_history_without_current
def match(command):

View File

@@ -0,0 +1,27 @@
from thefuck.utils import replace_argument
import re
# regex to match a suggested help command from the tool output
help_regex = r"(?:Run|Try) '([^']+)'(?: or '[^']+')? for (?:details|more information)."
def match(command):
if re.search(help_regex, command.output, re.I) is not None:
return True
if '--help' in command.output:
return True
return False
def get_new_command(command):
if re.search(help_regex, command.output) is not None:
match_obj = re.search(help_regex, command.output, re.I)
return match_obj.group(1)
return replace_argument(command.script, '-h', '--help')
enabled_by_default = True
priority = 5000

View File

@@ -1,5 +1,4 @@
from thefuck.utils import replace_command, for_app
from difflib import get_close_matches
from thefuck.utils import for_app, get_close_matches, replace_command
import re
@@ -25,8 +24,7 @@ def get_new_command(command):
available_lifecycles = _getavailable_lifecycles(command)
if available_lifecycles and failed_lifecycle:
selected_lifecycle = get_close_matches(
failed_lifecycle.group(1), available_lifecycles.group(1).split(", "),
3, 0.6)
failed_lifecycle.group(1), available_lifecycles.group(1).split(", "))
return replace_command(command, failed_lifecycle.group(1), selected_lifecycle)
else:
return []

View File

@@ -1,5 +1,4 @@
from difflib import get_close_matches
from thefuck.utils import get_all_executables, \
from thefuck.utils import get_all_executables, get_close_matches, \
get_valid_history_without_current, get_closest, which
from thefuck.specific.sudo import sudo_support

View File

@@ -4,6 +4,7 @@ from thefuck.utils import memoize, get_alias
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''',
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''',
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''']
@@ -15,7 +16,14 @@ def _get_matched_layout(command):
# result in a non-splitable sript as per shlex
cmd = command.script.split(' ')
for source_layout in source_layouts:
if all([ch in source_layout or ch in '-_' for ch in cmd[0]]):
is_all_match = True
for cmd_part in cmd:
if not all([ch in source_layout or ch in '-_' for ch in cmd_part]):
is_all_match = False
break
if is_all_match:
return source_layout

View File

@@ -9,5 +9,6 @@ def match(command):
def get_new_command(command):
path = re.findall(r"touch: cannot touch '(.+)/.+':", command.output)[0]
path = path = re.findall(
r"touch: (?:cannot touch ')?(.+)/.+'?:", command.output)[0]
return shell.and_(u'mkdir -p {}'.format(path), command.script)

View File

@@ -16,7 +16,8 @@ shells = {'bash': Bash,
'zsh': Zsh,
'csh': Tcsh,
'tcsh': Tcsh,
'powershell': Powershell}
'powershell': Powershell,
'pwsh': Powershell}
def _get_shell_from_env():

View File

@@ -1,9 +1,10 @@
import os
from subprocess import Popen, PIPE
from tempfile import gettempdir
from uuid import uuid4
from ..conf import settings
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
from ..utils import memoize
from ..utils import DEVNULL, memoize
from .generic import Generic
@@ -81,3 +82,10 @@ class Bash(Generic):
content=u'eval $(thefuck --alias)',
path=config,
reload=u'source {}'.format(config))
def info(self):
"""Returns the name and version of the current shell"""
proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
stdout=PIPE, stderr=DEVNULL)
version = proc.stdout.read().decode('utf-8').strip()
return u'Bash {}'.format(version)

View File

@@ -20,9 +20,17 @@ def _get_functions(overridden):
def _get_aliases(overridden):
aliases = {}
proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
alias_out = proc.stdout.read().decode('utf-8').strip().split('\n')
for alias in alias_out:
name, value = alias.replace('alias ', '', 1).split(' ', 1)
alias_out = proc.stdout.read().decode('utf-8').strip()
if not alias_out:
return aliases
for alias in alias_out.split('\n'):
for separator in (' ', '='):
split_alias = alias.replace('alias ', '', 1).split(separator, 1)
if len(split_alias) == 2:
name, value = split_alias
break
else:
continue
if name not in overridden:
aliases[name] = value
return aliases
@@ -95,6 +103,13 @@ class Fish(Generic):
path='~/.config/fish/config.fish',
reload='fish')
def info(self):
"""Returns the name and version of the current shell"""
proc = Popen(['fish', '-c', 'echo $FISH_VERSION'],
stdout=PIPE, stderr=DEVNULL)
version = proc.stdout.read().decode('utf-8').strip()
return u'Fish Shell {}'.format(version)
def put_to_history(self, command):
try:
return self._put_to_history(command)

View File

@@ -82,7 +82,7 @@ class Generic(object):
encoded = self.encode_utf8(command)
try:
splitted = [s.replace("??", "\ ") for s in shlex.split(encoded.replace('\ ', '??'))]
splitted = [s.replace("??", "\\ ") for s in shlex.split(encoded.replace('\\ ', '??'))]
except ValueError:
splitted = encoded.split(' ')
@@ -131,6 +131,10 @@ class Generic(object):
'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
'until', 'wait', 'while']
def info(self):
"""Returns the name and version of the current shell"""
return 'Generic Shell'
def _create_shell_configuration(self, content, path, reload):
return ShellConfiguration(
content=content,

View File

@@ -12,6 +12,7 @@ class Powershell(Generic):
' else { iex "$fuck"; }\n' \
' }\n' \
' }\n' \
' [Console]::ResetColor() \n' \
'}\n'
def and_(self, *commands):

View File

@@ -1,10 +1,11 @@
from time import time
import os
from subprocess import Popen, PIPE
from tempfile import gettempdir
from uuid import uuid4
from ..conf import settings
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
from ..utils import memoize
from ..utils import DEVNULL, memoize
from .generic import Generic
@@ -16,8 +17,10 @@ class Zsh(Generic):
TF_PYTHONIOENCODING=$PYTHONIOENCODING;
export TF_SHELL=zsh;
export TF_ALIAS={name};
export TF_SHELL_ALIASES=$(alias);
export TF_HISTORY="$(fc -ln -10)";
TF_SHELL_ALIASES=$(alias);
export TF_SHELL_ALIASES;
TF_HISTORY="$(fc -ln -10)";
export TF_HISTORY;
export PYTHONIOENCODING=utf-8;
TF_CMD=$(
thefuck {argument_placeholder} $@
@@ -49,7 +52,7 @@ class Zsh(Generic):
export THEFUCK_INSTANT_MODE=True;
export THEFUCK_OUTPUT_LOG={log};
thefuck --shell-logger {log};
rm {log};
rm -f {log};
exit
'''.format(log=log_path)
@@ -83,3 +86,10 @@ class Zsh(Generic):
content=u'eval $(thefuck --alias)',
path='~/.zshrc',
reload='source ~/.zshrc')
def info(self):
"""Returns the name and version of the current shell"""
proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
stdout=PIPE, stderr=DEVNULL)
version = proc.stdout.read().decode('utf-8').strip()
return u'ZSH {}'.format(version)

View File

@@ -1,5 +1,4 @@
import os
import sys
import msvcrt
import win_unicode_console
from .. import const
@@ -12,20 +11,18 @@ def init_output():
def get_key():
ch = msvcrt.getch()
if ch in (b'\x00', b'\xe0'): # arrow or function key prefix?
ch = msvcrt.getch() # second call returns the actual key code
ch = msvcrt.getwch()
if ch in ('\x00', '\xe0'): # arrow or function key prefix?
ch = msvcrt.getwch() # second call returns the actual key code
if ch in const.KEY_MAPPING:
return const.KEY_MAPPING[ch]
if ch == b'H':
if ch == 'H':
return const.KEY_UP
if ch == b'P':
if ch == 'P':
return const.KEY_DOWN
encoding = (sys.stdout.encoding
or os.environ.get('PYTHONIOENCODING', 'utf-8'))
return ch.decode(encoding)
return ch
def open_command(arg):

View File

@@ -3,11 +3,12 @@ import os
import pickle
import re
import shelve
import sys
import six
from decorator import decorator
from difflib import get_close_matches
from difflib import get_close_matches as difflib_get_close_matches
from functools import wraps
from .logs import warn
from .logs import warn, exception
from .conf import settings
from .system import Path
@@ -86,16 +87,23 @@ def default_settings(params):
return decorator(_default_settings)
def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
def get_closest(word, possibilities, cutoff=0.6, fallback_to_first=True):
"""Returns closest match or just first from possibilities."""
possibilities = list(possibilities)
try:
return get_close_matches(word, possibilities, n, cutoff)[0]
return difflib_get_close_matches(word, possibilities, 1, cutoff)[0]
except IndexError:
if fallback_to_first:
return possibilities[0]
def get_close_matches(word, possibilities, n=None, cutoff=0.6):
"""Overrides `difflib.get_close_match` to controle argument `n`."""
if n is None:
n = settings.num_close_matches
return difflib_get_close_matches(word, possibilities, n, cutoff)
@memoize
def get_all_executables():
from thefuck.shells import shell
@@ -190,6 +198,13 @@ class Cache(object):
self._db = None
def _init_db(self):
try:
self._setup_db()
except Exception:
exception("Unable to init cache", sys.exc_info())
self._db = {}
def _setup_db(self):
cache_dir = self._get_cache_dir()
cache_path = Path(cache_dir).joinpath('thefuck').as_posix()

View File

@@ -1,10 +1,10 @@
[tox]
envlist = py27,py34,py35,py36
envlist = py27,py34,py35,py36,py37
[testenv]
deps = -rrequirements.txt
commands = py.test -v --capture=sys
[flake8]
ignore = E501,W503
ignore = E501,W503,W504
exclude = venv,build,.tox,setup.py,fastentrypoints.py