mirror of
https://github.com/nvbn/thefuck.git
synced 2025-11-07 18:42:07 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86efc6a252 | ||
|
|
89207d6d7c | ||
|
|
f6e50bef82 | ||
|
|
81042514c8 | ||
|
|
2df1a5a45b | ||
|
|
72e88d6ba3 | ||
|
|
8db3cf6048 | ||
|
|
68949a5922 | ||
|
|
216d82b464 | ||
|
|
97f2d743b3 | ||
|
|
d5cc7ec43b | ||
|
|
33a87502cd | ||
|
|
82a12dda81 | ||
|
|
4c9099a79b | ||
|
|
1508ecfeae | ||
|
|
284d49da8d | ||
|
|
fb39d0bbd3 | ||
|
|
ed24e4ca61 | ||
|
|
a36a8b4de1 | ||
|
|
2678adf981 | ||
|
|
dd9554539f | ||
|
|
b65e3a9aad | ||
|
|
027b41da59 | ||
|
|
aa45585601 | ||
|
|
c205683a8d | ||
|
|
7c858fadb3 | ||
|
|
797ca1c564 | ||
|
|
7b10a86267 | ||
|
|
b62bb90a0d | ||
|
|
a696461cd3 | ||
|
|
7e6d1dbc7c | ||
|
|
4fb85b0a92 | ||
|
|
83e1710712 | ||
|
|
045c8ae76c | ||
|
|
bcb749722b | ||
|
|
f700b23f57 | ||
|
|
897572d278 | ||
|
|
0640509895 | ||
|
|
57fb6e079a | ||
|
|
83cf97dc26 | ||
|
|
9e788196e6 | ||
|
|
4ea02a3153 | ||
|
|
f966ecd4f5 | ||
|
|
629056077f | ||
|
|
4780027d63 | ||
|
|
4847078f37 | ||
|
|
d582159670 | ||
|
|
97123dbf73 | ||
|
|
10ac1a3b38 | ||
|
|
8fb5ddefb6 | ||
|
|
f1fab0dbb2 | ||
|
|
68df7154e5 | ||
|
|
08082e606b |
37
.github/ISSUE_TEMPLATE.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!-- If you have any issue with The Fuck, sorry about that, but we will do what we
|
||||||
|
can to fix that. Actually, maybe we already have, so first thing to do is to
|
||||||
|
update The Fuck and see if the bug is still there. -->
|
||||||
|
|
||||||
|
<!-- If it is (sorry again), check if the problem has not already been reported and
|
||||||
|
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.):
|
||||||
|
|
||||||
|
FILL THIS IN
|
||||||
|
|
||||||
|
Your system (Debian 7, ArchLinux, Windows, etc.):
|
||||||
|
|
||||||
|
<!-- FILL THIS IN -->
|
||||||
|
|
||||||
|
How to reproduce the bug:
|
||||||
|
|
||||||
|
FILL THIS IN
|
||||||
|
|
||||||
|
The output of The Fuck with `THEFUCK_DEBUG=true` exported (typically execute `export THEFUCK_DEBUG=true` in your shell before The Fuck):
|
||||||
|
|
||||||
|
FILL THIS IN
|
||||||
|
|
||||||
|
If the bug only appears with a specific application, the output of that application and its version:
|
||||||
|
|
||||||
|
FILL THIS IN
|
||||||
|
|
||||||
|
Anything else you think is relevant:
|
||||||
|
|
||||||
|
<!-- FILL THIS IN -->
|
||||||
|
|
||||||
|
<!-- It's only with enough information that we can do something to fix the problem. -->
|
||||||
12
.travis.yml
12
.travis.yml
@@ -11,17 +11,10 @@ matrix:
|
|||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
python: "3.4"
|
python: "3.4"
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
python: "3.3"
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
python: "2.7"
|
python: "2.7"
|
||||||
- os: osx
|
- os: osx
|
||||||
env: FORMULA="python"
|
|
||||||
language: generic
|
|
||||||
- os: osx
|
|
||||||
env: FORMULA="python3"
|
|
||||||
language: generic
|
language: generic
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
@@ -32,8 +25,9 @@ addons:
|
|||||||
- python3-commandnotfound
|
- python3-commandnotfound
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
|
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
|
||||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then if brew ls --versions $FORMULA; then brew upgrade $FORMULA || echo Python is up to date; else brew install $FORMULA; fi; fi
|
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
|
||||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p $FORMULA; fi
|
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
|
||||||
|
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
|
||||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
|
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
|
||||||
- pip install -U pip
|
- pip install -U pip
|
||||||
- pip install -U coveralls
|
- pip install -U coveralls
|
||||||
|
|||||||
@@ -23,3 +23,37 @@ It's only with enough information that we can do something to fix the problem.
|
|||||||
We gladly accept pull request on the [official
|
We gladly accept pull request on the [official
|
||||||
repository](https://github.com/nvbn/thefuck) for new rules, new features, bug
|
repository](https://github.com/nvbn/thefuck) for new rules, new features, bug
|
||||||
fixes, etc.
|
fixes, etc.
|
||||||
|
|
||||||
|
# Developing
|
||||||
|
|
||||||
|
Install `The Fuck` for development:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python setup.py develop
|
||||||
|
```
|
||||||
|
|
||||||
|
Run code style checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flake8
|
||||||
|
```
|
||||||
|
|
||||||
|
Run unit tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
py.test
|
||||||
|
```
|
||||||
|
|
||||||
|
Run unit and functional tests (requires docker):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
py.test --enable-functional
|
||||||
|
```
|
||||||
|
|
||||||
|
For sending package to pypi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install pandoc
|
||||||
|
./release.py
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Copyright (c) 2015 Vladimir Iakovlev
|
Copyright (c) 2015-2018 Vladimir Iakovlev
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
170
README.md
170
README.md
@@ -1,14 +1,15 @@
|
|||||||
# The Fuck [![Version][version-badge]][version-link] [![Build Status][travis-badge]][travis-link] [![Windows Build Status][appveyor-badge]][appveyor-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
|
# The Fuck [![Version][version-badge]][version-link] [![Build Status][travis-badge]][travis-link] [![Windows Build Status][appveyor-badge]][appveyor-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
|
||||||
|
|
||||||
Magnificent app which corrects your previous console command,
|
*The Fuck* is a magnificent app, inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
[tweet](https://twitter.com/liamosaur/status/506975850596536320),
|
||||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
|
that corrects errors in previous console commands.
|
||||||
|
|
||||||
The Fuck is too slow? [Try experimental instant mode!](#experimental-instant-mode)
|
|
||||||
|
Is *The Fuck* too slow? [Try the experimental instant mode!](#experimental-instant-mode)
|
||||||
|
|
||||||
[![gif with examples][examples-link]][examples-link]
|
[![gif with examples][examples-link]][examples-link]
|
||||||
|
|
||||||
Few more examples:
|
More examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
➜ apt-get install vim
|
➜ apt-get install vim
|
||||||
@@ -75,8 +76,8 @@ REPL-y 0.3.1
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are not scared to blindly run the changed command, there is a `require_confirmation`
|
If you're not afraid of blindly running corrected commands, the
|
||||||
[settings](#settings) option:
|
`require_confirmation` [settings](#settings) option can be disabled:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
➜ apt-get install vim
|
➜ apt-get install vim
|
||||||
@@ -92,35 +93,47 @@ Reading package lists... Done
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- python (3.3+)
|
- python (3.4+)
|
||||||
- pip
|
- pip
|
||||||
- python-dev
|
- python-dev
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
On OS X you can install `The Fuck` with [Homebrew][homebrew]:
|
On OS X, you can install *The Fuck* via [Homebrew][homebrew]:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install thefuck
|
brew install thefuck
|
||||||
```
|
```
|
||||||
|
|
||||||
On Ubuntu you can install `The Fuck` with:
|
On Ubuntu, install *The Fuck* with the following commands:
|
||||||
```bash
|
```bash
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install python3-dev python3-pip
|
sudo apt install python3-dev python3-pip
|
||||||
sudo pip3 install thefuck
|
sudo pip3 install thefuck
|
||||||
```
|
```
|
||||||
|
|
||||||
On other systems you can install `The Fuck` with `pip`:
|
On FreeBSD, install *The Fuck* with the following commands:
|
||||||
|
```bash
|
||||||
|
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
|
```bash
|
||||||
pip install thefuck
|
pip install thefuck
|
||||||
```
|
```
|
||||||
|
|
||||||
[Or using an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
[Alternatively, you may use an OS package manager (OS X, Ubuntu, Arch).](https://github.com/nvbn/thefuck/wiki/Installation)
|
||||||
|
|
||||||
<a href='#manual-installation' name='manual-installation'>#</a>
|
<a href='#manual-installation' name='manual-installation'>#</a>
|
||||||
You should place this command in your `.bash_profile`, `.bashrc`, `.zshrc` or other startup script:
|
It is recommended that you place this command in your `.bash_profile`,
|
||||||
|
`.bashrc`, `.zshrc` or other startup script:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
eval $(thefuck --alias)
|
eval $(thefuck --alias)
|
||||||
@@ -130,34 +143,36 @@ eval $(thefuck --alias FUCK)
|
|||||||
|
|
||||||
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
[Or in your shell config (Bash, Zsh, Fish, Powershell, tcsh).](https://github.com/nvbn/thefuck/wiki/Shell-aliases)
|
||||||
|
|
||||||
Changes will be available only in a new shell session.
|
Changes are only available in a new shell session. To make changes immediately
|
||||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
|
available, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
|
||||||
|
|
||||||
If you want to run fixed command without confirmation you can use `-y` option:
|
To run fixed commands without confirmation, use the `-y` option:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fuck -y
|
fuck -y
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to fix commands recursively until success you can use `-r` option:
|
To fix commands recursively until succeeding, use the `-r` option:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fuck -r
|
fuck -r
|
||||||
```
|
```
|
||||||
|
|
||||||
## Update
|
## Updating
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install thefuck --upgrade
|
pip3 install thefuck --upgrade
|
||||||
```
|
```
|
||||||
|
|
||||||
**Aliases changed in 1.34.**
|
**Note: Alias functionality was changed in v1.34 of *The Fuck***
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
The Fuck tries to match a rule for the previous command, creates a new command
|
*The Fuck* attempts to match the previous command with a rule. If a match is
|
||||||
using the matched rule and runs it. Rules enabled by default are as follows:
|
found, a new command is created using the matched rule and executed. The
|
||||||
|
following rules are enabled by default:
|
||||||
|
|
||||||
|
* `adb_unknown_command` – fixes misspelled commands like `adb logcta`;
|
||||||
* `ag_literal` – adds `-Q` to `ag` when suggested;
|
* `ag_literal` – adds `-Q` to `ag` when suggested;
|
||||||
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
|
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
|
||||||
* `cargo` – runs `cargo build` instead of `cargo`;
|
* `cargo` – runs `cargo build` instead of `cargo`;
|
||||||
@@ -186,11 +201,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
||||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
||||||
* `git_checkout` – fixes branch name or creates new branch;
|
* `git_checkout` – fixes branch name or creates new branch;
|
||||||
|
* `git_commit_amend` – offers `git commit --amend` after previous commit;
|
||||||
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
||||||
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
* `git_diff_staged` – adds `--staged` to previous `git diff` with unexpected output;
|
||||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||||
* `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename`
|
* `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename`
|
||||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
||||||
|
* `git_merge` – adds remote to branch names;
|
||||||
|
* `git_merge_unrelated` – adds `--allow-unrelated-histories` when required
|
||||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
||||||
* `git_pull` – sets upstream before executing previous `git pull`;
|
* `git_pull` – sets upstream before executing previous `git pull`;
|
||||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||||
@@ -214,10 +232,11 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
||||||
* `gradle_wrapper` – replaces `gradle` with `./gradlew`;
|
* `gradle_wrapper` – replaces `gradle` with `./gradlew`;
|
||||||
* `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`;
|
* `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`;
|
||||||
* `grep_recursive` – adds `-r` when you trying to `grep` directory;
|
* `grep_recursive` – adds `-r` when you try to `grep` directory;
|
||||||
* `grunt_task_not_found` – fixes misspelled `grunt` commands;
|
* `grunt_task_not_found` – fixes misspelled `grunt` commands;
|
||||||
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
||||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||||
|
* `heroku_multiple_apps` – add `--app <app>` to `heroku` commands like `heroku pg`;
|
||||||
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
||||||
* `history` – tries to replace command with most similar command from history;
|
* `history` – tries to replace command with most similar command from history;
|
||||||
* `hostscli` – tries to fix `hostscli` usage;
|
* `hostscli` – tries to fix `hostscli` usage;
|
||||||
@@ -225,6 +244,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `java` – removes `.java` extension when running Java programs;
|
* `java` – removes `.java` extension when running Java programs;
|
||||||
* `javac` – appends missing `.java` when compiling Java files;
|
* `javac` – appends missing `.java` when compiling Java files;
|
||||||
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
* `lein_not_task` – fixes wrong `lein` tasks like `lein rpl`;
|
||||||
|
* `long_form_help` – changes `-h` to `--help` when the short form version is not supported
|
||||||
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
* `ln_no_hard_link` – catches hard link creation on directories, suggest symbolic link;
|
||||||
* `ln_s_order` – fixes `ln -s` arguments order;
|
* `ln_s_order` – fixes `ln -s` arguments order;
|
||||||
* `ls_all` – adds `-A` to `ls` when output is empty;
|
* `ls_all` – adds `-A` to `ls` when output is empty;
|
||||||
@@ -233,7 +253,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||||
* `mercurial` – fixes wrong `hg` commands;
|
* `mercurial` – fixes wrong `hg` commands;
|
||||||
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
||||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
* `mkdir_p` – adds `-p` when you try to create a directory without parent;
|
||||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
||||||
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
|
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
|
||||||
@@ -246,13 +266,13 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||||
* `port_already_in_use` – kills process that bound port;
|
* `port_already_in_use` – kills process that bound port;
|
||||||
* `prove_recursively` – adds `-r` when called with directory;
|
* `prove_recursively` – adds `-r` when called with directory;
|
||||||
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
* `python_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||||
* `python_execute` – appends missing `.py` when executing Python files;
|
* `python_execute` – appends missing `.py` when executing Python files;
|
||||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
* `rm_dir` – adds `-rf` when you try to remove a directory;
|
||||||
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
||||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||||
* `sl_ls` – changes `sl` to `ls`;
|
* `sl_ls` – changes `sl` to `ls`;
|
||||||
@@ -267,6 +287,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`;
|
* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`;
|
||||||
* `tmux` – fixes `tmux` commands;
|
* `tmux` – fixes `tmux` commands;
|
||||||
* `unknown_command` – fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
|
* `unknown_command` – fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
|
||||||
|
* `unsudo` – removes `sudo` from previous command if a process refuses to run on super user privilege.
|
||||||
* `vagrant_up` – starts up the vagrant instance;
|
* `vagrant_up` – starts up the vagrant instance;
|
||||||
* `whois` – fixes `whois` command;
|
* `whois` – fixes `whois` command;
|
||||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
||||||
@@ -275,13 +296,16 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
|||||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
||||||
* `yarn_help` – makes it easier to open `yarn` documentation;
|
* `yarn_help` – makes it easier to open `yarn` documentation;
|
||||||
|
|
||||||
Enabled by default only on specific platforms:
|
The following rules are enabled by default on specific platforms only:
|
||||||
|
|
||||||
* `apt_get` – installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
|
* `apt_get` – installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
|
||||||
* `apt_get_search` – changes trying to search using `apt-get` with searching using `apt-cache`;
|
* `apt_get_search` – changes trying to search using `apt-get` with searching using `apt-cache`;
|
||||||
* `apt_invalid_operation` – fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
|
* `apt_invalid_operation` – fixes invalid `apt` and `apt-get` calls, like `apt-get isntall vim`;
|
||||||
|
* `apt_list_upgradable` – helps you run `apt list --upgradable` after `apt update`;
|
||||||
|
* `apt_upgrade` – helps you run `apt upgrade` after `apt list --upgradable`;
|
||||||
* `brew_cask_dependency` – installs cask dependencies;
|
* `brew_cask_dependency` – installs cask dependencies;
|
||||||
* `brew_install` – fixes formula name for `brew install`;
|
* `brew_install` – fixes formula name for `brew install`;
|
||||||
|
* `brew_reinstall` – turns `brew install <formula>` into `brew reinstall <formula>`;
|
||||||
* `brew_link` – adds `--overwrite --dry-run` if linking fails;
|
* `brew_link` – adds `--overwrite --dry-run` if linking fails;
|
||||||
* `brew_uninstall` – adds `--force` to `brew uninstall` if multiple versions were installed;
|
* `brew_uninstall` – adds `--force` to `brew uninstall` if multiple versions were installed;
|
||||||
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
* `brew_unknown_command` – fixes wrong brew commands, for example `brew docto/brew doctor`;
|
||||||
@@ -290,36 +314,40 @@ Enabled by default only on specific platforms:
|
|||||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
||||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
||||||
|
|
||||||
Bundled, but not enabled by default:
|
The following commands are bundled with *The Fuck*, but are not enabled by
|
||||||
|
default:
|
||||||
|
|
||||||
* `git_push_force` – adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
|
* `git_push_force` – adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
|
||||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||||
|
|
||||||
## Creating your own rules
|
## Creating your own rules
|
||||||
|
|
||||||
For adding your own rule you should create `your-rule-name.py`
|
To add your own rule, create a file named `your-rule-name.py`
|
||||||
in `~/.config/thefuck/rules`. The rule should contain two functions:
|
in `~/.config/thefuck/rules`. The rule file must contain two functions:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
match(command: Command) -> bool
|
match(command: Command) -> bool
|
||||||
get_new_command(command: Command) -> str | list[str]
|
get_new_command(command: Command) -> str | list[str]
|
||||||
```
|
```
|
||||||
|
|
||||||
Also the rule can contain an optional function
|
Additionally, rules can contain optional functions:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
side_effect(old_command: Command, fixed_command: str) -> None
|
side_effect(old_command: Command, fixed_command: str) -> None
|
||||||
```
|
```
|
||||||
and optional `enabled_by_default`, `requires_output` and `priority` variables.
|
Rules can also contain the optional variables `enabled_by_default`, `requires_output` and `priority`.
|
||||||
|
|
||||||
`Command` has three attributes: `script`, `output` and `script_parts`.
|
`Command` has three attributes: `script`, `output` and `script_parts`.
|
||||||
Rule shouldn't change `Command`.
|
Your rule should not change `Command`.
|
||||||
|
|
||||||
|
|
||||||
*Rules api changed in 3.0:* For accessing settings in rule you need to import it with `from thefuck.conf import settings`.
|
**Rules api changed in 3.0:** To access a rule's settings, import it with
|
||||||
`settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)).
|
`from thefuck.conf import settings`
|
||||||
|
|
||||||
Simple example of the rule for running script with `sudo`:
|
`settings` is a special object assembled from `~/.config/thefuck/settings.py`,
|
||||||
|
and values from env ([see more below](#settings)).
|
||||||
|
|
||||||
|
A simple example rule for running a script with `sudo`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def match(command):
|
def match(command):
|
||||||
@@ -347,7 +375,8 @@ requires_output = True
|
|||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
The Fuck has a few settings parameters which can be changed in `$XDG_CONFIG_HOME/thefuck/settings.py` (`$XDG_CONFIG_HOME` defaults to `~/.config`):
|
Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefuck/settings.py`
|
||||||
|
(`$XDG_CONFIG_HOME` defaults to `~/.config`):
|
||||||
|
|
||||||
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
|
* `rules` – list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
|
||||||
* `exclude_rules` – list of disabled rules, by default `[]`;
|
* `exclude_rules` – list of disabled rules, by default `[]`;
|
||||||
@@ -361,7 +390,7 @@ The Fuck has a few settings parameters which can be changed in `$XDG_CONFIG_HOME
|
|||||||
* `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
* `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
||||||
* `slow_commands` – list of slow commands.
|
* `slow_commands` – list of slow commands.
|
||||||
|
|
||||||
Example of `settings.py`:
|
An example of `settings.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
rules = ['sudo', 'no_command']
|
rules = ['sudo', 'no_command']
|
||||||
@@ -403,18 +432,39 @@ export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
|
|||||||
export THEFUCK_HISTORY_LIMIT='2000'
|
export THEFUCK_HISTORY_LIMIT='2000'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Third-party packages with rules
|
||||||
|
|
||||||
|
If you'd like to make a specific set of non-public rules, but would still like
|
||||||
|
to share them with others, create a package named `thefuck_contrib_*` with
|
||||||
|
the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
thefuck_contrib_foo
|
||||||
|
thefuck_contrib_foo
|
||||||
|
rules
|
||||||
|
__init__.py
|
||||||
|
*third-party rules*
|
||||||
|
__init__.py
|
||||||
|
*third-party-utils*
|
||||||
|
setup.py
|
||||||
|
```
|
||||||
|
|
||||||
|
*The Fuck* will find rules located in the `rules` module.
|
||||||
|
|
||||||
## Experimental instant mode
|
## Experimental instant mode
|
||||||
|
|
||||||
By default The Fuck reruns a previous command and that takes time,
|
The default behavior of *The Fuck* requires time to re-run previous commands.
|
||||||
in instant mode The Fuck logs output with [script](https://en.wikipedia.org/wiki/Script_(Unix))
|
When in instant mode, *The Fuck* saves time by logging output with [script](https://en.wikipedia.org/wiki/Script_(Unix)),
|
||||||
and just reads the log.
|
then reading the log.
|
||||||
|
|
||||||
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
|
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
|
||||||
|
|
||||||
At the moment only Python 3 with bash or zsh is supported.
|
Currently, instant mode only supports Python 3 with bash or zsh.
|
||||||
|
|
||||||
For enabling instant mode you need to add `--enable-experimental-instant-mode`
|
To enable instant mode, add `--enable-experimental-instant-mode`
|
||||||
to alias initialization in your `.bashrc`, `.bash_profile` or `.zshrc` like:
|
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
eval $(thefuck --alias --enable-experimental-instant-mode)
|
eval $(thefuck --alias --enable-experimental-instant-mode)
|
||||||
@@ -422,37 +472,7 @@ eval $(thefuck --alias --enable-experimental-instant-mode)
|
|||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Install `The Fuck` for development:
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
python setup.py develop
|
|
||||||
```
|
|
||||||
|
|
||||||
Run code style checks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flake8
|
|
||||||
```
|
|
||||||
|
|
||||||
Run unit tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
py.test
|
|
||||||
```
|
|
||||||
|
|
||||||
Run unit and functional tests (requires docker):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
py.test --enable-functional
|
|
||||||
```
|
|
||||||
|
|
||||||
For sending package to pypi:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install pandoc
|
|
||||||
./release.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## License MIT
|
## License MIT
|
||||||
Project License can be found [here](LICENSE.md).
|
Project License can be found [here](LICENSE.md).
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ build: false
|
|||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: "C:/Python27"
|
- PYTHON: "C:/Python27"
|
||||||
- PYTHON: "C:/Python33"
|
|
||||||
- PYTHON: "C:/Python34"
|
- PYTHON: "C:/Python34"
|
||||||
- PYTHON: "C:/Python35"
|
- PYTHON: "C:/Python35"
|
||||||
- PYTHON: "C:/Python36"
|
- PYTHON: "C:/Python36"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pip
|
|
||||||
flake8
|
flake8
|
||||||
pytest
|
pytest
|
||||||
mock
|
mock
|
||||||
|
|||||||
6
setup.py
6
setup.py
@@ -26,12 +26,12 @@ if version < (2, 7):
|
|||||||
print('thefuck requires Python version 2.7 or later' +
|
print('thefuck requires Python version 2.7 or later' +
|
||||||
' ({}.{} detected).'.format(*version))
|
' ({}.{} detected).'.format(*version))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
elif (3, 0) < version < (3, 3):
|
elif (3, 0) < version < (3, 4):
|
||||||
print('thefuck requires Python version 3.3 or later' +
|
print('thefuck requires Python version 3.4 or later' +
|
||||||
' ({}.{} detected).'.format(*version))
|
' ({}.{} detected).'.format(*version))
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
VERSION = '3.24'
|
VERSION = '3.27'
|
||||||
|
|
||||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||||
|
|||||||
41
tests/rules/test_adb_unknown_command.py
Normal file
41
tests/rules/test_adb_unknown_command.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.adb_unknown_command import match, get_new_command
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def output():
|
||||||
|
return '''Android Debug Bridge version 1.0.31
|
||||||
|
|
||||||
|
-d - directs command to the only connected USB device
|
||||||
|
returns an error if more than one USB device is present.
|
||||||
|
-e - directs command to the only running emulator.
|
||||||
|
returns an error if more than one emulator is running.
|
||||||
|
-s <specific device> - directs command to the device or emulator with the given
|
||||||
|
serial number or qualifier. Overrides ANDROID_SERIAL
|
||||||
|
environment variable.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
('adb lgcat'),
|
||||||
|
('adb puhs')])
|
||||||
|
def test_match(output, script):
|
||||||
|
assert match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
'git branch foo',
|
||||||
|
'abd push'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script, ''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, new_command', [
|
||||||
|
('adb puhs test.bin /sdcard/test.bin', 'adb push test.bin /sdcard/test.bin'),
|
||||||
|
('adb -s 1111 logcta', 'adb -s 1111 logcat'),
|
||||||
|
('adb -P 666 pulll /sdcard/test.bin', 'adb -P 666 pull /sdcard/test.bin'),
|
||||||
|
('adb -d logcatt', 'adb -d logcat'),
|
||||||
|
('adb -e reboott', 'adb -e reboot')])
|
||||||
|
def test_get_new_command(script, output, new_command):
|
||||||
|
assert get_new_command(Command(script, output)) == new_command
|
||||||
@@ -12,9 +12,8 @@ from thefuck.types import Command
|
|||||||
[('vim', 'main'), ('vim-tiny', 'main')])])
|
[('vim', 'main'), ('vim-tiny', 'main')])])
|
||||||
def test_match(mocker, command, packages):
|
def test_match(mocker, command, packages):
|
||||||
mocker.patch('thefuck.rules.apt_get.which', return_value=None)
|
mocker.patch('thefuck.rules.apt_get.which', return_value=None)
|
||||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||||
create=True)
|
create=True, return_value=packages)
|
||||||
mock.getPackages.return_value = packages
|
|
||||||
|
|
||||||
assert match(command)
|
assert match(command)
|
||||||
|
|
||||||
@@ -30,9 +29,8 @@ def test_match(mocker, command, packages):
|
|||||||
['vim'], '/usr/bin/vim')])
|
['vim'], '/usr/bin/vim')])
|
||||||
def test_not_match(mocker, command, packages, which):
|
def test_not_match(mocker, command, packages, which):
|
||||||
mocker.patch('thefuck.rules.apt_get.which', return_value=which)
|
mocker.patch('thefuck.rules.apt_get.which', return_value=which)
|
||||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||||
create=True)
|
create=True, return_value=packages)
|
||||||
mock.getPackages.return_value = packages
|
|
||||||
|
|
||||||
assert not match(command)
|
assert not match(command)
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ def test_not_match(mocker, command, packages, which):
|
|||||||
[('imagemagick', 'main'),
|
[('imagemagick', 'main'),
|
||||||
('graphicsmagick-imagemagick-compat', 'universe')])])
|
('graphicsmagick-imagemagick-compat', 'universe')])])
|
||||||
def test_get_new_command(mocker, command, new_command, packages):
|
def test_get_new_command(mocker, command, new_command, packages):
|
||||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||||
create=True)
|
create=True, return_value=packages)
|
||||||
mock.getPackages.return_value = packages
|
|
||||||
assert get_new_command(command) == new_command
|
assert get_new_command(command) == new_command
|
||||||
|
|||||||
75
tests/rules/test_apt_list_upgradable.py
Normal file
75
tests/rules/test_apt_list_upgradable.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.apt_list_upgradable import get_new_command, match
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
match_output = '''
|
||||||
|
Hit:1 http://us.archive.ubuntu.com/ubuntu zesty InRelease
|
||||||
|
Hit:2 http://us.archive.ubuntu.com/ubuntu zesty-updates InRelease
|
||||||
|
Get:3 http://us.archive.ubuntu.com/ubuntu zesty-backports InRelease [89.2 kB]
|
||||||
|
Hit:4 http://security.ubuntu.com/ubuntu zesty-security InRelease
|
||||||
|
Hit:5 http://ppa.launchpad.net/ubuntu-mozilla-daily/ppa/ubuntu zesty InRelease
|
||||||
|
Hit:6 https://download.docker.com/linux/ubuntu zesty InRelease
|
||||||
|
Hit:7 https://cli-assets.heroku.com/branches/stable/apt ./ InRelease
|
||||||
|
Fetched 89.2 kB in 0s (122 kB/s)
|
||||||
|
Reading package lists... Done
|
||||||
|
Building dependency tree
|
||||||
|
Reading state information... Done
|
||||||
|
8 packages can be upgraded. Run 'apt list --upgradable' to see them.
|
||||||
|
'''
|
||||||
|
|
||||||
|
no_match_output = '''
|
||||||
|
Hit:1 http://us.archive.ubuntu.com/ubuntu zesty InRelease
|
||||||
|
Get:2 http://us.archive.ubuntu.com/ubuntu zesty-updates InRelease [89.2 kB]
|
||||||
|
Get:3 http://us.archive.ubuntu.com/ubuntu zesty-backports InRelease [89.2 kB]
|
||||||
|
Get:4 http://security.ubuntu.com/ubuntu zesty-security InRelease [89.2 kB]
|
||||||
|
Hit:5 https://cli-assets.heroku.com/branches/stable/apt ./ InRelease
|
||||||
|
Hit:6 http://ppa.launchpad.net/ubuntu-mozilla-daily/ppa/ubuntu zesty InRelease
|
||||||
|
Hit:7 https://download.docker.com/linux/ubuntu zesty InRelease
|
||||||
|
Get:8 http://us.archive.ubuntu.com/ubuntu zesty-updates/main i386 Packages [232 kB]
|
||||||
|
Get:9 http://us.archive.ubuntu.com/ubuntu zesty-updates/main amd64 Packages [235 kB]
|
||||||
|
Get:10 http://us.archive.ubuntu.com/ubuntu zesty-updates/main amd64 DEP-11 Metadata [55.2 kB]
|
||||||
|
Get:11 http://us.archive.ubuntu.com/ubuntu zesty-updates/main DEP-11 64x64 Icons [32.3 kB]
|
||||||
|
Get:12 http://us.archive.ubuntu.com/ubuntu zesty-updates/universe amd64 Packages [156 kB]
|
||||||
|
Get:13 http://us.archive.ubuntu.com/ubuntu zesty-updates/universe i386 Packages [156 kB]
|
||||||
|
Get:14 http://us.archive.ubuntu.com/ubuntu zesty-updates/universe amd64 DEP-11 Metadata [175 kB]
|
||||||
|
Get:15 http://us.archive.ubuntu.com/ubuntu zesty-updates/universe DEP-11 64x64 Icons [253 kB]
|
||||||
|
Get:16 http://us.archive.ubuntu.com/ubuntu zesty-updates/multiverse amd64 DEP-11 Metadata [5,840 B]
|
||||||
|
Get:17 http://us.archive.ubuntu.com/ubuntu zesty-backports/universe amd64 DEP-11 Metadata [4,588 B]
|
||||||
|
Get:18 http://security.ubuntu.com/ubuntu zesty-security/main amd64 DEP-11 Metadata [12.7 kB]
|
||||||
|
Get:19 http://security.ubuntu.com/ubuntu zesty-security/main DEP-11 64x64 Icons [17.6 kB]
|
||||||
|
Get:20 http://security.ubuntu.com/ubuntu zesty-security/universe amd64 DEP-11 Metadata [21.6 kB]
|
||||||
|
Get:21 http://security.ubuntu.com/ubuntu zesty-security/universe DEP-11 64x64 Icons [47.7 kB]
|
||||||
|
Get:22 http://security.ubuntu.com/ubuntu zesty-security/multiverse amd64 DEP-11 Metadata [208 B]
|
||||||
|
Fetched 1,673 kB in 0s (1,716 kB/s)
|
||||||
|
Reading package lists... Done
|
||||||
|
Building dependency tree
|
||||||
|
Reading state information... Done
|
||||||
|
All packages are up to date.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_match():
|
||||||
|
assert match(Command('sudo apt update', match_output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('apt-cache search foo', ''),
|
||||||
|
Command('aptitude search foo', ''),
|
||||||
|
Command('apt search foo', ''),
|
||||||
|
Command('apt-get install foo', ''),
|
||||||
|
Command('apt-get source foo', ''),
|
||||||
|
Command('apt-get clean', ''),
|
||||||
|
Command('apt-get remove', ''),
|
||||||
|
Command('apt-get update', ''),
|
||||||
|
Command('sudo apt update', no_match_output)
|
||||||
|
])
|
||||||
|
def test_not_match(command):
|
||||||
|
assert not match(command)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_new_command():
|
||||||
|
new_command = get_new_command(Command('sudo apt update', match_output))
|
||||||
|
assert new_command == 'sudo apt list --upgradable'
|
||||||
|
|
||||||
|
new_command = get_new_command(Command('apt update', match_output))
|
||||||
|
assert new_command == 'apt list --upgradable'
|
||||||
36
tests/rules/test_apt_upgrade.py
Normal file
36
tests/rules/test_apt_upgrade.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.apt_upgrade import get_new_command, match
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
match_output = '''
|
||||||
|
Listing... Done
|
||||||
|
heroku/stable 6.15.2-1 amd64 [upgradable from: 6.14.43-1]
|
||||||
|
resolvconf/zesty-updates,zesty-updates 1.79ubuntu4.1 all [upgradable from: 1.79ubuntu4]
|
||||||
|
squashfs-tools/zesty-updates 1:4.3-3ubuntu2.17.04.1 amd64 [upgradable from: 1:4.3-3ubuntu2]
|
||||||
|
unattended-upgrades/zesty-updates,zesty-updates 0.93.1ubuntu2.4 all [upgradable from: 0.93.1ubuntu2.3]
|
||||||
|
'''
|
||||||
|
|
||||||
|
no_match_output = '''
|
||||||
|
Listing... Done
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def test_match():
|
||||||
|
assert match(Command('apt list --upgradable', match_output))
|
||||||
|
assert match(Command('sudo apt list --upgradable', match_output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('apt list --upgradable', no_match_output),
|
||||||
|
Command('sudo apt list --upgradable', no_match_output)
|
||||||
|
])
|
||||||
|
def test_not_match(command):
|
||||||
|
assert not match(command)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_new_command():
|
||||||
|
new_command = get_new_command(Command('apt list --upgradable', match_output))
|
||||||
|
assert new_command == 'apt upgrade'
|
||||||
|
|
||||||
|
new_command = get_new_command(Command('sudo apt list --upgradable', match_output))
|
||||||
|
assert new_command == 'sudo apt upgrade'
|
||||||
28
tests/rules/test_brew_reinstall.py
Normal file
28
tests/rules/test_brew_reinstall.py
Normal 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
|
||||||
23
tests/rules/test_cd_correction.py
Normal file
23
tests/rules/test_cd_correction.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.cd_correction import match
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('cd foo', 'cd: foo: No such file or directory'),
|
||||||
|
Command('cd foo/bar/baz',
|
||||||
|
'cd: foo: No such file or directory'),
|
||||||
|
Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz'),
|
||||||
|
Command('cd /foo/bar/', 'cd: The directory "/foo/bar/" does not exist')])
|
||||||
|
def test_match(command):
|
||||||
|
assert match(command)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('command', [
|
||||||
|
Command('cd foo', ''), Command('', '')])
|
||||||
|
def test_not_match(command):
|
||||||
|
assert not match(command)
|
||||||
|
|
||||||
|
|
||||||
|
# Note that get_new_command uses local filesystem, so not testing it here.
|
||||||
|
# Instead, see the functional test `functional.test_cd_correction`
|
||||||
@@ -7,7 +7,8 @@ from thefuck.types import Command
|
|||||||
Command('cd foo', 'cd: foo: No such file or directory'),
|
Command('cd foo', 'cd: foo: No such file or directory'),
|
||||||
Command('cd foo/bar/baz',
|
Command('cd foo/bar/baz',
|
||||||
'cd: foo: No such file or directory'),
|
'cd: foo: No such file or directory'),
|
||||||
Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz')])
|
Command('cd foo/bar/baz', 'cd: can\'t cd to foo/bar/baz'),
|
||||||
|
Command('cd /foo/bar/', 'cd: The directory "/foo/bar/" does not exist')])
|
||||||
def test_match(command):
|
def test_match(command):
|
||||||
assert match(command)
|
assert match(command)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from thefuck.types import Command
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def output(branch_name):
|
def output(src_branch_name):
|
||||||
return "fatal: A branch named '{}' already exists.".format(branch_name)
|
return "fatal: A branch named '{}' already exists.".format(src_branch_name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -17,18 +17,25 @@ def new_command(branch_name):
|
|||||||
'git branch -D {0} && git checkout -b {0}', 'git checkout {0}']]
|
'git branch -D {0} && git checkout -b {0}', 'git checkout {0}']]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('script, branch_name', [
|
@pytest.mark.parametrize('script, src_branch_name, branch_name', [
|
||||||
('git branch foo', 'foo'), ('git checkout bar', 'bar')])
|
('git branch foo', 'foo', 'foo'),
|
||||||
|
('git checkout bar', 'bar', 'bar'),
|
||||||
|
('git checkout -b "let\'s-push-this"', '"let\'s-push-this"', '"let\'s-push-this"')])
|
||||||
def test_match(output, script, branch_name):
|
def test_match(output, script, branch_name):
|
||||||
assert match(Command(script, output))
|
assert match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('script', ['git branch foo', 'git checkout bar'])
|
@pytest.mark.parametrize('script', [
|
||||||
|
'git branch foo',
|
||||||
|
'git checkout bar',
|
||||||
|
'git checkout -b "let\'s-push-this"'])
|
||||||
def test_not_match(script):
|
def test_not_match(script):
|
||||||
assert not match(Command(script, ''))
|
assert not match(Command(script, ''))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('script, branch_name, ', [
|
@pytest.mark.parametrize('script, src_branch_name, branch_name', [
|
||||||
('git branch foo', 'foo'), ('git checkout bar', 'bar')])
|
('git branch foo', 'foo', 'foo'),
|
||||||
def test_get_new_command(output, new_command, script, branch_name):
|
('git checkout bar', 'bar', 'bar'),
|
||||||
|
('git checkout -b "let\'s-push-this"', "let's-push-this", "let\\'s-push-this")])
|
||||||
|
def test_get_new_command(output, new_command, script, src_branch_name, branch_name):
|
||||||
assert get_new_command(Command(script, output)) == new_command
|
assert get_new_command(Command(script, output)) == new_command
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def test_get_branches(branches, branch_list, git_branch):
|
|||||||
@pytest.mark.parametrize('branches, command, new_command', [
|
@pytest.mark.parametrize('branches, command, new_command', [
|
||||||
(b'',
|
(b'',
|
||||||
Command('git checkout unknown', did_not_match('unknown')),
|
Command('git checkout unknown', did_not_match('unknown')),
|
||||||
'git branch unknown && git checkout unknown'),
|
'git checkout -b unknown'),
|
||||||
(b'',
|
(b'',
|
||||||
Command('git commit unknown', did_not_match('unknown')),
|
Command('git commit unknown', did_not_match('unknown')),
|
||||||
'git branch unknown && git commit unknown'),
|
'git branch unknown && git commit unknown'),
|
||||||
|
|||||||
25
tests/rules/test_git_commit_amend.py
Normal file
25
tests/rules/test_git_commit_amend.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.git_commit_amend import match, get_new_command
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, output', [
|
||||||
|
('git commit -m "test"', 'test output'),
|
||||||
|
('git commit', '')])
|
||||||
|
def test_match(output, script):
|
||||||
|
assert match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
'git branch foo',
|
||||||
|
'git checkout feature/test_commit',
|
||||||
|
'git push'])
|
||||||
|
def test_not_match(script):
|
||||||
|
assert not match(Command(script, ''))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script', [
|
||||||
|
('git commit -m "test commit"'),
|
||||||
|
('git commit')])
|
||||||
|
def test_get_new_command(script):
|
||||||
|
assert get_new_command(Command(script, '')) == 'git commit --amend'
|
||||||
@@ -8,10 +8,16 @@ command2 = Command('git log README.md -p CONTRIBUTING.md',
|
|||||||
"fatal: bad flag '-p' used after filename")
|
"fatal: bad flag '-p' used after filename")
|
||||||
command3 = Command('git log -p README.md --name-only',
|
command3 = Command('git log -p README.md --name-only',
|
||||||
"fatal: bad flag '--name-only' used after filename")
|
"fatal: bad flag '--name-only' used after filename")
|
||||||
|
command4 = Command('git log README.md -p',
|
||||||
|
"fatal: option '-p' must come before non-option arguments")
|
||||||
|
command5 = Command('git log README.md -p CONTRIBUTING.md',
|
||||||
|
"fatal: option '-p' must come before non-option arguments")
|
||||||
|
command6 = Command('git log -p README.md --name-only',
|
||||||
|
"fatal: option '--name-only' must come before non-option arguments")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('command', [
|
@pytest.mark.parametrize('command', [
|
||||||
command1, command2, command3])
|
command1, command2, command3, command4, command5, command6])
|
||||||
def test_match(command):
|
def test_match(command):
|
||||||
assert match(command)
|
assert match(command)
|
||||||
|
|
||||||
@@ -26,6 +32,9 @@ def test_not_match(command):
|
|||||||
@pytest.mark.parametrize('command, result', [
|
@pytest.mark.parametrize('command, result', [
|
||||||
(command1, "git log -p README.md"),
|
(command1, "git log -p README.md"),
|
||||||
(command2, "git log -p README.md CONTRIBUTING.md"),
|
(command2, "git log -p README.md CONTRIBUTING.md"),
|
||||||
(command3, "git log -p --name-only README.md")])
|
(command3, "git log -p --name-only README.md"),
|
||||||
|
(command4, "git log -p README.md"),
|
||||||
|
(command5, "git log -p README.md CONTRIBUTING.md"),
|
||||||
|
(command6, "git log -p --name-only README.md")])
|
||||||
def test_get_new_command(command, result):
|
def test_get_new_command(command, result):
|
||||||
assert get_new_command(command) == result
|
assert get_new_command(command) == result
|
||||||
|
|||||||
26
tests/rules/test_git_merge.py
Normal file
26
tests/rules/test_git_merge.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import pytest
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
def test_match(output):
|
||||||
|
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()),
|
||||||
|
'git merge remote/local'),
|
||||||
|
(Command('git merge -m "test" local', output()),
|
||||||
|
'git merge -m "test" remote/local'),
|
||||||
|
(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
|
||||||
25
tests/rules/test_git_merge_unrelated.py
Normal file
25
tests/rules/test_git_merge_unrelated.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import pytest
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
def test_match(output):
|
||||||
|
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()),
|
||||||
|
'git merge local --allow-unrelated-histories'),
|
||||||
|
(Command('git merge -m "test" local', output()),
|
||||||
|
'git merge -m "test" local --allow-unrelated-histories'),
|
||||||
|
(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
|
||||||
@@ -4,30 +4,68 @@ from thefuck.types import Command
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def output():
|
def output(branch_name):
|
||||||
return '''fatal: The current branch master has no upstream branch.
|
if not branch_name:
|
||||||
|
return ''
|
||||||
|
return '''fatal: The current branch {} has no upstream branch.
|
||||||
To push the current branch and set the remote as upstream, use
|
To push the current branch and set the remote as upstream, use
|
||||||
|
|
||||||
git push --set-upstream origin master
|
git push --set-upstream origin {}
|
||||||
|
|
||||||
|
'''.format(branch_name, branch_name)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def output_bitbucket():
|
||||||
|
return '''Total 0 (delta 0), reused 0 (delta 0)
|
||||||
|
remote:
|
||||||
|
remote: Create pull request for feature/set-upstream:
|
||||||
|
remote: https://bitbucket.org/set-upstream
|
||||||
|
remote:
|
||||||
|
To git@bitbucket.org:test.git
|
||||||
|
e5e7fbb..700d998 feature/set-upstream -> feature/set-upstream
|
||||||
|
Branch feature/set-upstream set up to track remote branch feature/set-upstream from origin.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
def test_match(output):
|
@pytest.mark.parametrize('script, branch_name', [
|
||||||
assert match(Command('git push', output))
|
('git push', 'master'),
|
||||||
assert match(Command('git push master', output))
|
('git push origin', 'master')])
|
||||||
assert not match(Command('git push master', ''))
|
def test_match(output, script, branch_name):
|
||||||
assert not match(Command('ls', output))
|
assert match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
def test_get_new_command(output):
|
def test_match_bitbucket(output_bitbucket):
|
||||||
assert get_new_command(Command('git push', output))\
|
assert not match(Command('git push origin', output_bitbucket))
|
||||||
== "git push --set-upstream origin master"
|
|
||||||
assert get_new_command(Command('git push -u', output))\
|
|
||||||
== "git push --set-upstream origin master"
|
@pytest.mark.parametrize('script, branch_name', [
|
||||||
assert get_new_command(Command('git push -u origin', output))\
|
('git push master', None),
|
||||||
== "git push --set-upstream origin master"
|
('ls', 'master')])
|
||||||
assert get_new_command(Command('git push --set-upstream origin', output))\
|
def test_not_match(output, script, branch_name):
|
||||||
== "git push --set-upstream origin master"
|
assert not match(Command(script, output))
|
||||||
assert get_new_command(Command('git push --quiet', output))\
|
|
||||||
== "git push --set-upstream origin master --quiet"
|
|
||||||
|
@pytest.mark.parametrize('script, branch_name, new_command', [
|
||||||
|
('git push', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push master', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push -u', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push -u origin', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push origin', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push --set-upstream origin', 'master',
|
||||||
|
'git push --set-upstream origin master'),
|
||||||
|
('git push --quiet', 'master',
|
||||||
|
'git push --set-upstream origin master --quiet'),
|
||||||
|
('git push --quiet origin', 'master',
|
||||||
|
'git push --set-upstream origin master --quiet'),
|
||||||
|
('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")])
|
||||||
|
def test_get_new_command(output, script, branch_name, new_command):
|
||||||
|
assert get_new_command(Command(script, output)) == new_command
|
||||||
|
|||||||
55
tests/rules/test_heroku_multiple_apps.py
Normal file
55
tests/rules/test_heroku_multiple_apps.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from thefuck.types import Command
|
||||||
|
from thefuck.rules.heroku_multiple_apps import match, get_new_command
|
||||||
|
|
||||||
|
|
||||||
|
suggest_output = '''
|
||||||
|
▸ Multiple apps in git remotes
|
||||||
|
▸ Usage: --remote heroku-dev
|
||||||
|
▸ or: --app myapp-dev
|
||||||
|
▸ Your local git repository has more than 1 app referenced in git remotes.
|
||||||
|
▸ Because of this, we can't determine which app you want to run this command against.
|
||||||
|
▸ Specify the app you want with --app or --remote.
|
||||||
|
▸ Heroku remotes in repo:
|
||||||
|
▸ myapp (heroku)
|
||||||
|
▸ myapp-dev (heroku-dev)
|
||||||
|
▸
|
||||||
|
▸ https://devcenter.heroku.com/articles/multiple-environments
|
||||||
|
'''
|
||||||
|
|
||||||
|
not_match_output = '''
|
||||||
|
=== HEROKU_POSTGRESQL_TEAL_URL, DATABASE_URL
|
||||||
|
Plan: Hobby-basic
|
||||||
|
Status: Available
|
||||||
|
Connections: 20/20
|
||||||
|
PG Version: 9.6.4
|
||||||
|
Created: 2017-01-01 00:00 UTC
|
||||||
|
Data Size: 99.9 MB
|
||||||
|
Tables: 99
|
||||||
|
Rows: 12345/10000000 (In compliance)
|
||||||
|
Fork/Follow: Unsupported
|
||||||
|
Rollback: Unsupported
|
||||||
|
Continuous Protection: Off
|
||||||
|
Add-on: postgresql-round-12345
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('cmd', ['pg'])
|
||||||
|
def test_match(cmd):
|
||||||
|
assert match(
|
||||||
|
Command('heroku {}'.format(cmd), suggest_output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('script, output', [
|
||||||
|
('heroku pg', not_match_output)])
|
||||||
|
def test_not_match(script, output):
|
||||||
|
assert not match(Command(script, output))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('cmd, result', [
|
||||||
|
('pg', ['heroku pg --app myapp', 'heroku pg --app myapp-dev'])])
|
||||||
|
def test_get_new_command(cmd, result):
|
||||||
|
command = Command('heroku {}'.format(cmd), suggest_output)
|
||||||
|
assert get_new_command(command) == result
|
||||||
22
tests/rules/test_long_form_help.py
Normal file
22
tests/rules/test_long_form_help.py
Normal 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
|
||||||
@@ -7,7 +7,9 @@ from thefuck.types import Command
|
|||||||
|
|
||||||
@pytest.mark.parametrize('command', [
|
@pytest.mark.parametrize('command', [
|
||||||
Command(u'фзе-пуе', 'command not found: фзе-пуе'),
|
Command(u'фзе-пуе', 'command not found: фзе-пуе'),
|
||||||
Command(u'λσ', 'command not found: λσ')])
|
Command(u'λσ', 'command not found: λσ'),
|
||||||
|
Command(u'שפא-עקא', 'command not found: שפא-עקא'),
|
||||||
|
Command(u'ךד', 'command not found: ךד')])
|
||||||
def test_match(command):
|
def test_match(command):
|
||||||
assert switch_lang.match(command)
|
assert switch_lang.match(command)
|
||||||
|
|
||||||
@@ -16,13 +18,16 @@ def test_match(command):
|
|||||||
Command(u'pat-get', 'command not found: pat-get'),
|
Command(u'pat-get', 'command not found: pat-get'),
|
||||||
Command(u'ls', 'command not found: ls'),
|
Command(u'ls', 'command not found: ls'),
|
||||||
Command(u'агсл', 'command not found: агсл'),
|
Command(u'агсл', 'command not found: агсл'),
|
||||||
Command(u'фзе-пуе', 'some info')])
|
Command(u'фзе-пуе', 'some info'),
|
||||||
|
Command(u'שפא-עקא', 'some info')])
|
||||||
def test_not_match(command):
|
def test_not_match(command):
|
||||||
assert not switch_lang.match(command)
|
assert not switch_lang.match(command)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('command, new_command', [
|
@pytest.mark.parametrize('command, new_command', [
|
||||||
(Command(u'фзе-пуе штыефдд мшь', ''), 'apt-get install vim'),
|
(Command(u'фзе-пуе штыефдд мшь', ''), 'apt-get install vim'),
|
||||||
(Command(u'λσ -λα', ''), 'ls -la')])
|
(Command(u'λσ -λα', ''), 'ls -la'),
|
||||||
|
(Command(u'שפא-עקא ןמדאשךך הןצ', ''), 'apt-get install vim'),
|
||||||
|
(Command(u'ךד -ךש', ''), 'ls -la')])
|
||||||
def test_get_new_command(command, new_command):
|
def test_get_new_command(command, new_command):
|
||||||
assert switch_lang.get_new_command(command) == new_command
|
assert switch_lang.get_new_command(command) == new_command
|
||||||
|
|||||||
22
tests/rules/test_unsudo.py
Normal file
22
tests/rules/test_unsudo.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import pytest
|
||||||
|
from thefuck.rules.unsudo import match, get_new_command
|
||||||
|
from thefuck.types import Command
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('output', [
|
||||||
|
'you cannot perform this operation as root'])
|
||||||
|
def test_match(output):
|
||||||
|
assert match(Command('sudo ls', output))
|
||||||
|
|
||||||
|
|
||||||
|
def test_not_match():
|
||||||
|
assert not match(Command('', ''))
|
||||||
|
assert not match(Command('sudo ls', 'Permission denied'))
|
||||||
|
assert not match(Command('ls', 'you cannot perform this operation as root'))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('before, after', [
|
||||||
|
('sudo ls', 'ls'),
|
||||||
|
('sudo pacaur -S helloworld', 'pacaur -S helloworld')])
|
||||||
|
def test_get_new_command(before, after):
|
||||||
|
assert get_new_command(Command(before, '')) == after
|
||||||
@@ -51,6 +51,7 @@ class TestBash(object):
|
|||||||
def test_app_alias_variables_correctly_set(self, shell):
|
def test_app_alias_variables_correctly_set(self, shell):
|
||||||
alias = shell.app_alias('fuck')
|
alias = shell.app_alias('fuck')
|
||||||
assert "fuck () {" in alias
|
assert "fuck () {" in alias
|
||||||
|
assert 'TF_SHELL=bash' in alias
|
||||||
assert "TF_ALIAS=fuck" in alias
|
assert "TF_ALIAS=fuck" in alias
|
||||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ class TestFish(object):
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def Popen(self, mocker):
|
def Popen(self, mocker):
|
||||||
mock = mocker.patch('thefuck.shells.fish.Popen')
|
mock = mocker.patch('thefuck.shells.fish.Popen')
|
||||||
mock.return_value.stdout.read.return_value = (
|
mock.return_value.stdout.read.side_effect = [(
|
||||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||||
b'man\nmath\npopd\npushd\nruby')
|
b'man\nmath\npopd\npushd\nruby'),
|
||||||
|
(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
|
return mock
|
||||||
|
|
||||||
@pytest.mark.parametrize('key, value', [
|
@pytest.mark.parametrize('key, value', [
|
||||||
@@ -42,7 +44,8 @@ class TestFish(object):
|
|||||||
('open', 'open'),
|
('open', 'open'),
|
||||||
('vim', 'vim'),
|
('vim', 'vim'),
|
||||||
('ll', 'fish -ic "ll"'),
|
('ll', 'fish -ic "ll"'),
|
||||||
('ls', 'ls')]) # Fish has no aliases but functions
|
('ls', 'ls'),
|
||||||
|
('g', 'git')])
|
||||||
def test_from_shell(self, before, after, shell):
|
def test_from_shell(self, before, after, shell):
|
||||||
assert shell.from_shell(before) == after
|
assert shell.from_shell(before) == after
|
||||||
|
|
||||||
@@ -65,12 +68,17 @@ class TestFish(object):
|
|||||||
'math': 'math',
|
'math': 'math',
|
||||||
'popd': 'popd',
|
'popd': 'popd',
|
||||||
'pushd': 'pushd',
|
'pushd': 'pushd',
|
||||||
'ruby': 'ruby'}
|
'ruby': 'ruby',
|
||||||
|
'g': 'git',
|
||||||
|
'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):
|
def test_app_alias(self, shell):
|
||||||
assert 'function fuck' in shell.app_alias('fuck')
|
assert 'function fuck' in shell.app_alias('fuck')
|
||||||
assert 'function FUCK' in shell.app_alias('FUCK')
|
assert 'function FUCK' in shell.app_alias('FUCK')
|
||||||
assert 'thefuck' in shell.app_alias('fuck')
|
assert 'thefuck' in shell.app_alias('fuck')
|
||||||
|
assert 'TF_SHELL=fish' in shell.app_alias('fuck')
|
||||||
assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck')
|
assert 'TF_ALIAS=fuck PYTHONIOENCODING' in shell.app_alias('fuck')
|
||||||
assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck')
|
assert 'PYTHONIOENCODING=utf-8 thefuck' in shell.app_alias('fuck')
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class TestTcsh(object):
|
|||||||
'll': 'ls -alF'}
|
'll': 'ls -alF'}
|
||||||
|
|
||||||
def test_app_alias(self, shell):
|
def test_app_alias(self, shell):
|
||||||
|
assert 'setenv TF_SHELL tcsh' in shell.app_alias('fuck')
|
||||||
assert 'alias fuck' in shell.app_alias('fuck')
|
assert 'alias fuck' in shell.app_alias('fuck')
|
||||||
assert 'alias FUCK' in shell.app_alias('FUCK')
|
assert 'alias FUCK' in shell.app_alias('FUCK')
|
||||||
assert 'thefuck' in shell.app_alias('fuck')
|
assert 'thefuck' in shell.app_alias('fuck')
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class TestZsh(object):
|
|||||||
def test_app_alias_variables_correctly_set(self, shell):
|
def test_app_alias_variables_correctly_set(self, shell):
|
||||||
alias = shell.app_alias('fuck')
|
alias = shell.app_alias('fuck')
|
||||||
assert "fuck () {" in alias
|
assert "fuck () {" in alias
|
||||||
|
assert 'TF_SHELL=zsh' in alias
|
||||||
assert "TF_ALIAS=fuck" in alias
|
assert "TF_ALIAS=fuck" in alias
|
||||||
assert 'PYTHONIOENCODING=utf-8' in alias
|
assert 'PYTHONIOENCODING=utf-8' in alias
|
||||||
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
assert 'TF_SHELL_ALIASES=$(alias)' in alias
|
||||||
|
|||||||
@@ -76,8 +76,12 @@ CONFIGURATION_TIMEOUT = 60
|
|||||||
|
|
||||||
USER_COMMAND_MARK = u'\u200B' * 10
|
USER_COMMAND_MARK = u'\u200B' * 10
|
||||||
|
|
||||||
LOG_SIZE = 1000
|
|
||||||
|
|
||||||
LOG_SIZE_IN_BYTES = 1024 * 1024
|
LOG_SIZE_IN_BYTES = 1024 * 1024
|
||||||
|
|
||||||
|
LOG_SIZE_TO_CLEAN = 10 * 1024
|
||||||
|
|
||||||
DIFF_WITH_ALIAS = 0.5
|
DIFF_WITH_ALIAS = 0.5
|
||||||
|
|
||||||
|
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
|
||||||
|
|
||||||
|
SHELL_LOGGER_LIMIT = 5
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import sys
|
||||||
from .conf import settings
|
from .conf import settings
|
||||||
from .types import Rule
|
from .types import Rule
|
||||||
from .system import Path
|
from .system import Path
|
||||||
@@ -18,17 +19,33 @@ def get_loaded_rules(rules_paths):
|
|||||||
yield rule
|
yield rule
|
||||||
|
|
||||||
|
|
||||||
|
def get_rules_import_paths():
|
||||||
|
"""Yields all rules import paths.
|
||||||
|
|
||||||
|
:rtype: Iterable[Path]
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Bundled rules:
|
||||||
|
yield Path(__file__).parent.joinpath('rules')
|
||||||
|
# Rules defined by user:
|
||||||
|
yield settings.user_dir.joinpath('rules')
|
||||||
|
# Packages with third-party rules:
|
||||||
|
for path in sys.path:
|
||||||
|
for contrib_module in Path(path).glob('thefuck_contrib_*'):
|
||||||
|
contrib_rules = contrib_module.joinpath('rules')
|
||||||
|
if contrib_rules.is_dir():
|
||||||
|
yield contrib_rules
|
||||||
|
|
||||||
|
|
||||||
def get_rules():
|
def get_rules():
|
||||||
"""Returns all enabled rules.
|
"""Returns all enabled rules.
|
||||||
|
|
||||||
:rtype: [Rule]
|
:rtype: [Rule]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
bundled = Path(__file__).parent \
|
paths = [rule_path for path in get_rules_import_paths()
|
||||||
.joinpath('rules') \
|
for rule_path in sorted(path.glob('*.py'))]
|
||||||
.glob('*.py')
|
return sorted(get_loaded_rules(paths),
|
||||||
user = settings.user_dir.joinpath('rules').glob('*.py')
|
|
||||||
return sorted(get_loaded_rules(sorted(bundled) + sorted(user)),
|
|
||||||
key=lambda rule: rule.priority)
|
key=lambda rule: rule.priority)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from ..argument_parser import Parser # noqa: E402
|
|||||||
from ..utils import get_installation_info # noqa: E402
|
from ..utils import get_installation_info # noqa: E402
|
||||||
from .alias import print_alias # noqa: E402
|
from .alias import print_alias # noqa: E402
|
||||||
from .fix_command import fix_command # noqa: E402
|
from .fix_command import fix_command # noqa: E402
|
||||||
from .shell_logger import shell_logger # noqa: E402
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -27,6 +26,11 @@ def main():
|
|||||||
elif known_args.alias:
|
elif known_args.alias:
|
||||||
print_alias(known_args)
|
print_alias(known_args)
|
||||||
elif known_args.shell_logger:
|
elif known_args.shell_logger:
|
||||||
|
try:
|
||||||
|
from .shell_logger import shell_logger # noqa: E402
|
||||||
|
except ImportError:
|
||||||
|
logs.warn('Shell logger supports only Linux and macOS')
|
||||||
|
else:
|
||||||
shell_logger(known_args.shell_logger)
|
shell_logger(known_args.shell_logger)
|
||||||
else:
|
else:
|
||||||
parser.print_usage()
|
parser.print_usage()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from ..system import init_output
|
|||||||
|
|
||||||
init_output()
|
init_output()
|
||||||
|
|
||||||
|
import getpass # noqa: E402
|
||||||
import os # noqa: E402
|
import os # noqa: E402
|
||||||
import json # noqa: E402
|
import json # noqa: E402
|
||||||
from tempfile import gettempdir # noqa: E402
|
from tempfile import gettempdir # noqa: E402
|
||||||
@@ -27,7 +28,9 @@ def _get_shell_pid():
|
|||||||
|
|
||||||
def _get_not_configured_usage_tracker_path():
|
def _get_not_configured_usage_tracker_path():
|
||||||
"""Returns path of special file where we store latest shell pid."""
|
"""Returns path of special file where we store latest shell pid."""
|
||||||
return Path(gettempdir()).joinpath('thefuck.last_not_configured_run')
|
return Path(gettempdir()).joinpath(u'thefuck.last_not_configured_run_{}'.format(
|
||||||
|
getpass.getuser(),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
def _record_first_run():
|
def _record_first_run():
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
import array
|
import array
|
||||||
import fcntl
|
import fcntl
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import mmap
|
||||||
import os
|
import os
|
||||||
import pty
|
import pty
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import termios
|
import termios
|
||||||
import tty
|
import tty
|
||||||
from ..logs import warn
|
from .. import logs, const
|
||||||
|
|
||||||
|
|
||||||
def _read(f, fd):
|
def _read(f, fd):
|
||||||
data = os.read(fd, 1024)
|
data = os.read(fd, 1024)
|
||||||
|
try:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
f.flush()
|
except ValueError:
|
||||||
|
position = const.LOG_SIZE_IN_BYTES - const.LOG_SIZE_TO_CLEAN
|
||||||
|
f.move(0, const.LOG_SIZE_TO_CLEAN, position)
|
||||||
|
f.seek(position)
|
||||||
|
f.write(b'\x00' * const.LOG_SIZE_TO_CLEAN)
|
||||||
|
f.seek(position)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -61,10 +68,12 @@ def shell_logger(output):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not os.environ.get('SHELL'):
|
if not os.environ.get('SHELL'):
|
||||||
warn("Shell logger doesn't support your platform.")
|
logs.warn("Shell logger doesn't support your platform.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with open(output, 'wb') as f:
|
fd = os.open(output, os.O_CREAT | os.O_TRUNC | os.O_RDWR)
|
||||||
return_code = _spawn(os.environ['SHELL'], partial(_read, f))
|
os.write(fd, b'\x00' * const.LOG_SIZE_IN_BYTES)
|
||||||
|
buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_WRITE)
|
||||||
|
return_code = _spawn(os.environ['SHELL'], partial(_read, buffer))
|
||||||
|
|
||||||
sys.exit(return_code)
|
sys.exit(return_code)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from ..conf import settings
|
from ..conf import settings
|
||||||
from . import read_log, rerun
|
from . import read_log, rerun, shell_logger
|
||||||
|
|
||||||
|
|
||||||
def get_output(script, expanded):
|
def get_output(script, expanded):
|
||||||
@@ -12,6 +12,8 @@ def get_output(script, expanded):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if shell_logger.is_available():
|
||||||
|
return shell_logger.get_output(script)
|
||||||
if settings.instant_mode:
|
if settings.instant_mode:
|
||||||
return read_log.get_output(script)
|
return read_log.get_output(script)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import mmap
|
||||||
|
import re
|
||||||
try:
|
try:
|
||||||
from shutil import get_terminal_size
|
from shutil import get_terminal_size
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -18,11 +20,6 @@ def _group_by_calls(log):
|
|||||||
script_line = None
|
script_line = None
|
||||||
lines = []
|
lines = []
|
||||||
for line in log:
|
for line in log:
|
||||||
try:
|
|
||||||
line = line.decode()
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if const.USER_COMMAND_MARK in line or ps1_counter > 0:
|
if const.USER_COMMAND_MARK in line or ps1_counter > 0:
|
||||||
if script_line and ps1_counter == 0:
|
if script_line and ps1_counter == 0:
|
||||||
yield script_line, lines
|
yield script_line, lines
|
||||||
@@ -53,13 +50,14 @@ def _get_script_group_lines(grouped, script):
|
|||||||
|
|
||||||
|
|
||||||
def _get_output_lines(script, log_file):
|
def _get_output_lines(script, log_file):
|
||||||
lines = log_file.readlines()[-const.LOG_SIZE:]
|
data = log_file.read().decode()
|
||||||
|
data = re.sub(r'\x00+$', '', data)
|
||||||
|
lines = data.split('\n')
|
||||||
grouped = list(_group_by_calls(lines))
|
grouped = list(_group_by_calls(lines))
|
||||||
script_lines = _get_script_group_lines(grouped, script)
|
script_lines = _get_script_group_lines(grouped, script)
|
||||||
|
|
||||||
screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
|
screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
|
||||||
stream = pyte.Stream(screen)
|
stream = pyte.Stream(screen)
|
||||||
stream.feed(''.join(script_lines))
|
stream.feed('\n'.join(script_lines))
|
||||||
return screen.display
|
return screen.display
|
||||||
|
|
||||||
|
|
||||||
@@ -91,10 +89,11 @@ def get_output(script):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with logs.debug_time(u'Read output from log'), \
|
with logs.debug_time(u'Read output from log'):
|
||||||
open(os.environ['THEFUCK_OUTPUT_LOG'], 'rb') as log_file:
|
fd = os.open(os.environ['THEFUCK_OUTPUT_LOG'], os.O_RDONLY)
|
||||||
_skip_old_lines(log_file)
|
buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_READ)
|
||||||
lines = _get_output_lines(script, log_file)
|
_skip_old_lines(buffer)
|
||||||
|
lines = _get_output_lines(script, buffer)
|
||||||
output = '\n'.join(lines).strip()
|
output = '\n'.join(lines).strip()
|
||||||
logs.debug(u'Received output: {}'.format(output))
|
logs.debug(u'Received output: {}'.format(output))
|
||||||
return output
|
return output
|
||||||
|
|||||||
60
thefuck/output_readers/shell_logger.py
Normal file
60
thefuck/output_readers/shell_logger.py
Normal 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
|
||||||
54
thefuck/rules/adb_unknown_command.py
Normal file
54
thefuck/rules/adb_unknown_command.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from thefuck.utils import is_app, get_closest, replace_argument
|
||||||
|
|
||||||
|
|
||||||
|
_ADB_COMMANDS = (
|
||||||
|
'backup',
|
||||||
|
'bugreport',
|
||||||
|
'connect',
|
||||||
|
'devices',
|
||||||
|
'disable-verity',
|
||||||
|
'disconnect',
|
||||||
|
'enable-verity',
|
||||||
|
'emu',
|
||||||
|
'forward',
|
||||||
|
'get-devpath',
|
||||||
|
'get-serialno',
|
||||||
|
'get-state',
|
||||||
|
'install',
|
||||||
|
'install-multiple',
|
||||||
|
'jdwp',
|
||||||
|
'keygen',
|
||||||
|
'kill-server',
|
||||||
|
'logcat',
|
||||||
|
'pull',
|
||||||
|
'push',
|
||||||
|
'reboot',
|
||||||
|
'reconnect',
|
||||||
|
'restore',
|
||||||
|
'reverse',
|
||||||
|
'root',
|
||||||
|
'run-as',
|
||||||
|
'shell',
|
||||||
|
'sideload',
|
||||||
|
'start-server',
|
||||||
|
'sync',
|
||||||
|
'tcpip',
|
||||||
|
'uninstall',
|
||||||
|
'unroot',
|
||||||
|
'usb',
|
||||||
|
'wait-for',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def match(command):
|
||||||
|
return (is_app(command, 'adb')
|
||||||
|
and command.output.startswith('Android Debug Bridge version'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
for idx, arg in enumerate(command.script_parts[1:]):
|
||||||
|
# allowed params to ADB are a/d/e/s/H/P/L where s, H, P and L take additional args
|
||||||
|
# for example 'adb -s 111 logcat' or 'adb -e logcat'
|
||||||
|
if not arg[0] == '-' and not command.script_parts[idx] in ('-s', '-H', '-P', '-L'):
|
||||||
|
adb_cmd = get_closest(arg, _ADB_COMMANDS)
|
||||||
|
return replace_argument(command.script, arg, adb_cmd)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from types import ModuleType
|
||||||
from thefuck.specific.apt import apt_available
|
from thefuck.specific.apt import apt_available
|
||||||
from thefuck.utils import memoize, which
|
from thefuck.utils import memoize, which
|
||||||
from thefuck.shells import shell
|
from thefuck.shells import shell
|
||||||
@@ -5,8 +6,14 @@ from thefuck.shells import shell
|
|||||||
try:
|
try:
|
||||||
from CommandNotFound import CommandNotFound
|
from CommandNotFound import CommandNotFound
|
||||||
|
|
||||||
command_not_found = CommandNotFound()
|
|
||||||
enabled_by_default = apt_available
|
enabled_by_default = apt_available
|
||||||
|
|
||||||
|
if isinstance(CommandNotFound, ModuleType):
|
||||||
|
# For ubuntu 18.04+
|
||||||
|
_get_packages = CommandNotFound.CommandNotFound().get_packages
|
||||||
|
else:
|
||||||
|
# For older versions
|
||||||
|
_get_packages = CommandNotFound().getPackages
|
||||||
except ImportError:
|
except ImportError:
|
||||||
enabled_by_default = False
|
enabled_by_default = False
|
||||||
|
|
||||||
@@ -21,7 +28,7 @@ def _get_executable(command):
|
|||||||
@memoize
|
@memoize
|
||||||
def get_package(executable):
|
def get_package(executable):
|
||||||
try:
|
try:
|
||||||
packages = command_not_found.getPackages(executable)
|
packages = _get_packages(executable)
|
||||||
return packages[0][0]
|
return packages[0][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# IndexError is thrown when no matching package is found
|
# IndexError is thrown when no matching package is found
|
||||||
|
|||||||
16
thefuck/rules/apt_list_upgradable.py
Normal file
16
thefuck/rules/apt_list_upgradable.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from thefuck.specific.apt import apt_available
|
||||||
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
enabled_by_default = apt_available
|
||||||
|
|
||||||
|
|
||||||
|
@sudo_support
|
||||||
|
@for_app('apt')
|
||||||
|
def match(command):
|
||||||
|
return "Run 'apt list --upgradable' to see them." in command.output
|
||||||
|
|
||||||
|
|
||||||
|
@sudo_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return 'apt list --upgradable'
|
||||||
16
thefuck/rules/apt_upgrade.py
Normal file
16
thefuck/rules/apt_upgrade.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from thefuck.specific.apt import apt_available
|
||||||
|
from thefuck.specific.sudo import sudo_support
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
enabled_by_default = apt_available
|
||||||
|
|
||||||
|
|
||||||
|
@sudo_support
|
||||||
|
@for_app('apt')
|
||||||
|
def match(command):
|
||||||
|
return command.script == "apt list --upgradable" and len(command.output.strip().split('\n')) > 1
|
||||||
|
|
||||||
|
|
||||||
|
@sudo_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return 'apt upgrade'
|
||||||
@@ -15,7 +15,7 @@ def _get_formulas():
|
|||||||
for file_name in os.listdir(brew_formula_path):
|
for file_name in os.listdir(brew_formula_path):
|
||||||
if file_name.endswith('.rb'):
|
if file_name.endswith('.rb'):
|
||||||
yield file_name[:-3]
|
yield file_name[:-3]
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
thefuck/rules/brew_reinstall.py
Normal file
19
thefuck/rules/brew_reinstall.py
Normal 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')
|
||||||
@@ -21,9 +21,12 @@ def _get_sub_dirs(parent):
|
|||||||
@for_app('cd')
|
@for_app('cd')
|
||||||
def match(command):
|
def match(command):
|
||||||
"""Match function copied from cd_mkdir.py"""
|
"""Match function copied from cd_mkdir.py"""
|
||||||
return (command.script.startswith('cd ')
|
return (
|
||||||
and ('no such file or directory' in command.output.lower()
|
command.script.startswith('cd ') and any((
|
||||||
or 'cd: can\'t cd to' in command.output.lower()))
|
'no such file or directory' in command.output.lower(),
|
||||||
|
'cd: can\'t cd to' in command.output.lower(),
|
||||||
|
'does not exist' in command.output.lower()
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
@@ -37,7 +40,11 @@ def get_new_command(command):
|
|||||||
dest = command.script_parts[1].split(os.sep)
|
dest = command.script_parts[1].split(os.sep)
|
||||||
if dest[-1] == '':
|
if dest[-1] == '':
|
||||||
dest = dest[:-1]
|
dest = dest[:-1]
|
||||||
if six.PY2:
|
|
||||||
|
if dest[0] == '':
|
||||||
|
cwd = os.sep
|
||||||
|
dest = dest[1:]
|
||||||
|
elif six.PY2:
|
||||||
cwd = os.getcwdu()
|
cwd = os.getcwdu()
|
||||||
else:
|
else:
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ from thefuck.shells import shell
|
|||||||
@for_app('cd')
|
@for_app('cd')
|
||||||
def match(command):
|
def match(command):
|
||||||
return (
|
return (
|
||||||
'no such file or directory' in command.output.lower()
|
command.script.startswith('cd ') and any((
|
||||||
or 'cd: can\'t cd to' in command.output.lower()
|
'no such file or directory' in command.output.lower(),
|
||||||
or 'the system cannot find the path specified.' in command.output.lower()
|
'cd: can\'t cd to' in command.output.lower(),
|
||||||
)
|
'does not exist' in command.output.lower()
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
@sudo_support
|
@sudo_support
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ def _is_bad_zip(file):
|
|||||||
try:
|
try:
|
||||||
with zipfile.ZipFile(file, 'r') as archive:
|
with zipfile.ZipFile(file, 'r') as archive:
|
||||||
return len(archive.namelist()) > 1
|
return len(archive.namelist()) > 1
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ from thefuck.utils import eager
|
|||||||
@git_support
|
@git_support
|
||||||
def match(command):
|
def match(command):
|
||||||
return ("fatal: A branch named '" in command.output
|
return ("fatal: A branch named '" in command.output
|
||||||
and " already exists." in command.output)
|
and "' already exists." in command.output)
|
||||||
|
|
||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
@eager
|
@eager
|
||||||
def get_new_command(command):
|
def get_new_command(command):
|
||||||
branch_name = re.findall(
|
branch_name = re.findall(
|
||||||
r"fatal: A branch named '([^']*)' already exists.", command.output)[0]
|
r"fatal: A branch named '(.+)' already exists.", command.output)[0]
|
||||||
|
branch_name = branch_name.replace("'", r"\'")
|
||||||
new_command_templates = [['git branch -d {0}', 'git branch {0}'],
|
new_command_templates = [['git branch -d {0}', 'git branch {0}'],
|
||||||
['git branch -d {0}', 'git checkout -b {0}'],
|
['git branch -d {0}', 'git checkout -b {0}'],
|
||||||
['git branch -D {0}', 'git branch {0}'],
|
['git branch -D {0}', 'git branch {0}'],
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ def get_new_command(command):
|
|||||||
fallback_to_first=False)
|
fallback_to_first=False)
|
||||||
if closest_branch:
|
if closest_branch:
|
||||||
return replace_argument(command.script, missing_file, closest_branch)
|
return replace_argument(command.script, missing_file, closest_branch)
|
||||||
|
elif command.script_parts[1] == 'checkout':
|
||||||
|
return replace_argument(command.script, 'checkout', 'checkout -b')
|
||||||
else:
|
else:
|
||||||
return shell.and_('git branch {}', '{}').format(
|
return shell.and_('git branch {}', '{}').format(
|
||||||
missing_file, command.script)
|
missing_file, command.script)
|
||||||
|
|||||||
11
thefuck/rules/git_commit_amend.py
Normal file
11
thefuck/rules/git_commit_amend.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('commit' in command.script_parts)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return 'git commit --amend'
|
||||||
@@ -2,11 +2,12 @@ import re
|
|||||||
from thefuck.specific.git import git_support
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
error_pattern = "fatal: bad flag '(.*?)' used after filename"
|
error_pattern = "fatal: bad flag '(.*?)' used after filename"
|
||||||
|
error_pattern2 = "fatal: option '(.*?)' must come before non-option arguments"
|
||||||
|
|
||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
def match(command):
|
def match(command):
|
||||||
return re.search(error_pattern, command.output)
|
return re.search(error_pattern, command.output) or re.search(error_pattern2, command.output)
|
||||||
|
|
||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
@@ -14,7 +15,7 @@ def get_new_command(command):
|
|||||||
command_parts = command.script_parts[:]
|
command_parts = command.script_parts[:]
|
||||||
|
|
||||||
# find the bad flag
|
# find the bad flag
|
||||||
bad_flag = re.search(error_pattern, command.output).group(1)
|
bad_flag = match(command).group(1)
|
||||||
bad_flag_index = command_parts.index(bad_flag)
|
bad_flag_index = command_parts.index(bad_flag)
|
||||||
|
|
||||||
# find the filename
|
# find the filename
|
||||||
|
|||||||
18
thefuck/rules/git_merge.py
Normal file
18
thefuck/rules/git_merge.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import re
|
||||||
|
from thefuck.utils import replace_argument
|
||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('merge' in command.script
|
||||||
|
and ' - not something we can merge' in command.output
|
||||||
|
and 'Did you mean this?' in command.output)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
unknown_branch = re.findall(r'merge: (.+) - not something we can merge', command.output)[0]
|
||||||
|
remote_branch = re.findall(r'Did you mean this\?\n\t([^\n]+)', command.output)[0]
|
||||||
|
|
||||||
|
return replace_argument(command.script, unknown_branch, remote_branch)
|
||||||
12
thefuck/rules/git_merge_unrelated.py
Normal file
12
thefuck/rules/git_merge_unrelated.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from thefuck.specific.git import git_support
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def match(command):
|
||||||
|
return ('merge' in command.script
|
||||||
|
and 'fatal: refusing to merge unrelated histories' in command.output)
|
||||||
|
|
||||||
|
|
||||||
|
@git_support
|
||||||
|
def get_new_command(command):
|
||||||
|
return command.script + ' --allow-unrelated-histories'
|
||||||
@@ -5,8 +5,8 @@ from thefuck.specific.git import git_support
|
|||||||
|
|
||||||
@git_support
|
@git_support
|
||||||
def match(command):
|
def match(command):
|
||||||
return ('push' in command.script
|
return ('push' in command.script_parts
|
||||||
and 'set-upstream' in command.output)
|
and 'git push --set-upstream' in command.output)
|
||||||
|
|
||||||
|
|
||||||
def _get_upstream_option_index(command_parts):
|
def _get_upstream_option_index(command_parts):
|
||||||
@@ -32,7 +32,13 @@ def get_new_command(command):
|
|||||||
# In case of `git push -u` we don't have next argument:
|
# In case of `git push -u` we don't have next argument:
|
||||||
if len(command_parts) > upstream_option_index:
|
if len(command_parts) > upstream_option_index:
|
||||||
command_parts.pop(upstream_option_index)
|
command_parts.pop(upstream_option_index)
|
||||||
|
else:
|
||||||
|
# the only non-qualified permitted options are the repository and refspec; git's
|
||||||
|
# suggestion include them, so they won't be lost, but would be duplicated otherwise.
|
||||||
|
push_idx = command_parts.index('push') + 1
|
||||||
|
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].strip()
|
arguments = re.findall(r'git push (.*)', command.output)[0].replace("'", r"\'").strip()
|
||||||
return replace_argument(" ".join(command_parts), 'push',
|
return replace_argument(" ".join(command_parts), 'push',
|
||||||
'push {}'.format(arguments))
|
'push {}'.format(arguments))
|
||||||
|
|||||||
12
thefuck/rules/heroku_multiple_apps.py
Normal file
12
thefuck/rules/heroku_multiple_apps.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import re
|
||||||
|
from thefuck.utils import for_app
|
||||||
|
|
||||||
|
|
||||||
|
@for_app('heroku')
|
||||||
|
def match(command):
|
||||||
|
return 'https://devcenter.heroku.com/articles/multiple-environments' in command.output
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
apps = re.findall('([^ ]*) \([^)]*\)', command.output)
|
||||||
|
return [command.script + ' --app ' + app for app in apps]
|
||||||
27
thefuck/rules/long_form_help.py
Normal file
27
thefuck/rules/long_form_help.py
Normal 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
|
||||||
@@ -5,7 +5,8 @@ target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVB
|
|||||||
|
|
||||||
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
|
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
|
||||||
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
|
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
|
||||||
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
|
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''',
|
||||||
|
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''']
|
||||||
|
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
|
|||||||
15
thefuck/rules/unsudo.py
Normal file
15
thefuck/rules/unsudo.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
patterns = ['you cannot perform this operation as root']
|
||||||
|
|
||||||
|
|
||||||
|
def match(command):
|
||||||
|
if command.script_parts and command.script_parts[0] != 'sudo':
|
||||||
|
return False
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
if pattern in command.output.lower():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_command(command):
|
||||||
|
return ' '.join(command.script_parts[1:])
|
||||||
@@ -20,9 +20,7 @@ shells = {'bash': Bash,
|
|||||||
|
|
||||||
|
|
||||||
def _get_shell_from_env():
|
def _get_shell_from_env():
|
||||||
path = os.environ.get('SHELL', '')
|
name = os.environ.get('TF_SHELL')
|
||||||
base_name = os.path.basename(path)
|
|
||||||
name = os.path.splitext(base_name)[0]
|
|
||||||
|
|
||||||
if name in shells:
|
if name in shells:
|
||||||
return shells[name]()
|
return shells[name]()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class Bash(Generic):
|
|||||||
return '''
|
return '''
|
||||||
function {name} () {{
|
function {name} () {{
|
||||||
TF_PYTHONIOENCODING=$PYTHONIOENCODING;
|
TF_PYTHONIOENCODING=$PYTHONIOENCODING;
|
||||||
|
export TF_SHELL=bash;
|
||||||
export TF_ALIAS={name};
|
export TF_ALIAS={name};
|
||||||
export TF_SHELL_ALIASES=$(alias);
|
export TF_SHELL_ALIASES=$(alias);
|
||||||
export TF_HISTORY=$(fc -ln -10);
|
export TF_HISTORY=$(fc -ln -10);
|
||||||
|
|||||||
@@ -10,12 +10,32 @@ from .generic import Generic
|
|||||||
|
|
||||||
|
|
||||||
@cache('~/.config/fish/config.fish', '~/.config/fish/functions')
|
@cache('~/.config/fish/config.fish', '~/.config/fish/functions')
|
||||||
def _get_aliases(overridden):
|
def _get_functions(overridden):
|
||||||
proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
|
proc = Popen(['fish', '-ic', 'functions'], stdout=PIPE, stderr=DEVNULL)
|
||||||
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
|
functions = proc.stdout.read().decode('utf-8').strip().split('\n')
|
||||||
return {func: func for func in functions if func not in overridden}
|
return {func: func for func in functions if func not in overridden}
|
||||||
|
|
||||||
|
|
||||||
|
@cache('~/.config/fish/config.fish')
|
||||||
|
def _get_aliases(overridden):
|
||||||
|
aliases = {}
|
||||||
|
proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class Fish(Generic):
|
class Fish(Generic):
|
||||||
def _get_overridden_aliases(self):
|
def _get_overridden_aliases(self):
|
||||||
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
|
||||||
@@ -35,7 +55,7 @@ class Fish(Generic):
|
|||||||
# It is VERY important to have the variables declared WITHIN the alias
|
# It is VERY important to have the variables declared WITHIN the alias
|
||||||
return ('function {0} -d "Correct your previous console command"\n'
|
return ('function {0} -d "Correct your previous console command"\n'
|
||||||
' set -l fucked_up_command $history[1]\n'
|
' set -l fucked_up_command $history[1]\n'
|
||||||
' env TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
' env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
|
||||||
' thefuck $fucked_up_command | read -l unfucked_command\n'
|
' thefuck $fucked_up_command | read -l unfucked_command\n'
|
||||||
' if [ "$unfucked_command" != "" ]\n'
|
' if [ "$unfucked_command" != "" ]\n'
|
||||||
' eval $unfucked_command\n{1}'
|
' eval $unfucked_command\n{1}'
|
||||||
@@ -44,12 +64,17 @@ class Fish(Generic):
|
|||||||
|
|
||||||
def get_aliases(self):
|
def get_aliases(self):
|
||||||
overridden = self._get_overridden_aliases()
|
overridden = self._get_overridden_aliases()
|
||||||
return _get_aliases(overridden)
|
functions = _get_functions(overridden)
|
||||||
|
raw_aliases = _get_aliases(overridden)
|
||||||
|
functions.update(raw_aliases)
|
||||||
|
return functions
|
||||||
|
|
||||||
def _expand_aliases(self, command_script):
|
def _expand_aliases(self, command_script):
|
||||||
aliases = self.get_aliases()
|
aliases = self.get_aliases()
|
||||||
binary = command_script.split(' ')[0]
|
binary = command_script.split(' ')[0]
|
||||||
if binary in aliases:
|
if binary in aliases and aliases[binary] != binary:
|
||||||
|
return command_script.replace(binary, aliases[binary], 1)
|
||||||
|
elif binary in aliases:
|
||||||
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
|
return u'fish -ic "{}"'.format(command_script.replace('"', r'\"'))
|
||||||
else:
|
else:
|
||||||
return command_script
|
return command_script
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Powershell(Generic):
|
|||||||
' else { iex "$fuck"; }\n' \
|
' else { iex "$fuck"; }\n' \
|
||||||
' }\n' \
|
' }\n' \
|
||||||
' }\n' \
|
' }\n' \
|
||||||
|
' [Console]::ResetColor() \n' \
|
||||||
'}\n'
|
'}\n'
|
||||||
|
|
||||||
def and_(self, *commands):
|
def and_(self, *commands):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from .generic import Generic
|
|||||||
|
|
||||||
class Tcsh(Generic):
|
class Tcsh(Generic):
|
||||||
def app_alias(self, alias_name):
|
def app_alias(self, alias_name):
|
||||||
return ("alias {0} 'setenv TF_ALIAS {0} && "
|
return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
|
||||||
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
"set fucked_cmd=`history -h 2 | head -n 1` && "
|
||||||
"eval `thefuck ${{fucked_cmd}}`'").format(alias_name)
|
"eval `thefuck ${{fucked_cmd}}`'").format(alias_name)
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,17 @@ class Zsh(Generic):
|
|||||||
# It is VERY important to have the variables declared WITHIN the function
|
# It is VERY important to have the variables declared WITHIN the function
|
||||||
return '''
|
return '''
|
||||||
{name} () {{
|
{name} () {{
|
||||||
TF_HISTORY=$(fc -ln -10)
|
TF_PYTHONIOENCODING=$PYTHONIOENCODING;
|
||||||
|
export TF_SHELL=zsh;
|
||||||
|
export TF_ALIAS={name};
|
||||||
|
export TF_SHELL_ALIASES=$(alias);
|
||||||
|
export TF_HISTORY="$(fc -ln -10)";
|
||||||
|
export PYTHONIOENCODING=utf-8;
|
||||||
TF_CMD=$(
|
TF_CMD=$(
|
||||||
TF_ALIAS={name}
|
thefuck {argument_placeholder} $@
|
||||||
TF_SHELL_ALIASES=$(alias)
|
|
||||||
TF_HISTORY=$TF_HISTORY
|
|
||||||
PYTHONIOENCODING=utf-8
|
|
||||||
thefuck {argument_placeholder} $*
|
|
||||||
) && eval $TF_CMD;
|
) && eval $TF_CMD;
|
||||||
|
unset TF_HISTORY;
|
||||||
|
export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
|
||||||
{alter_history}
|
{alter_history}
|
||||||
}}
|
}}
|
||||||
'''.format(
|
'''.format(
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ def get_brew_path_prefix():
|
|||||||
try:
|
try:
|
||||||
return subprocess.check_output(['brew', '--prefix'],
|
return subprocess.check_output(['brew', '--prefix'],
|
||||||
universal_newlines=True).strip()
|
universal_newlines=True).strip()
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user