mirror of
https://github.com/nvbn/thefuck.git
synced 2025-03-14 06:38:32 +00:00
Merge branch 'master' of https://github.com/nvbn/thefuck into bugfix/apt_get
This commit is contained in:
commit
c558438bc2
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.py]
|
||||
max_line_length = 119
|
11
.github/ISSUE_TEMPLATE.md
vendored
11
.github/ISSUE_TEMPLATE.md
vendored
@ -6,17 +6,14 @@ update The Fuck and see if the bug is still there. -->
|
||||
if not, just open an issue on [GitHub](https://github.com/nvbn/thefuck) with
|
||||
the following basic information: -->
|
||||
|
||||
The output of `thefuck --version` (something like `The Fuck 3.1 using Python 3.5.0`):
|
||||
|
||||
FILL THIS IN
|
||||
|
||||
Your shell and its version (`bash`, `zsh`, *Windows PowerShell*, etc.):
|
||||
The output of `thefuck --version` (something like `The Fuck 3.1 using Python
|
||||
3.5.0 and Bash 4.4.12(1)-release`):
|
||||
|
||||
FILL THIS IN
|
||||
|
||||
Your system (Debian 7, ArchLinux, Windows, etc.):
|
||||
|
||||
<!-- FILL THIS IN -->
|
||||
FILL THIS IN
|
||||
|
||||
How to reproduce the bug:
|
||||
|
||||
@ -32,6 +29,6 @@ If the bug only appears with a specific application, the output of that applicat
|
||||
|
||||
Anything else you think is relevant:
|
||||
|
||||
<!-- FILL THIS IN -->
|
||||
FILL THIS IN
|
||||
|
||||
<!-- It's only with enough information that we can do something to fix the problem. -->
|
||||
|
46
.travis.yml
46
.travis.yml
@ -1,25 +1,25 @@
|
||||
language: python
|
||||
sudo: false
|
||||
os: linux
|
||||
dist: xenial
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
python: "3.6"
|
||||
- os: linux
|
||||
dist: trusty
|
||||
python: "3.5"
|
||||
- os: linux
|
||||
dist: trusty
|
||||
python: "3.4"
|
||||
- os: linux
|
||||
dist: trusty
|
||||
python: "2.7"
|
||||
- python: "nightly"
|
||||
- python: "3.8-dev"
|
||||
- python: "3.8"
|
||||
- python: "3.7-dev"
|
||||
- python: "3.7"
|
||||
- python: "3.6-dev"
|
||||
- python: "3.6"
|
||||
- python: "3.5"
|
||||
- python: "2.7"
|
||||
- os: osx
|
||||
env: FORMULA="python"
|
||||
language: generic
|
||||
- os: osx
|
||||
env: FORMULA="python3"
|
||||
language: generic
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
- python: 3.8-dev
|
||||
- python: 3.7-dev
|
||||
- python: 3.6-dev
|
||||
services:
|
||||
- docker
|
||||
addons:
|
||||
@ -28,9 +28,11 @@ addons:
|
||||
- python-commandnotfound
|
||||
- python3-commandnotfound
|
||||
before_install:
|
||||
- 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 virtualenv venv -p $FORMULA; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then rm -rf /usr/local/include/c++; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
|
||||
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
|
||||
- pip install -U pip
|
||||
- pip install -U coveralls
|
||||
@ -42,7 +44,7 @@ script:
|
||||
- flake8
|
||||
- export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
|
||||
- export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests"
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3.8 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 3.8 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
|
||||
after_success:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3.8 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
|
||||
|
@ -26,6 +26,8 @@ fixes, etc.
|
||||
|
||||
# Developing
|
||||
|
||||
[Create and activate a Python 3 virtual environment.](https://docs.python.org/3/tutorial/venv.html)
|
||||
|
||||
Install `The Fuck` for development:
|
||||
|
||||
```bash
|
||||
|
159
README.md
159
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)
|
||||
|
||||
Magnificent app which corrects your previous console command,
|
||||
inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
[tweet](https://twitter.com/liamosaur/status/506975850596536320).
|
||||
*The Fuck* is a magnificent app, inspired by a [@liamosaur](https://twitter.com/liamosaur/)
|
||||
[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]
|
||||
|
||||
Few more examples:
|
||||
More examples:
|
||||
|
||||
```bash
|
||||
➜ 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`
|
||||
[settings](#settings) option:
|
||||
If you're not afraid of blindly running corrected commands, the
|
||||
`require_confirmation` [settings](#settings) option can be disabled:
|
||||
|
||||
```bash
|
||||
➜ apt-get install vim
|
||||
@ -98,29 +99,40 @@ Reading package lists... Done
|
||||
|
||||
## Installation
|
||||
|
||||
On OS X you can install `The Fuck` with [Homebrew][homebrew]:
|
||||
On OS X, you can install *The Fuck* via [Homebrew][homebrew] (or via [Linuxbrew][linuxbrew] on Linux):
|
||||
|
||||
```bash
|
||||
brew install thefuck
|
||||
```
|
||||
|
||||
On Ubuntu you can install `The Fuck` with:
|
||||
On Ubuntu / Mint, install *The Fuck* with the following commands:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install python3-dev python3-pip
|
||||
sudo apt install python3-dev python3-pip python3-setuptools
|
||||
sudo pip3 install thefuck
|
||||
```
|
||||
|
||||
On other systems you can install `The Fuck` with `pip`:
|
||||
On FreeBSD, install *The Fuck* with the following commands:
|
||||
```bash
|
||||
pkg install thefuck
|
||||
```
|
||||
|
||||
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
|
||||
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>
|
||||
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
|
||||
eval $(thefuck --alias)
|
||||
@ -130,43 +142,47 @@ eval $(thefuck --alias FUCK)
|
||||
|
||||
[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.
|
||||
To make them available immediately, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
|
||||
Changes are only available in a new shell session. To make changes immediately
|
||||
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 `--yeah` option (or just `-y` for short, or `--hard` if you're especially frustrated):
|
||||
|
||||
```bash
|
||||
fuck -y
|
||||
fuck --yeah
|
||||
```
|
||||
|
||||
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
|
||||
fuck -r
|
||||
```
|
||||
|
||||
## Update
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
pip3 install thefuck --upgrade
|
||||
```
|
||||
|
||||
**Aliases changed in 1.34.**
|
||||
**Note: Alias functionality was changed in v1.34 of *The Fuck***
|
||||
|
||||
## How it works
|
||||
|
||||
The Fuck tries to match a rule for the previous command, creates a new command
|
||||
using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
*The Fuck* attempts to match the previous command with a rule. If a match is
|
||||
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;
|
||||
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
|
||||
* `az_cli` – fixes misspelled commands like `az providers`;
|
||||
* `cargo` – runs `cargo build` instead of `cargo`;
|
||||
* `cargo_no_command` – fixes wrongs commands like `cargo buid`;
|
||||
* `cat_dir` – replaces `cat` with `ls` when you try to `cat` a directory;
|
||||
* `cd_correction` – spellchecks and correct failed cd commands;
|
||||
* `cd_mkdir` – creates directories before cd'ing into them;
|
||||
* `cd_parent` – changes `cd..` to `cd ..`;
|
||||
* `chmod_x` – add execution bit;
|
||||
* `choco_install` – append common suffixes for chocolatey packages;
|
||||
* `composer_not_command` – fixes composer command name;
|
||||
* `cp_omitting_directory` – adds `-a` when you `cp` directory;
|
||||
* `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
|
||||
@ -174,7 +190,9 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `dirty_unzip` – fixes `unzip` command that unzipped in the current directory;
|
||||
* `django_south_ghost` – adds `--delete-ghost-migrations` to failed because ghosts django south migration;
|
||||
* `django_south_merge` – adds `--merge` to inconsistent django south migration;
|
||||
* `docker_login` – executes a `docker login` and repeats the previous command;
|
||||
* `docker_not_command` – fixes wrong docker commands like `docker tags`;
|
||||
* `docker_image_being_used_by_container` ‐ removes the container that is using the image before removing the image;
|
||||
* `dry` – fixes repetitions like `git git push`;
|
||||
* `fab_command_not_found` – fix misspelled fabric commands;
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
@ -184,16 +202,19 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_add_force` – adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
|
||||
* `git_bisect_usage` – fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
|
||||
* `git_branch_delete` – changes `git branch -d` to `git branch -D`;
|
||||
* `git_branch_delete_checked_out` – changes `git branch -d` to `git checkout master && git branch -D` when trying to delete a checked out branch;
|
||||
* `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_checkout` – fixes branch name or creates new branch;
|
||||
* `git_commit_amend` – offers `git commit --amend` after previous commit;
|
||||
* `git_commit_reset` – offers `git reset HEAD~` after previous commit;
|
||||
* `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_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||
* `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_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_pull` – sets upstream before executing previous `git pull`;
|
||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||
@ -208,16 +229,17 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
||||
* `git_rm_staged` – adds `-f` or `--cached` when you try to `rm` a file with staged changes
|
||||
* `git_rebase_merge_dir` – offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
|
||||
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistant remote;
|
||||
* `git_remote_seturl_add` – runs `git remote add` when `git remote set_url` on nonexistent remote;
|
||||
* `git_stash` – stashes your local modifications before rebasing or switching branch;
|
||||
* `git_stash_pop` – adds your local modifications before popping stash, then resets;
|
||||
* `git_tag_force` – adds `--force` to `git tag <tagname>` when the tag already exists;
|
||||
* `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
|
||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
|
||||
* `go_unknown_command` – fixes wrong `go` commands, for example `go bulid`;
|
||||
* `gradle_no_task` – fixes not found or ambiguous `gradle` task;
|
||||
* `gradle_wrapper` – replaces `gradle` with `./gradlew`;
|
||||
* `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`;
|
||||
* `grep_recursive` – adds `-r` when you trying to `grep` directory;
|
||||
* `grep_arguments_order` – fixes `grep` arguments order for situations like `grep -lir . test`;
|
||||
* `grep_recursive` – adds `-r` when you try to `grep` directory;
|
||||
* `grunt_task_not_found` – fixes misspelled `grunt` commands;
|
||||
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
@ -229,6 +251,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
* `javac` – appends missing `.java` when compiling Java files;
|
||||
* `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_s_order` – fixes `ln -s` arguments order;
|
||||
* `ls_all` – adds `-A` to `ls` when output is empty;
|
||||
@ -237,26 +260,29 @@ 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`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
||||
* `mkdir_p` – adds `-p` when you trying to create directory without parent;
|
||||
* `mkdir_p` – adds `-p` when you try to create a directory without parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled lifecycle phases with `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled life cycle phases with `mvn`;
|
||||
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
|
||||
* `npm_run_script` – adds missing `run-script` for custom `npm` scripts;
|
||||
* `npm_wrong_command` – fixes wrong npm commands like `npm urgrade`;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `open` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
|
||||
* `pip_install` – fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
|
||||
* `pip_unknown_command` – fixes wrong `pip` commands, for example `pip instatl/pip install`;
|
||||
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||
* `port_already_in_use` – kills process that bound port;
|
||||
* `prove_recursively` – adds `-r` when called with directory;
|
||||
* `python_command` – prepends `python` when you trying to run not executable/without `./` python script;
|
||||
* `pyenv_no_such_command` – fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`;
|
||||
* `python_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||
* `remove_trailing_cedilla` – remove trailling cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `rm_dir` – adds `-rf` when you trying to remove directory;
|
||||
* `remove_shell_prompt_literal` – remove leading shell prompt symbol `$`, common when copying commands from documentations;
|
||||
* `remove_trailing_cedilla` – remove trailing cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `rm_dir` – adds `-rf` when you try to remove a directory;
|
||||
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||
* `sl_ls` – changes `sl` to `ls`;
|
||||
@ -265,12 +291,14 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `sudo_command_from_user_path` – runs commands from users `$PATH` with `sudo`;
|
||||
* `switch_lang` – switches command from your local layout to en;
|
||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||
* `terraform_init.py` – run `terraform init` before plan or apply;
|
||||
* `test.py` – runs `py.test` instead of `test.py`;
|
||||
* `touch` – creates missing directories before "touching";
|
||||
* `tsuru_login` – runs `tsuru login` if not authenticated or session expired;
|
||||
* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`;
|
||||
* `tmux` – fixes `tmux` commands;
|
||||
* `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;
|
||||
* `whois` – fixes `whois` command;
|
||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
||||
@ -279,7 +307,7 @@ using the matched rule and runs it. Rules enabled by default are as follows:
|
||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
||||
* `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_search` – changes trying to search using `apt-get` with searching using `apt-cache`;
|
||||
@ -288,44 +316,51 @@ Enabled by default only on specific platforms:
|
||||
* `apt_upgrade` – helps you run `apt upgrade` after `apt list --upgradable`;
|
||||
* `brew_cask_dependency` – installs cask dependencies;
|
||||
* `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_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_update_formula` – turns `brew update <formula>` into `brew upgrade <formula>`;
|
||||
* `dnf_no_such_command` – fixes mistyped DNF commands;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman` or `yaourt`.
|
||||
* `nixos_cmd_not_found` – installs apps on NixOS;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman`, `yay` or `yaourt`.
|
||||
* `yum_invalid_operation` – fixes invalid `yum` calls, like `yum isntall vim`;
|
||||
|
||||
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`);
|
||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||
|
||||
## Creating your own rules
|
||||
|
||||
For adding your own rule you should create `your-rule-name.py`
|
||||
in `~/.config/thefuck/rules`. The rule should contain two functions:
|
||||
To add your own rule, create a file named `your-rule-name.py`
|
||||
in `~/.config/thefuck/rules`. The rule file must contain two functions:
|
||||
|
||||
```python
|
||||
match(command: Command) -> bool
|
||||
get_new_command(command: Command) -> str | list[str]
|
||||
```
|
||||
|
||||
Also the rule can contain an optional function
|
||||
Additionally, rules can contain optional functions:
|
||||
|
||||
```python
|
||||
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`.
|
||||
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`.
|
||||
`settings` is a special object filled with `~/.config/thefuck/settings.py` and values from env ([see more below](#settings)).
|
||||
**Rules api changed in 3.0:** To access a rule's settings, import it with
|
||||
`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
|
||||
def match(command):
|
||||
@ -353,9 +388,10 @@ requires_output = True
|
||||
|
||||
## 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.const.DEFAULT_RULES`;
|
||||
* `exclude_rules` – list of disabled rules, by default `[]`;
|
||||
* `require_confirmation` – requires confirmation before running new command, by default `True`;
|
||||
* `wait_command` – max amount of time in seconds for getting previous command output;
|
||||
@ -365,9 +401,10 @@ The Fuck has a few settings parameters which can be changed in `$XDG_CONFIG_HOME
|
||||
* `history_limit` – numeric value of how many history commands will be scanned, like `2000`;
|
||||
* `alter_history` – push fixed command to history, by default `True`;
|
||||
* `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;
|
||||
* `num_close_matches` – maximum number of close matches to suggest, by default `3`.
|
||||
|
||||
Example of `settings.py`:
|
||||
An example of `settings.py`:
|
||||
|
||||
```python
|
||||
rules = ['sudo', 'no_command']
|
||||
@ -380,6 +417,7 @@ debug = False
|
||||
history_limit = 9999
|
||||
wait_slow_command = 20
|
||||
slow_commands = ['react-native', 'gradle']
|
||||
num_close_matches = 5
|
||||
```
|
||||
|
||||
Or via environment variables:
|
||||
@ -395,7 +433,8 @@ rule with lower `priority` will be matched first;
|
||||
* `THEFUCK_HISTORY_LIMIT` – how many history commands will be scanned, like `2000`;
|
||||
* `THEFUCK_ALTER_HISTORY` – push fixed command to history `true/false`;
|
||||
* `THEFUCK_WAIT_SLOW_COMMAND` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
||||
* `THEFUCK_SLOW_COMMANDS` – list of slow commands, like `lein:gradle`.
|
||||
* `THEFUCK_SLOW_COMMANDS` – list of slow commands, like `lein:gradle`;
|
||||
* `THEFUCK_NUM_CLOSE_MATCHES` – maximum number of close matches to suggest, like `5`.
|
||||
|
||||
For example:
|
||||
|
||||
@ -407,13 +446,14 @@ export THEFUCK_WAIT_COMMAND=10
|
||||
export THEFUCK_NO_COLORS='false'
|
||||
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
|
||||
export THEFUCK_HISTORY_LIMIT='2000'
|
||||
export THEFUCK_NUM_CLOSE_MATCHES='5'
|
||||
```
|
||||
|
||||
## Third-party packages with rules
|
||||
|
||||
If you want to make very specific rules or rules, that you don't want to make public,
|
||||
but share with other people.
|
||||
You can create a special package with name `thefuck_contrib_*` with following structure:
|
||||
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
|
||||
@ -426,20 +466,22 @@ thefuck_contrib_foo
|
||||
setup.py
|
||||
```
|
||||
|
||||
And thefuck will find all rules from `rules` module.
|
||||
*The Fuck* will find rules located in the `rules` module.
|
||||
|
||||
## Experimental instant mode
|
||||
|
||||
By default The Fuck reruns a previous command and that takes time,
|
||||
in instant mode The Fuck logs output with [script](https://en.wikipedia.org/wiki/Script_(Unix))
|
||||
and just reads the log.
|
||||
The default behavior of *The Fuck* requires time to re-run previous commands.
|
||||
When in instant mode, *The Fuck* saves time by logging output with [script](https://en.wikipedia.org/wiki/Script_(Unix)),
|
||||
then reading the log.
|
||||
|
||||
[![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. zsh's autocorrect function also needs to be disabled in order for thefuck to work properly.
|
||||
|
||||
For enabling instant mode you need to add `--enable-experimental-instant-mode`
|
||||
to alias initialization in your `.bashrc`, `.bash_profile` or `.zshrc` like:
|
||||
To enable instant mode, add `--enable-experimental-instant-mode`
|
||||
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
eval $(thefuck --alias --enable-experimental-instant-mode)
|
||||
@ -464,4 +506,5 @@ Project License can be found [here](LICENSE.md).
|
||||
[license-badge]: https://img.shields.io/badge/license-MIT-007EC7.svg
|
||||
[examples-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example.gif
|
||||
[instant-mode-gif-link]: https://raw.githubusercontent.com/nvbn/thefuck/master/example_instant_mode.gif
|
||||
[homebrew]: http://brew.sh/
|
||||
[homebrew]: https://brew.sh/
|
||||
[linuxbrew]: https://linuxbrew.sh/
|
||||
|
@ -3,9 +3,9 @@ build: false
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:/Python27"
|
||||
- PYTHON: "C:/Python34"
|
||||
- PYTHON: "C:/Python35"
|
||||
- PYTHON: "C:/Python36"
|
||||
- PYTHON: "C:/Python37"
|
||||
|
||||
init:
|
||||
- "ECHO %PYTHON%"
|
||||
|
@ -37,7 +37,7 @@ http://github.com/ninjaaron/fast-entry_points
|
||||
'''
|
||||
from setuptools.command import easy_install
|
||||
import re
|
||||
TEMPLATE = '''\
|
||||
TEMPLATE = r'''\
|
||||
# -*- coding: utf-8 -*-
|
||||
# EASY-INSTALL-ENTRY-SCRIPT: '{3}','{4}','{5}'
|
||||
__requires__ = '{3}'
|
||||
@ -83,7 +83,7 @@ def main():
|
||||
import shutil
|
||||
import sys
|
||||
dests = sys.argv[1:] or ['.']
|
||||
filename = re.sub('\.pyc$', '.py', __file__)
|
||||
filename = re.sub(r'\.pyc$', '.py', __file__)
|
||||
|
||||
for dst in dests:
|
||||
shutil.copy(filename, dst)
|
||||
|
@ -32,4 +32,6 @@ call('git push --tags', shell=True)
|
||||
|
||||
env = os.environ
|
||||
env['CONVERT_README'] = 'true'
|
||||
call('python setup.py sdist bdist_wheel upload', shell=True, env=env)
|
||||
call('rm -rf dist/*', shell=True, env=env)
|
||||
call('python setup.py sdist bdist_wheel', shell=True, env=env)
|
||||
call('twine upload dist/*', shell=True, env=env)
|
||||
|
@ -1,4 +1,3 @@
|
||||
pip
|
||||
flake8
|
||||
pytest
|
||||
mock
|
||||
@ -9,3 +8,4 @@ pexpect
|
||||
pypandoc
|
||||
pytest-benchmark
|
||||
pytest-docker-pexpect
|
||||
twine
|
||||
|
6
setup.py
6
setup.py
@ -26,12 +26,12 @@ if version < (2, 7):
|
||||
print('thefuck requires Python version 2.7 or later' +
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
elif (3, 0) < version < (3, 4):
|
||||
print('thefuck requires Python version 3.4 or later' +
|
||||
elif (3, 0) < version < (3, 5):
|
||||
print('thefuck requires Python version 3.5 or later' +
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.25'
|
||||
VERSION = '3.29'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
|
25
snapcraft.yaml
Normal file
25
snapcraft.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
name: thefuck
|
||||
version: stable
|
||||
version-script: git -C parts/thefuck/build describe --abbrev=0 --tags
|
||||
summary: Magnificent app which corrects your previous console command.
|
||||
description: |
|
||||
The Fuck tries to match a rule for the previous command,
|
||||
creates a new command using the matched rule and runs it.
|
||||
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
||||
apps:
|
||||
thefuck:
|
||||
command: bin/thefuck
|
||||
environment:
|
||||
PYTHONIOENCODING: utf-8
|
||||
fuck:
|
||||
command: bin/fuck
|
||||
environment:
|
||||
PYTHONIOENCODING: utf-8
|
||||
|
||||
parts:
|
||||
thefuck:
|
||||
source: https://github.com/nvbn/thefuck.git
|
||||
plugin: python
|
@ -42,7 +42,7 @@ def no_cache(monkeypatch):
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def functional(request):
|
||||
if request.node.get_marker('functional') \
|
||||
if request.node.get_closest_marker('functional') \
|
||||
and not request.config.getoption('enable_functional'):
|
||||
pytest.skip('functional tests are disabled')
|
||||
|
||||
|
@ -1,45 +0,0 @@
|
||||
import pytest
|
||||
import time
|
||||
|
||||
dockerfile = u'''
|
||||
FROM python:3
|
||||
RUN adduser --disabled-password --gecos '' test
|
||||
ENV SEED "{seed}"
|
||||
WORKDIR /src
|
||||
USER test
|
||||
RUN echo 'eval $(thefuck --alias)' > /home/test/.bashrc
|
||||
RUN echo > /home/test/.bash_history
|
||||
RUN git config --global user.email "you@example.com"
|
||||
RUN git config --global user.name "Your Name"
|
||||
USER root
|
||||
'''.format(seed=time.time())
|
||||
|
||||
|
||||
def plot(proc, TIMEOUT):
|
||||
proc.sendline(u'cd /home/test/')
|
||||
proc.sendline(u'fuck')
|
||||
assert proc.expect([TIMEOUT, u'No fucks given'])
|
||||
proc.sendline(u'git init')
|
||||
proc.sendline(u'git add .')
|
||||
proc.sendline(u'git commit -a -m init')
|
||||
proc.sendline(u'git brnch')
|
||||
proc.sendline(u'fuck')
|
||||
assert proc.expect([TIMEOUT, u'git branch'])
|
||||
proc.send('\n')
|
||||
assert proc.expect([TIMEOUT, u'master'])
|
||||
proc.sendline(u'echo test')
|
||||
proc.sendline(u'echo tst')
|
||||
proc.sendline(u'fuck')
|
||||
assert proc.expect([TIMEOUT, u'echo test'])
|
||||
proc.send('\n')
|
||||
assert proc.expect([TIMEOUT, u'test'])
|
||||
|
||||
|
||||
@pytest.mark.functional
|
||||
@pytest.mark.benchmark(min_rounds=10)
|
||||
def test_performance(spawnu, TIMEOUT, benchmark):
|
||||
proc = spawnu(u'thefuck/python3-bash-performance',
|
||||
dockerfile, u'bash')
|
||||
proc.sendline(u'pip install /src')
|
||||
proc.sendline(u'su test')
|
||||
assert benchmark(plot, proc, TIMEOUT) is None
|
58
tests/output_readers/test_rerun.py
Normal file
58
tests/output_readers/test_rerun.py
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from mock import Mock, patch
|
||||
from psutil import AccessDenied, TimeoutExpired
|
||||
|
||||
from thefuck.output_readers import rerun
|
||||
|
||||
|
||||
class TestRerun(object):
|
||||
def setup_method(self, test_method):
|
||||
self.patcher = patch('thefuck.output_readers.rerun.Process')
|
||||
process_mock = self.patcher.start()
|
||||
self.proc_mock = process_mock.return_value = Mock()
|
||||
|
||||
def teardown_method(self, test_method):
|
||||
self.patcher.stop()
|
||||
|
||||
@patch('thefuck.output_readers.rerun._wait_output', return_value=False)
|
||||
@patch('thefuck.output_readers.rerun.Popen')
|
||||
def test_get_output(self, popen_mock, wait_output_mock):
|
||||
popen_mock.return_value.stdout.read.return_value = b'output'
|
||||
assert rerun.get_output('', '') is None
|
||||
wait_output_mock.assert_called_once()
|
||||
|
||||
def test_wait_output_is_slow(self, settings):
|
||||
assert rerun._wait_output(Mock(), True)
|
||||
self.proc_mock.wait.assert_called_once_with(settings.wait_slow_command)
|
||||
|
||||
def test_wait_output_is_not_slow(self, settings):
|
||||
assert rerun._wait_output(Mock(), False)
|
||||
self.proc_mock.wait.assert_called_once_with(settings.wait_command)
|
||||
|
||||
@patch('thefuck.output_readers.rerun._kill_process')
|
||||
def test_wait_output_timeout(self, kill_process_mock):
|
||||
self.proc_mock.wait.side_effect = TimeoutExpired(3)
|
||||
self.proc_mock.children.return_value = []
|
||||
assert not rerun._wait_output(Mock(), False)
|
||||
kill_process_mock.assert_called_once_with(self.proc_mock)
|
||||
|
||||
@patch('thefuck.output_readers.rerun._kill_process')
|
||||
def test_wait_output_timeout_children(self, kill_process_mock):
|
||||
self.proc_mock.wait.side_effect = TimeoutExpired(3)
|
||||
self.proc_mock.children.return_value = [Mock()] * 2
|
||||
assert not rerun._wait_output(Mock(), False)
|
||||
assert kill_process_mock.call_count == 3
|
||||
|
||||
def test_kill_process(self):
|
||||
proc = Mock()
|
||||
rerun._kill_process(proc)
|
||||
proc.kill.assert_called_once_with()
|
||||
|
||||
@patch('thefuck.output_readers.rerun.logs')
|
||||
def test_kill_process_access_denied(self, logs_mock):
|
||||
proc = Mock()
|
||||
proc.kill.side_effect = AccessDenied()
|
||||
rerun._kill_process(proc)
|
||||
proc.kill.assert_called_once_with()
|
||||
logs_mock.debug.assert_called_once()
|
@ -9,7 +9,7 @@ def output():
|
||||
'If you meant to search for a literal string, run ag with -Q\n')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', ['ag \('])
|
||||
@pytest.mark.parametrize('script', ['ag \\('])
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
@ -20,6 +20,6 @@ def test_not_match(script):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, new_cmd', [
|
||||
('ag \(', 'ag -Q \(')])
|
||||
('ag \\(', 'ag -Q \\(')])
|
||||
def test_get_new_command(script, new_cmd, output):
|
||||
assert get_new_command((Command(script, output))) == new_cmd
|
||||
|
@ -12,9 +12,8 @@ from thefuck.types import Command
|
||||
[('vim', 'main'), ('vim-tiny', 'main')])])
|
||||
def test_match(mocker, command, packages):
|
||||
mocker.patch('thefuck.rules.apt_get.which', return_value=None)
|
||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
||||
create=True)
|
||||
mock.getPackages.return_value = packages
|
||||
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||
create=True, return_value=packages)
|
||||
|
||||
assert match(command)
|
||||
|
||||
@ -30,9 +29,8 @@ def test_match(mocker, command, packages):
|
||||
['vim'], '/usr/bin/vim')])
|
||||
def test_not_match(mocker, command, packages, which):
|
||||
mocker.patch('thefuck.rules.apt_get.which', return_value=which)
|
||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
||||
create=True)
|
||||
mock.getPackages.return_value = packages
|
||||
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||
create=True, return_value=packages)
|
||||
|
||||
assert not match(command)
|
||||
|
||||
@ -49,7 +47,7 @@ def test_not_match(mocker, command, packages, which):
|
||||
[('imagemagick', 'main'),
|
||||
('graphicsmagick-imagemagick-compat', 'universe')])])
|
||||
def test_get_new_command(mocker, command, new_command, packages):
|
||||
mock = mocker.patch('thefuck.rules.apt_get.command_not_found',
|
||||
create=True)
|
||||
mock.getPackages.return_value = packages
|
||||
mocker.patch('thefuck.rules.apt_get._get_packages',
|
||||
create=True, return_value=packages)
|
||||
|
||||
assert get_new_command(command) == new_command
|
||||
|
@ -156,6 +156,8 @@ def test_get_operations(set_help, app, help_text, operations):
|
||||
apt_get_help, 'apt-get install vim'),
|
||||
('apt saerch vim', invalid_operation('saerch'),
|
||||
apt_help, 'apt search vim'),
|
||||
('apt uninstall vim', invalid_operation('uninstall'),
|
||||
apt_help, 'apt remove vim'),
|
||||
])
|
||||
def test_get_new_command(set_help, output, script, help_text, result):
|
||||
set_help(help_text)
|
||||
|
@ -1,8 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.rules.apt_list_upgradable import get_new_command, match
|
||||
from thefuck.types import Command
|
||||
|
||||
match_output = '''
|
||||
full_english_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]
|
||||
@ -17,6 +19,11 @@ Reading state information... Done
|
||||
8 packages can be upgraded. Run 'apt list --upgradable' to see them.
|
||||
'''
|
||||
|
||||
match_output = [
|
||||
full_english_output,
|
||||
'Führen Sie »apt list --upgradable« aus, um sie anzuzeigen.' # German
|
||||
]
|
||||
|
||||
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]
|
||||
@ -48,8 +55,9 @@ All packages are up to date.
|
||||
'''
|
||||
|
||||
|
||||
def test_match():
|
||||
assert match(Command('sudo apt update', match_output))
|
||||
@pytest.mark.parametrize('output', match_output)
|
||||
def test_match(output):
|
||||
assert match(Command('sudo apt update', output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
@ -67,9 +75,10 @@ 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))
|
||||
@pytest.mark.parametrize('output', match_output)
|
||||
def test_get_new_command(output):
|
||||
new_command = get_new_command(Command('sudo apt update', output))
|
||||
assert new_command == 'sudo apt list --upgradable'
|
||||
|
||||
new_command = get_new_command(Command('apt update', match_output))
|
||||
new_command = get_new_command(Command('apt update', output))
|
||||
assert new_command == 'apt list --upgradable'
|
||||
|
44
tests/rules/test_az_cli.py
Normal file
44
tests/rules/test_az_cli.py
Normal file
@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
|
||||
from thefuck.rules.az_cli import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
no_suggestions = '''\
|
||||
az provider: error: the following arguments are required: _subcommand
|
||||
usage: az provider [-h] {list,show,register,unregister,operation} ...
|
||||
'''
|
||||
|
||||
|
||||
misspelled_command = '''\
|
||||
az: 'providers' is not in the 'az' command group. See 'az --help'.
|
||||
|
||||
The most similar choice to 'providers' is:
|
||||
provider
|
||||
'''
|
||||
|
||||
misspelled_subcommand = '''\
|
||||
az provider: 'lis' is not in the 'az provider' command group. See 'az provider --help'.
|
||||
|
||||
The most similar choice to 'lis' is:
|
||||
list
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('az providers', misspelled_command),
|
||||
Command('az provider lis', misspelled_subcommand)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
def test_not_match():
|
||||
assert not match(Command('az provider', no_suggestions))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, result', [
|
||||
(Command('az providers list', misspelled_command), ['az provider list']),
|
||||
(Command('az provider lis', misspelled_subcommand), ['az provider list'])
|
||||
])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command) == result
|
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
|
39
tests/rules/test_cat_dir.py
Normal file
39
tests/rules/test_cat_dir.py
Normal file
@ -0,0 +1,39 @@
|
||||
import pytest
|
||||
from thefuck.rules.cat_dir import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isdir(mocker):
|
||||
return mocker.patch('thefuck.rules.cat_dir'
|
||||
'.os.path.isdir')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('cat foo', 'cat: foo: Is a directory\n'),
|
||||
Command('cat /foo/bar/', 'cat: /foo/bar/: Is a directory\n'),
|
||||
Command('cat cat/', 'cat: cat/: Is a directory\n'),
|
||||
])
|
||||
def test_match(command, isdir):
|
||||
isdir.return_value = True
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('cat foo', 'foo bar baz'),
|
||||
Command('cat foo bar', 'foo bar baz'),
|
||||
Command('notcat foo bar', 'some output'),
|
||||
])
|
||||
def test_not_match(command, isdir):
|
||||
isdir.return_value = False
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('cat foo', 'cat: foo: Is a directory\n'), 'ls foo'),
|
||||
(Command('cat /foo/bar/', 'cat: /foo/bar/: Is a directory\n'), 'ls /foo/bar/'),
|
||||
(Command('cat cat', 'cat: cat: Is a directory\n'), 'ls cat'),
|
||||
])
|
||||
def test_get_new_command(command, new_command):
|
||||
isdir.return_value = True
|
||||
assert get_new_command(command) == new_command
|
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/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/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)
|
||||
|
||||
|
86
tests/rules/test_choco_install.py
Normal file
86
tests/rules/test_choco_install.py
Normal file
@ -0,0 +1,86 @@
|
||||
import pytest
|
||||
from thefuck.rules.choco_install import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
package_not_found_error = (
|
||||
'Chocolatey v0.10.15\n'
|
||||
'Installing the following packages:\n'
|
||||
'logstitcher\n'
|
||||
'By installing you accept licenses for the packages.\n'
|
||||
'logstitcher not installed. The package was not found with the source(s) listed.\n'
|
||||
' Source(s): \'https://chocolatey.org/api/v2/\'\n'
|
||||
' NOTE: When you specify explicit sources, it overrides default sources.\n'
|
||||
'If the package version is a prerelease and you didn\'t specify `--pre`,\n'
|
||||
' the package may not be found.\n'
|
||||
'Please see https://chocolatey.org/docs/troubleshooting for more\n'
|
||||
' assistance.\n'
|
||||
'\n'
|
||||
'Chocolatey installed 0/1 packages. 1 packages failed.\n'
|
||||
' See the log for details (C:\\ProgramData\\chocolatey\\logs\\chocolatey.log).\n'
|
||||
'\n'
|
||||
'Failures\n'
|
||||
' - logstitcher - logstitcher not installed. The package was not found with the source(s) listed.\n'
|
||||
' Source(s): \'https://chocolatey.org/api/v2/\'\n'
|
||||
' NOTE: When you specify explicit sources, it overrides default sources.\n'
|
||||
'If the package version is a prerelease and you didn\'t specify `--pre`,\n'
|
||||
' the package may not be found.\n'
|
||||
'Please see https://chocolatey.org/docs/troubleshooting for more\n'
|
||||
' assistance.\n'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('choco install logstitcher', package_not_found_error),
|
||||
Command('cinst logstitcher', package_not_found_error),
|
||||
Command('choco install logstitcher -y', package_not_found_error),
|
||||
Command('cinst logstitcher -y', package_not_found_error),
|
||||
Command('choco install logstitcher -y -n=test', package_not_found_error),
|
||||
Command('cinst logstitcher -y -n=test', package_not_found_error),
|
||||
Command('choco install logstitcher -y -n=test /env', package_not_found_error),
|
||||
Command('cinst logstitcher -y -n=test /env', package_not_found_error),
|
||||
Command('choco install chocolatey -y', package_not_found_error),
|
||||
Command('cinst chocolatey -y', package_not_found_error)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('choco /?', ''),
|
||||
Command('choco upgrade logstitcher', ''),
|
||||
Command('cup logstitcher', ''),
|
||||
Command('choco upgrade logstitcher -y', ''),
|
||||
Command('cup logstitcher -y', ''),
|
||||
Command('choco upgrade logstitcher -y -n=test', ''),
|
||||
Command('cup logstitcher -y -n=test', ''),
|
||||
Command('choco upgrade logstitcher -y -n=test /env', ''),
|
||||
Command('cup logstitcher -y -n=test /env', ''),
|
||||
Command('choco upgrade chocolatey -y', ''),
|
||||
Command('cup chocolatey -y', ''),
|
||||
Command('choco uninstall logstitcher', ''),
|
||||
Command('cuninst logstitcher', ''),
|
||||
Command('choco uninstall logstitcher -y', ''),
|
||||
Command('cuninst logstitcher -y', ''),
|
||||
Command('choco uninstall logstitcher -y -n=test', ''),
|
||||
Command('cuninst logstitcher -y -n=test', ''),
|
||||
Command('choco uninstall logstitcher -y -n=test /env', ''),
|
||||
Command('cuninst logstitcher -y -n=test /env', ''),
|
||||
Command('choco uninstall chocolatey -y', ''),
|
||||
Command('cuninst chocolatey -y', '')])
|
||||
def not_test_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('choco install logstitcher', 'choco install logstitcher.install'),
|
||||
('cinst logstitcher', 'cinst logstitcher.install'),
|
||||
('choco install logstitcher -y', 'choco install logstitcher.install -y'),
|
||||
('cinst logstitcher -y', 'cinst logstitcher.install -y'),
|
||||
('choco install logstitcher -y -n=test', 'choco install logstitcher.install -y -n=test'),
|
||||
('cinst logstitcher -y -n=test', 'cinst logstitcher.install -y -n=test'),
|
||||
('choco install logstitcher -y -n=test /env', 'choco install logstitcher.install -y -n=test /env'),
|
||||
('cinst logstitcher -y -n=test /env', 'cinst logstitcher.install -y -n=test /env'),
|
||||
('choco install chocolatey -y', 'choco install chocolatey.install -y'),
|
||||
('cinst chocolatey -y', 'cinst chocolatey.install -y'), ])
|
||||
def test_get_new_command(before, after):
|
||||
assert (get_new_command(Command(before, '')) == after)
|
27
tests/rules/test_docker_image_being_used_by_container.py
Normal file
27
tests/rules/test_docker_image_being_used_by_container.py
Normal file
@ -0,0 +1,27 @@
|
||||
from thefuck.rules.docker_image_being_used_by_container import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
|
||||
assert match(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_not_match():
|
||||
err_response = 'bash: docker: command not found'
|
||||
assert not match(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_not_docker_command():
|
||||
err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
|
||||
assert not match(Command('git image rm -f cd809b04b6ff', err_response))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
err_response = """
|
||||
Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image
|
||||
is being used by running container e5e2591040d1
|
||||
"""
|
||||
result = get_new_command(Command('docker image rm -f cd809b04b6ff', err_response))
|
||||
expected = 'docker container rm -f e5e2591040d1 && docker image rm -f cd809b04b6ff'
|
||||
assert result == expected
|
37
tests/rules/test_docker_login.py
Normal file
37
tests/rules/test_docker_login.py
Normal file
@ -0,0 +1,37 @@
|
||||
from thefuck.rules.docker_login import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
err_response1 = """
|
||||
Sending build context to Docker daemon 118.8kB
|
||||
Step 1/6 : FROM foo/bar:fdb7c6d
|
||||
pull access denied for foo/bar, repository does not exist or may require 'docker login'
|
||||
"""
|
||||
assert match(Command('docker build -t artifactory:9090/foo/bar:fdb7c6d .', err_response1))
|
||||
|
||||
err_response2 = """
|
||||
The push refers to repository [artifactory:9090/foo/bar]
|
||||
push access denied for foo/bar, repository does not exist or may require 'docker login'
|
||||
"""
|
||||
assert match(Command('docker push artifactory:9090/foo/bar:fdb7c6d', err_response2))
|
||||
|
||||
err_response3 = """
|
||||
docker push artifactory:9090/foo/bar:fdb7c6d
|
||||
The push refers to repository [artifactory:9090/foo/bar]
|
||||
9c29c7ad209d: Preparing
|
||||
71f3ad53dfe0: Preparing
|
||||
f58ee068224c: Preparing
|
||||
aeddc924d0f7: Preparing
|
||||
c2040e5d6363: Preparing
|
||||
4d42df4f350f: Preparing
|
||||
35723dab26f9: Preparing
|
||||
71f3ad53dfe0: Pushed
|
||||
cb95fa0faeb1: Layer already exists
|
||||
"""
|
||||
assert not match(Command('docker push artifactory:9090/foo/bar:fdb7c6d', err_response3))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('docker build -t artifactory:9090/foo/bar:fdb7c6d .', '')) == 'docker login && docker build -t artifactory:9090/foo/bar:fdb7c6d .'
|
||||
assert get_new_command(Command('docker push artifactory:9090/foo/bar:fdb7c6d', '')) == 'docker login && docker push artifactory:9090/foo/bar:fdb7c6d'
|
@ -4,6 +4,46 @@ from thefuck.types import Command
|
||||
from thefuck.rules.docker_not_command import get_new_command, match
|
||||
|
||||
|
||||
_DOCKER_SWARM_OUTPUT = '''
|
||||
Usage: docker swarm COMMAND
|
||||
|
||||
Manage Swarm
|
||||
|
||||
Commands:
|
||||
ca Display and rotate the root CA
|
||||
init Initialize a swarm
|
||||
join Join a swarm as a node and/or manager
|
||||
join-token Manage join tokens
|
||||
leave Leave the swarm
|
||||
unlock Unlock swarm
|
||||
unlock-key Manage the unlock key
|
||||
update Update the swarm
|
||||
|
||||
Run 'docker swarm COMMAND --help' for more information on a command.
|
||||
'''
|
||||
_DOCKER_IMAGE_OUTPUT = '''
|
||||
Usage: docker image COMMAND
|
||||
|
||||
Manage images
|
||||
|
||||
Commands:
|
||||
build Build an image from a Dockerfile
|
||||
history Show the history of an image
|
||||
import Import the contents from a tarball to create a filesystem image
|
||||
inspect Display detailed information on one or more images
|
||||
load Load an image from a tar archive or STDIN
|
||||
ls List images
|
||||
prune Remove unused images
|
||||
pull Pull an image or a repository from a registry
|
||||
push Push an image or a repository to a registry
|
||||
rm Remove one or more images
|
||||
save Save one or more images to a tar archive (streamed to STDOUT by default)
|
||||
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
|
||||
|
||||
Run 'docker image COMMAND --help' for more information on a command.
|
||||
'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docker_help(mocker):
|
||||
help = b'''Usage: docker [OPTIONS] COMMAND [arg...]
|
||||
@ -104,6 +144,94 @@ Run 'docker COMMAND --help' for more information on a command.
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docker_help_new(mocker):
|
||||
helptext_new = b'''
|
||||
Usage: docker [OPTIONS] COMMAND
|
||||
|
||||
A self-sufficient runtime for containers
|
||||
|
||||
Options:
|
||||
--config string Location of client config files (default "/Users/ik1ne/.docker")
|
||||
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var
|
||||
and default context set with "docker context use")
|
||||
-D, --debug Enable debug mode
|
||||
-H, --host list Daemon socket(s) to connect to
|
||||
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
|
||||
--tls Use TLS; implied by --tlsverify
|
||||
--tlscacert string Trust certs signed only by this CA (default "/Users/ik1ne/.docker/ca.pem")
|
||||
--tlscert string Path to TLS certificate file (default "/Users/ik1ne/.docker/cert.pem")
|
||||
--tlskey string Path to TLS key file (default "/Users/ik1ne/.docker/key.pem")
|
||||
--tlsverify Use TLS and verify the remote
|
||||
-v, --version Print version information and quit
|
||||
|
||||
Management Commands:
|
||||
builder Manage builds
|
||||
config Manage Docker configs
|
||||
container Manage containers
|
||||
context Manage contexts
|
||||
image Manage images
|
||||
network Manage networks
|
||||
node Manage Swarm nodes
|
||||
plugin Manage plugins
|
||||
secret Manage Docker secrets
|
||||
service Manage services
|
||||
stack Manage Docker stacks
|
||||
swarm Manage Swarm
|
||||
system Manage Docker
|
||||
trust Manage trust on Docker images
|
||||
volume Manage volumes
|
||||
|
||||
Commands:
|
||||
attach Attach local standard input, output, and error streams to a running container
|
||||
build Build an image from a Dockerfile
|
||||
commit Create a new image from a container's changes
|
||||
cp Copy files/folders between a container and the local filesystem
|
||||
create Create a new container
|
||||
diff Inspect changes to files or directories on a container's filesystem
|
||||
events Get real time events from the server
|
||||
exec Run a command in a running container
|
||||
export Export a container's filesystem as a tar archive
|
||||
history Show the history of an image
|
||||
images List images
|
||||
import Import the contents from a tarball to create a filesystem image
|
||||
info Display system-wide information
|
||||
inspect Return low-level information on Docker objects
|
||||
kill Kill one or more running containers
|
||||
load Load an image from a tar archive or STDIN
|
||||
login Log in to a Docker registry
|
||||
logout Log out from a Docker registry
|
||||
logs Fetch the logs of a container
|
||||
pause Pause all processes within one or more containers
|
||||
port List port mappings or a specific mapping for the container
|
||||
ps List containers
|
||||
pull Pull an image or a repository from a registry
|
||||
push Push an image or a repository to a registry
|
||||
rename Rename a container
|
||||
restart Restart one or more containers
|
||||
rm Remove one or more containers
|
||||
rmi Remove one or more images
|
||||
run Run a command in a new container
|
||||
save Save one or more images to a tar archive (streamed to STDOUT by default)
|
||||
search Search the Docker Hub for images
|
||||
start Start one or more stopped containers
|
||||
stats Display a live stream of container(s) resource usage statistics
|
||||
stop Stop one or more running containers
|
||||
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
|
||||
top Display the running processes of a container
|
||||
unpause Unpause all processes within one or more containers
|
||||
update Update configuration of one or more containers
|
||||
version Show the Docker version information
|
||||
wait Block until one or more containers stop, then print their exit codes
|
||||
|
||||
Run 'docker COMMAND --help' for more information on a command.
|
||||
'''
|
||||
mock = mocker.patch('subprocess.Popen')
|
||||
mock.return_value.stdout = BytesIO(b'')
|
||||
mock.return_value.stderr = BytesIO(helptext_new)
|
||||
return mock
|
||||
|
||||
|
||||
def output(cmd):
|
||||
return "docker: '{}' is not a docker command.\n" \
|
||||
"See 'docker --help'.".format(cmd)
|
||||
@ -113,6 +241,24 @@ def test_match():
|
||||
assert match(Command('docker pes', output('pes')))
|
||||
|
||||
|
||||
# tests docker (management command)
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('docker swarn', output('swarn')),
|
||||
('docker imge', output('imge'))])
|
||||
def test_match_management_cmd(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
# tests docker (management cmd) (management subcmd)
|
||||
@pytest.mark.usefixtures('no_memoize')
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('docker swarm int', _DOCKER_SWARM_OUTPUT),
|
||||
('docker image la', _DOCKER_IMAGE_OUTPUT)])
|
||||
def test_match_management_subcmd(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('docker ps', ''),
|
||||
('cat pes', output('pes'))])
|
||||
@ -120,10 +266,28 @@ def test_not_match(script, output):
|
||||
assert not match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('docker_help')
|
||||
@pytest.mark.usefixtures('no_memoize', 'docker_help')
|
||||
@pytest.mark.parametrize('wrong, fixed', [
|
||||
('pes', ['ps', 'push', 'pause']),
|
||||
('tags', ['tag', 'stats', 'images'])])
|
||||
def test_get_new_command(wrong, fixed):
|
||||
command = Command('docker {}'.format(wrong), output(wrong))
|
||||
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
|
||||
@pytest.mark.parametrize('wrong, fixed', [
|
||||
('swarn', ['swarm', 'start', 'search']),
|
||||
('inage', ['image', 'images', 'rename'])])
|
||||
def test_get_new_management_command(wrong, fixed):
|
||||
command = Command('docker {}'.format(wrong), output(wrong))
|
||||
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
|
||||
@pytest.mark.parametrize('wrong, fixed, output', [
|
||||
('swarm int', ['swarm init', 'swarm join', 'swarm join-token'], _DOCKER_SWARM_OUTPUT),
|
||||
('image la', ['image load', 'image ls', 'image tag'], _DOCKER_IMAGE_OUTPUT)])
|
||||
def test_get_new_management_command_subcommand(wrong, fixed, output):
|
||||
command = Command('docker {}'.format(wrong), output)
|
||||
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
|
||||
|
29
tests/rules/test_git_branch_delete_checked_out.py
Normal file
29
tests/rules/test_git_branch_delete_checked_out.py
Normal file
@ -0,0 +1,29 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_branch_delete_checked_out import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output():
|
||||
return "error: Cannot delete branch 'foo' checked out at '/bar/foo'"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("script", ["git branch -d foo", "git branch -D foo"])
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("script", ["git branch -d foo", "git branch -D foo"])
|
||||
def test_not_match(script):
|
||||
assert not match(Command(script, "Deleted branch foo (was a1b2c3d)."))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"script, new_command",
|
||||
[
|
||||
("git branch -d foo", "git checkout master && git branch -D foo"),
|
||||
("git branch -D foo", "git checkout master && git branch -D foo"),
|
||||
],
|
||||
)
|
||||
def test_get_new_command(script, new_command, output):
|
||||
assert get_new_command(Command(script, output)) == new_command
|
@ -4,7 +4,6 @@ from thefuck.rules.git_checkout import match, get_branches, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def did_not_match(target, did_you_forget=False):
|
||||
error = ("error: pathspec '{}' did not match any "
|
||||
"file(s) known to git.".format(target))
|
||||
@ -40,6 +39,11 @@ def test_not_match(command):
|
||||
(b'', []),
|
||||
(b'* master', ['master']),
|
||||
(b' remotes/origin/master', ['master']),
|
||||
(b' remotes/origin/test/1', ['test/1']),
|
||||
(b' remotes/origin/test/1/2/3', ['test/1/2/3']),
|
||||
(b' test/1', ['test/1']),
|
||||
(b' test/1/2/3', ['test/1/2/3']),
|
||||
(b' remotes/origin/HEAD -> origin/master', []),
|
||||
(b' just-another-branch', ['just-another-branch']),
|
||||
(b'* master\n just-another-branch', ['master', 'just-another-branch']),
|
||||
(b'* master\n remotes/origin/master\n just-another-branch',
|
||||
|
25
tests/rules/test_git_commit_reset.py
Normal file
25
tests/rules/test_git_commit_reset.py
Normal file
@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_commit_reset 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 reset HEAD~'
|
@ -3,24 +3,22 @@ from thefuck.rules.git_merge import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output():
|
||||
return 'merge: local - not something we can merge\n\n' \
|
||||
'Did you mean this?\n\tremote/local'
|
||||
output = 'merge: local - not something we can merge\n\n' \
|
||||
'Did you mean this?\n\tremote/local'
|
||||
|
||||
|
||||
def test_match(output):
|
||||
def test_match():
|
||||
assert match(Command('git merge test', output))
|
||||
assert not match(Command('git merge master', ''))
|
||||
assert not match(Command('ls', output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git merge local', output()),
|
||||
(Command('git merge local', output),
|
||||
'git merge remote/local'),
|
||||
(Command('git merge -m "test" local', output()),
|
||||
(Command('git merge -m "test" local', output),
|
||||
'git merge -m "test" remote/local'),
|
||||
(Command('git merge -m "test local" local', output()),
|
||||
(Command('git merge -m "test local" local', output),
|
||||
'git merge -m "test local" remote/local')])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
||||
|
23
tests/rules/test_git_merge_unrelated.py
Normal file
23
tests/rules/test_git_merge_unrelated.py
Normal file
@ -0,0 +1,23 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_merge_unrelated import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
output = 'fatal: refusing to merge unrelated histories'
|
||||
|
||||
|
||||
def test_match():
|
||||
assert match(Command('git merge test', output))
|
||||
assert not match(Command('git merge master', ''))
|
||||
assert not match(Command('ls', output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('git merge local', output),
|
||||
'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
|
@ -15,6 +15,19 @@ To push the current branch and set the remote as upstream, use
|
||||
'''.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.
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, branch_name', [
|
||||
('git push', 'master'),
|
||||
('git push origin', 'master')])
|
||||
@ -22,6 +35,10 @@ def test_match(output, script, branch_name):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
def test_match_bitbucket(output_bitbucket):
|
||||
assert not match(Command('git push origin', output_bitbucket))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, branch_name', [
|
||||
('git push master', None),
|
||||
('ls', 'master')])
|
||||
@ -49,6 +66,10 @@ def test_not_match(output, script, branch_name):
|
||||
('git -c test=test push --quiet origin', 'master',
|
||||
'git -c test=test push --set-upstream origin master --quiet'),
|
||||
('git push', "test's",
|
||||
"git push --set-upstream origin test\\'s")])
|
||||
"git push --set-upstream origin test\\'s"),
|
||||
('git push --force', 'master',
|
||||
'git push --set-upstream origin master --force'),
|
||||
('git push --force-with-lease', 'master',
|
||||
'git push --set-upstream origin master --force-with-lease')])
|
||||
def test_get_new_command(output, script, branch_name, new_command):
|
||||
assert get_new_command(Command(script, output)) == new_command
|
||||
|
21
tests/rules/test_go_unknown_command.py
Normal file
21
tests/rules/test_go_unknown_command.py
Normal file
@ -0,0 +1,21 @@
|
||||
import pytest
|
||||
from thefuck.rules.go_unknown_command import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def build_misspelled_output():
|
||||
return '''go bulid: unknown command
|
||||
Run 'go help' for usage.'''
|
||||
|
||||
|
||||
def test_match(build_misspelled_output):
|
||||
assert match(Command('go bulid', build_misspelled_output))
|
||||
|
||||
|
||||
def test_not_match():
|
||||
assert not match(Command('go run', 'go run: no go files listed'))
|
||||
|
||||
|
||||
def test_get_new_command(build_misspelled_output):
|
||||
assert get_new_command(Command('go bulid', build_misspelled_output)) == 'go build'
|
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
|
25
tests/rules/test_nixos_cmd_not_found.py
Normal file
25
tests/rules/test_nixos_cmd_not_found.py
Normal file
@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
from thefuck.rules.nixos_cmd_not_found import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('vim', 'nix-env -iA nixos.vim')])
|
||||
def test_match(mocker, command):
|
||||
mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('vim', ''),
|
||||
Command('', '')])
|
||||
def test_not_match(mocker, command):
|
||||
mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('vim', 'nix-env -iA nixos.vim'), 'nix-env -iA nixos.vim && vim'),
|
||||
(Command('pacman', 'nix-env -iA nixos.pacman'), 'nix-env -iA nixos.pacman && pacman')])
|
||||
def test_get_new_command(mocker, command, new_command):
|
||||
assert get_new_command(command) == new_command
|
@ -6,7 +6,7 @@ from thefuck.types import Command
|
||||
@pytest.fixture(autouse=True)
|
||||
def get_all_executables(mocker):
|
||||
mocker.patch('thefuck.rules.no_command.get_all_executables',
|
||||
return_value=['vim', 'fsck', 'git', 'go'])
|
||||
return_value=['vim', 'fsck', 'git', 'go', 'python'])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@ -20,6 +20,7 @@ def history_without_current(mocker):
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('vom file.py', 'vom: not found'),
|
||||
('fucck', 'fucck: not found'),
|
||||
('puthon', "'puthon' is not recognized as an internal or external command"),
|
||||
('got commit', 'got: command not found')])
|
||||
def test_match(mocker, script, output):
|
||||
mocker.patch('thefuck.rules.no_command.which', return_value=None)
|
||||
|
@ -11,6 +11,7 @@ extra/llvm35 3.5.2-13/usr/bin/llc'''
|
||||
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yay -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@ -19,6 +20,7 @@ def test_match(command):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yay -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@ -31,6 +33,7 @@ def test_match_mocked(subp_mock, command):
|
||||
@pytest.mark.skipif(not getattr(pacman_not_found, 'enabled_by_default', True),
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command, fixed', [
|
||||
(Command('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
@ -39,6 +42,7 @@ def test_get_new_command(command, fixed):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, fixed', [
|
||||
(Command('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
|
27
tests/rules/test_pip_install.py
Normal file
27
tests/rules/test_pip_install.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
from thefuck.rules.pip_install import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
def test_match():
|
||||
response1 = """
|
||||
Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: '/Library/Python/2.7/site-packages/entrypoints.pyc'
|
||||
Consider using the `--user` option or check the permissions.
|
||||
"""
|
||||
assert match(Command('pip install -r requirements.txt', response1))
|
||||
|
||||
response2 = """
|
||||
Collecting bacon
|
||||
Downloading https://files.pythonhosted.org/packages/b2/81/19fb79139ee71c8bc4e5a444546f318e2b87253b8939ec8a7e10d63b7341/bacon-0.3.1.zip (11.0MB)
|
||||
100% |████████████████████████████████| 11.0MB 3.0MB/s
|
||||
Installing collected packages: bacon
|
||||
Running setup.py install for bacon ... done
|
||||
Successfully installed bacon-0.3.1
|
||||
"""
|
||||
assert not match(Command('pip install bacon', response2))
|
||||
|
||||
|
||||
def test_get_new_command():
|
||||
assert get_new_command(Command('pip install -r requirements.txt', '')) == 'pip install --user -r requirements.txt'
|
||||
assert get_new_command(Command('pip install bacon', '')) == 'pip install --user bacon'
|
||||
assert get_new_command(Command('pip install --user -r requirements.txt', '')) == 'sudo pip install -r requirements.txt'
|
52
tests/rules/test_pyenv_no_such_command.py
Normal file
52
tests/rules/test_pyenv_no_such_command.py
Normal file
@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
|
||||
from thefuck.rules.pyenv_no_such_command import get_new_command, match
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output(pyenv_cmd):
|
||||
return "pyenv: no such command `{}'".format(pyenv_cmd)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(mocker):
|
||||
mock = mocker.patch('thefuck.rules.pyenv_no_such_command.Popen')
|
||||
mock.return_value.stdout.readlines.return_value = (
|
||||
b'--version\nactivate\ncommands\ncompletions\ndeactivate\nexec_\n'
|
||||
b'global\nhelp\nhooks\ninit\ninstall\nlocal\nprefix_\n'
|
||||
b'realpath.dylib\nrehash\nroot\nshell\nshims\nuninstall\nversion_\n'
|
||||
b'version-file\nversion-file-read\nversion-file-write\nversion-name_\n'
|
||||
b'version-origin\nversions\nvirtualenv\nvirtualenv-delete_\n'
|
||||
b'virtualenv-init\nvirtualenv-prefix\nvirtualenvs_\n'
|
||||
b'virtualenvwrapper\nvirtualenvwrapper_lazy\nwhence\nwhich_\n'
|
||||
).split()
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, pyenv_cmd', [
|
||||
('pyenv globe', 'globe'),
|
||||
('pyenv intall 3.8.0', 'intall'),
|
||||
('pyenv list', 'list'),
|
||||
])
|
||||
def test_match(script, pyenv_cmd, output):
|
||||
assert match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('pyenv global', 'system'),
|
||||
('pyenv versions', ' 3.7.0\n 3.7.1\n* 3.7.2\n'),
|
||||
('pyenv install --list', ' 3.7.0\n 3.7.1\n 3.7.2\n'),
|
||||
])
|
||||
def test_not_match(script, output):
|
||||
assert not match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, pyenv_cmd, result', [
|
||||
('pyenv globe', 'globe', 'pyenv global'),
|
||||
('pyenv intall 3.8.0', 'intall', 'pyenv install 3.8.0'),
|
||||
('pyenv list', 'list', 'pyenv install --list'),
|
||||
('pyenv remove 3.8.0', 'remove', 'pyenv uninstall 3.8.0'),
|
||||
])
|
||||
def test_get_new_command(script, pyenv_cmd, output, result):
|
||||
assert result in get_new_command(Command(script, output))
|
38
tests/rules/test_remove_shell_prompt_literal.py
Normal file
38
tests/rules/test_remove_shell_prompt_literal.py
Normal file
@ -0,0 +1,38 @@
|
||||
import pytest
|
||||
from thefuck.rules.remove_shell_prompt_literal import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output():
|
||||
return "$: command not found"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("script", ["$ cd newdir", " $ cd newdir"])
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
Command("$", "$: command not found"),
|
||||
Command(" $", "$: command not found"),
|
||||
Command("$?", "127: command not found"),
|
||||
Command(" $?", "127: command not found"),
|
||||
Command("", ""),
|
||||
],
|
||||
)
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"script, new_command",
|
||||
[
|
||||
("$ cd newdir", "cd newdir"),
|
||||
("$ python3 -m virtualenv env", "python3 -m virtualenv env"),
|
||||
],
|
||||
)
|
||||
def test_get_new_command(script, new_command, output):
|
||||
assert get_new_command(Command(script, output)) == new_command
|
@ -1,13 +1,17 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
from thefuck.rules import switch_lang
|
||||
from thefuck.types import 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):
|
||||
assert switch_lang.match(command)
|
||||
|
||||
@ -16,13 +20,20 @@ def test_match(command):
|
||||
Command(u'pat-get', 'command not found: pat-get'),
|
||||
Command(u'ls', 'command not found: ls'),
|
||||
Command(u'агсл', 'command not found: агсл'),
|
||||
Command(u'фзе-пуе', 'some info')])
|
||||
Command(u'фзе-пуе', 'some info'),
|
||||
Command(u'שפא-עקא', 'some info'),
|
||||
Command(u'녀애 ㅣㄴ', 'some info')])
|
||||
def test_not_match(command):
|
||||
assert not switch_lang.match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command(u'фзе-пуе штыефдд мшь', ''), 'apt-get install vim'),
|
||||
(Command(u'λσ -λα', ''), 'ls -la')])
|
||||
(Command(u'λσ -λα', ''), 'ls -la'),
|
||||
(Command(u'שפא-עקא ןמדאשךך הןצ', ''), 'apt-get install vim'),
|
||||
(Command(u'ךד -ךש', ''), 'ls -la'),
|
||||
(Command(u'멧-ㅎㄷㅅ ㅑㅜㄴㅅ미ㅣ 퍄ㅡ', ''), 'apt-get install vim'),
|
||||
(Command(u'ㅣㄴ -ㅣㅁ', ''), 'ls -la'),
|
||||
(Command(u'ㅔㅁㅅ촤', ''), 'patchk'), ])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert switch_lang.get_new_command(command) == new_command
|
||||
|
33
tests/rules/test_terraform_init.py
Normal file
33
tests/rules/test_terraform_init.py
Normal file
@ -0,0 +1,33 @@
|
||||
import pytest
|
||||
from thefuck.rules.terraform_init import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('terraform plan', 'Error: Initialization required. '
|
||||
'Please see the error message above.'),
|
||||
('terraform plan', 'This module is not yet installed. Run "terraform init" '
|
||||
'to install all modules required by this configuration.'),
|
||||
('terraform apply', 'Error: Initialization required. '
|
||||
'Please see the error message above.'),
|
||||
('terraform apply', 'This module is not yet installed. Run "terraform init" '
|
||||
'to install all modules required by this configuration.')])
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('terraform --version', 'Terraform v0.12.2'),
|
||||
('terraform plan', 'No changes. Infrastructure is up-to-date.'),
|
||||
('terraform apply', 'Apply complete! Resources: 0 added, 0 changed, 0 destroyed.'),
|
||||
])
|
||||
def test_not_match(script, output):
|
||||
assert not match(Command(script, output=output))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, new_command', [
|
||||
(Command('terraform plan', ''), 'terraform init && terraform plan'),
|
||||
(Command('terraform apply', ''), 'terraform init && terraform apply'),
|
||||
])
|
||||
def test_get_new_command(command, new_command):
|
||||
assert get_new_command(command) == new_command
|
@ -3,25 +3,31 @@ from thefuck.rules.touch import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output():
|
||||
return "touch: cannot touch '/a/b/c':" \
|
||||
" No such file or directory"
|
||||
def output(is_bsd):
|
||||
if is_bsd:
|
||||
return "touch: /a/b/c: No such file or directory"
|
||||
return "touch: cannot touch '/a/b/c': No such file or directory"
|
||||
|
||||
|
||||
def test_match(output):
|
||||
command = Command('touch /a/b/c', output)
|
||||
@pytest.mark.parametrize('script, is_bsd', [
|
||||
('touch /a/b/c', False),
|
||||
('touch /a/b/c', True)])
|
||||
def test_match(script, is_bsd):
|
||||
command = Command(script, output(is_bsd))
|
||||
assert match(command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('touch /a/b/c', ''),
|
||||
Command('ls /a/b/c', output())])
|
||||
Command('ls /a/b/c', output(False))])
|
||||
def test_not_match(command):
|
||||
assert not match(command)
|
||||
|
||||
|
||||
def test_get_new_command(output):
|
||||
command = Command('touch /a/b/c', output)
|
||||
@pytest.mark.parametrize('script, is_bsd', [
|
||||
('touch /a/b/c', False),
|
||||
('touch /a/b/c', True)])
|
||||
def test_get_new_command(script, is_bsd):
|
||||
command = Command(script, output(is_bsd))
|
||||
fixed_command = get_new_command(command)
|
||||
assert fixed_command == 'mkdir -p /a/b && touch /a/b/c'
|
||||
|
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
|
173
tests/rules/test_yum_invalid_operation.py
Normal file
173
tests/rules/test_yum_invalid_operation.py
Normal file
@ -0,0 +1,173 @@
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from thefuck.rules.yum_invalid_operation import match, get_new_command, _get_operations
|
||||
from thefuck.types import Command
|
||||
|
||||
yum_help_text = '''Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
|
||||
Usage: yum [options] COMMAND
|
||||
|
||||
List of Commands:
|
||||
|
||||
check Check for problems in the rpmdb
|
||||
check-update Check for available package updates
|
||||
clean Remove cached data
|
||||
deplist List a package's dependencies
|
||||
distribution-synchronization Synchronize installed packages to the latest available versions
|
||||
downgrade downgrade a package
|
||||
erase Remove a package or packages from your system
|
||||
fs Acts on the filesystem data of the host, mainly for removing docs/lanuages for minimal hosts.
|
||||
fssnapshot Creates filesystem snapshots, or lists/deletes current snapshots.
|
||||
groups Display, or use, the groups information
|
||||
help Display a helpful usage message
|
||||
history Display, or use, the transaction history
|
||||
info Display details about a package or group of packages
|
||||
install Install a package or packages on your system
|
||||
langavailable Check available languages
|
||||
langinfo List languages information
|
||||
langinstall Install appropriate language packs for a language
|
||||
langlist List installed languages
|
||||
langremove Remove installed language packs for a language
|
||||
list List a package or groups of packages
|
||||
load-transaction load a saved transaction from filename
|
||||
makecache Generate the metadata cache
|
||||
provides Find what package provides the given value
|
||||
reinstall reinstall a package
|
||||
repo-pkgs Treat a repo. as a group of packages, so we can install/remove all of them
|
||||
repolist Display the configured software repositories
|
||||
search Search package details for the given string
|
||||
shell Run an interactive yum shell
|
||||
swap Simple way to swap packages, instead of using shell
|
||||
update Update a package or packages on your system
|
||||
update-minimal Works like upgrade, but goes to the 'newest' package match which fixes a problem that affects your system
|
||||
updateinfo Acts on repository update information
|
||||
upgrade Update packages taking obsoletes into account
|
||||
version Display a version for the machine and/or available repos.
|
||||
|
||||
|
||||
Options:
|
||||
-h, --help show this help message and exit
|
||||
-t, --tolerant be tolerant of errors
|
||||
-C, --cacheonly run entirely from system cache, don't update cache
|
||||
-c [config file], --config=[config file]
|
||||
config file location
|
||||
-R [minutes], --randomwait=[minutes]
|
||||
maximum command wait time
|
||||
-d [debug level], --debuglevel=[debug level]
|
||||
debugging output level
|
||||
--showduplicates show duplicates, in repos, in list/search commands
|
||||
-e [error level], --errorlevel=[error level]
|
||||
error output level
|
||||
--rpmverbosity=[debug level name]
|
||||
debugging output level for rpm
|
||||
-q, --quiet quiet operation
|
||||
-v, --verbose verbose operation
|
||||
-y, --assumeyes answer yes for all questions
|
||||
--assumeno answer no for all questions
|
||||
--version show Yum version and exit
|
||||
--installroot=[path] set install root
|
||||
--enablerepo=[repo] enable one or more repositories (wildcards allowed)
|
||||
--disablerepo=[repo] disable one or more repositories (wildcards allowed)
|
||||
-x [package], --exclude=[package]
|
||||
exclude package(s) by name or glob
|
||||
--disableexcludes=[repo]
|
||||
disable exclude from main, for a repo or for
|
||||
everything
|
||||
--disableincludes=[repo]
|
||||
disable includepkgs for a repo or for everything
|
||||
--obsoletes enable obsoletes processing during updates
|
||||
--noplugins disable Yum plugins
|
||||
--nogpgcheck disable gpg signature checking
|
||||
--disableplugin=[plugin]
|
||||
disable plugins by name
|
||||
--enableplugin=[plugin]
|
||||
enable plugins by name
|
||||
--skip-broken skip packages with depsolving problems
|
||||
--color=COLOR control whether color is used
|
||||
--releasever=RELEASEVER
|
||||
set value of $releasever in yum config and repo files
|
||||
--downloadonly don't update, just download
|
||||
--downloaddir=DLDIR specifies an alternate directory to store packages
|
||||
--setopt=SETOPTS set arbitrary config and repo options
|
||||
--bugfix Include bugfix relevant packages, in updates
|
||||
--security Include security relevant packages, in updates
|
||||
--advisory=ADVS, --advisories=ADVS
|
||||
Include packages needed to fix the given advisory, in
|
||||
updates
|
||||
--bzs=BZS Include packages needed to fix the given BZ, in
|
||||
updates
|
||||
--cves=CVES Include packages needed to fix the given CVE, in
|
||||
updates
|
||||
--sec-severity=SEVS, --secseverity=SEVS
|
||||
Include security relevant packages matching the
|
||||
severity, in updates
|
||||
|
||||
Plugin Options:
|
||||
--samearch-priorities
|
||||
Priority-exclude packages based on name + arch
|
||||
'''
|
||||
yum_unsuccessful_search_text = '''Warning: No matches found for: {}
|
||||
No matches found
|
||||
'''
|
||||
yum_successful_vim_search_text = '''================================================== N/S matched: vim ===================================================
|
||||
protobuf-vim.x86_64 : Vim syntax highlighting for Google Protocol Buffers descriptions
|
||||
vim-X11.x86_64 : The VIM version of the vi editor for the X Window System - GVim
|
||||
vim-common.x86_64 : The common files needed by any version of the VIM editor
|
||||
vim-enhanced.x86_64 : A version of the VIM editor which includes recent enhancements
|
||||
vim-filesystem.x86_64 : VIM filesystem layout
|
||||
vim-filesystem.noarch : VIM filesystem layout
|
||||
vim-minimal.x86_64 : A minimal version of the VIM editor
|
||||
|
||||
Name and summary matches only, use "search all" for everything.
|
||||
'''
|
||||
|
||||
yum_invalid_op_text = '''Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
|
||||
No such command: {}. Please use /usr/bin/yum --help
|
||||
'''
|
||||
|
||||
yum_operations = [
|
||||
'check', 'check-update', 'clean', 'deplist', 'distribution-synchronization', 'downgrade', 'erase', 'fs',
|
||||
'fssnapshot', 'groups', 'help', 'history', 'info', 'install', 'langavailable', 'langinfo', 'langinstall',
|
||||
'langlist', 'langremove', 'list', 'load-transaction', 'makecache', 'provides', 'reinstall', 'repo-pkgs', 'repolist',
|
||||
'search', 'shell', 'swap', 'update', 'update-minimal', 'updateinfo', 'upgrade', 'version', ]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
'saerch', 'uninstall',
|
||||
])
|
||||
def test_match(command):
|
||||
assert match(Command('yum {}'.format(command), yum_invalid_op_text.format(command)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command, output', [
|
||||
('vim', ''),
|
||||
('yum', yum_help_text,),
|
||||
('yum help', yum_help_text,),
|
||||
('yum search asdf', yum_unsuccessful_search_text.format('asdf'),),
|
||||
('yum search vim', yum_successful_vim_search_text)
|
||||
])
|
||||
def test_not_match(command, output):
|
||||
assert not match(Command(command, output))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def yum_help(mocker):
|
||||
mock = mocker.patch('subprocess.Popen')
|
||||
mock.return_value.stdout = BytesIO(bytes(yum_help_text.encode('utf-8')))
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'yum_help')
|
||||
def test_get_operations():
|
||||
assert _get_operations() == yum_operations
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'yum_help')
|
||||
@pytest.mark.parametrize('script, output, result', [
|
||||
('yum uninstall', yum_invalid_op_text.format('uninstall'), 'yum remove',),
|
||||
('yum saerch asdf', yum_invalid_op_text.format('saerch'), 'yum search asdf',),
|
||||
('yum hlep', yum_invalid_op_text.format('hlep'), 'yum help',),
|
||||
])
|
||||
def test_get_new_command(script, output, result):
|
||||
assert get_new_command(Command(script, output))[0] == result
|
@ -11,6 +11,11 @@ class TestBash(object):
|
||||
def shell(self):
|
||||
return Bash()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.bash.Popen')
|
||||
return mock
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
@ -73,3 +78,13 @@ class TestBash(object):
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
assert shell.info() == 'Bash 3.5.9'
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = OSError
|
||||
with pytest.raises(OSError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['bash', '-c', 'echo $BASH_VERSION']
|
||||
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from thefuck.const import ARGUMENT_PLACEHOLDER
|
||||
from thefuck.shells import Fish
|
||||
|
||||
|
||||
@ -16,7 +17,8 @@ class TestFish(object):
|
||||
mock.return_value.stdout.read.side_effect = [(
|
||||
b'cd\nfish_config\nfuck\nfunced\nfuncsave\ngrep\nhistory\nll\nls\n'
|
||||
b'man\nmath\npopd\npushd\nruby'),
|
||||
b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git']
|
||||
(b'alias fish_key_reader /usr/bin/fish_key_reader\nalias g git\n'
|
||||
b'alias alias_with_equal_sign=echo\ninvalid_alias'), b'func1\nfunc2', b'']
|
||||
return mock
|
||||
|
||||
@pytest.mark.parametrize('key, value', [
|
||||
@ -27,8 +29,9 @@ class TestFish(object):
|
||||
('THEFUCK_OVERRIDDEN_ALIASES', '\ncut,\n\ngit,\tsed\r')])
|
||||
def test_get_overridden_aliases(self, shell, os_environ, key, value):
|
||||
os_environ[key] = value
|
||||
assert shell._get_overridden_aliases() == {'cd', 'cut', 'git', 'grep',
|
||||
'ls', 'man', 'open', 'sed'}
|
||||
overridden = shell._get_overridden_aliases()
|
||||
assert set(overridden) == {'cd', 'cut', 'git', 'grep',
|
||||
'ls', 'man', 'open', 'sed'}
|
||||
|
||||
@pytest.mark.parametrize('before, after', [
|
||||
('cd', 'cd'),
|
||||
@ -69,7 +72,9 @@ class TestFish(object):
|
||||
'pushd': 'pushd',
|
||||
'ruby': 'ruby',
|
||||
'g': 'git',
|
||||
'fish_key_reader': '/usr/bin/fish_key_reader'}
|
||||
'fish_key_reader': '/usr/bin/fish_key_reader',
|
||||
'alias_with_equal_sign': 'echo'}
|
||||
assert shell.get_aliases() == {'func1': 'func1', 'func2': 'func2'}
|
||||
|
||||
def test_app_alias(self, shell):
|
||||
assert 'function fuck' in shell.app_alias('fuck')
|
||||
@ -78,6 +83,7 @@ class TestFish(object):
|
||||
assert 'TF_SHELL=fish' 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 ARGUMENT_PLACEHOLDER in shell.app_alias('fuck')
|
||||
|
||||
def test_app_alias_alter_history(self, settings, shell):
|
||||
settings.alter_history = True
|
||||
@ -109,3 +115,18 @@ class TestFish(object):
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_get_version(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'fish, version 3.5.9\n']
|
||||
assert shell._get_version() == '3.5.9'
|
||||
assert Popen.call_args[0][0] == ['fish', '--version']
|
||||
|
||||
@pytest.mark.parametrize('side_effect, exception', [
|
||||
([b'\n'], IndexError),
|
||||
(OSError('file not found'), OSError),
|
||||
])
|
||||
def test_get_version_error(self, side_effect, exception, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
with pytest.raises(exception):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['fish', '--version']
|
||||
|
@ -43,3 +43,14 @@ class TestGeneric(object):
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert shell.how_to_configure() is None
|
||||
|
||||
@pytest.mark.parametrize('side_effect, expected_info, warn', [
|
||||
([u'3.5.9'], u'Generic Shell 3.5.9', False),
|
||||
([OSError], u'Generic Shell', True),
|
||||
])
|
||||
def test_info(self, side_effect, expected_info, warn, shell, mocker):
|
||||
warn_mock = mocker.patch('thefuck.shells.generic.warn')
|
||||
shell._get_version = mocker.Mock(side_effect=side_effect)
|
||||
assert shell.info() == expected_info
|
||||
assert warn_mock.called is warn
|
||||
assert shell._get_version.called
|
||||
|
@ -10,6 +10,11 @@ class TestPowershell(object):
|
||||
def shell(self):
|
||||
return Powershell()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.powershell.Popen')
|
||||
return mock
|
||||
|
||||
def test_and_(self, shell):
|
||||
assert shell.and_('ls', 'cd') == '(ls) -and (cd)'
|
||||
|
||||
@ -20,3 +25,20 @@ class TestPowershell(object):
|
||||
|
||||
def test_how_to_configure(self, shell):
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
@pytest.mark.parametrize('side_effect, expected_version, call_args', [
|
||||
([b'''Major Minor Build Revision
|
||||
----- ----- ----- --------
|
||||
5 1 17763 316 \n'''], 'PowerShell 5.1.17763.316', ['powershell.exe']),
|
||||
([IOError, b'PowerShell 6.1.2\n'], 'PowerShell 6.1.2', ['powershell.exe', 'pwsh'])])
|
||||
def test_info(self, side_effect, expected_version, call_args, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
assert shell.info() == expected_version
|
||||
assert Popen.call_count == len(call_args)
|
||||
assert all([Popen.call_args_list[i][0][0][0] == call_arg for i, call_arg in enumerate(call_args)])
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = RuntimeError
|
||||
with pytest.raises(RuntimeError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['powershell.exe', '$PSVersionTable.PSVersion']
|
||||
|
@ -61,3 +61,17 @@ class TestTcsh(object):
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [
|
||||
b'tcsh 6.20.00 (Astron) 2016-11-24 (unknown-unknown-bsd44) \n']
|
||||
assert shell.info() == 'Tcsh 6.20.00'
|
||||
assert Popen.call_args[0][0] == ['tcsh', '--version']
|
||||
|
||||
@pytest.mark.parametrize('side_effect, exception', [
|
||||
([b'\n'], IndexError), (OSError, OSError)])
|
||||
def test_get_version_error(self, side_effect, exception, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = side_effect
|
||||
with pytest.raises(exception):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['tcsh', '--version']
|
||||
|
@ -11,6 +11,11 @@ class TestZsh(object):
|
||||
def shell(self):
|
||||
return Zsh()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def Popen(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.zsh.Popen')
|
||||
return mock
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def shell_aliases(self):
|
||||
os.environ['TF_SHELL_ALIASES'] = (
|
||||
@ -68,3 +73,13 @@ class TestZsh(object):
|
||||
config_exists):
|
||||
config_exists.return_value = False
|
||||
assert not shell.how_to_configure().can_configure_automatically
|
||||
|
||||
def test_info(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = [b'3.5.9']
|
||||
assert shell.info() == 'ZSH 3.5.9'
|
||||
|
||||
def test_get_version_error(self, shell, Popen):
|
||||
Popen.return_value.stdout.read.side_effect = OSError
|
||||
with pytest.raises(OSError):
|
||||
shell._get_version()
|
||||
assert Popen.call_args[0][0] == ['zsh', '-c', 'echo $ZSH_VERSION']
|
||||
|
@ -53,7 +53,8 @@ class TestSettingsFromEnv(object):
|
||||
'THEFUCK_NO_COLORS': 'false',
|
||||
'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
|
||||
'THEFUCK_WAIT_SLOW_COMMAND': '999',
|
||||
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew'})
|
||||
'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew',
|
||||
'THEFUCK_NUM_CLOSE_MATCHES': '359'})
|
||||
settings.init()
|
||||
assert settings.rules == ['bash', 'lisp']
|
||||
assert settings.exclude_rules == ['git', 'vim']
|
||||
@ -63,6 +64,7 @@ class TestSettingsFromEnv(object):
|
||||
assert settings.priority == {'bash': 10, 'vim': 15}
|
||||
assert settings.wait_slow_command == 999
|
||||
assert settings.slow_commands == ['lein', 'react-native', './gradlew']
|
||||
assert settings.num_close_matches == 359
|
||||
|
||||
def test_from_env_with_DEFAULT(self, os_environ, settings):
|
||||
os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
import pytest
|
||||
import warnings
|
||||
from mock import Mock
|
||||
from mock import Mock, call, patch
|
||||
from thefuck.utils import default_settings, \
|
||||
memoize, get_closest, get_all_executables, replace_argument, \
|
||||
get_all_matched_commands, is_app, for_app, cache, \
|
||||
get_valid_history_without_current, _cache
|
||||
get_valid_history_without_current, _cache, get_close_matches
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@ -50,6 +50,18 @@ class TestGetClosest(object):
|
||||
fallback_to_first=False) is None
|
||||
|
||||
|
||||
class TestGetCloseMatches(object):
|
||||
@patch('thefuck.utils.difflib_get_close_matches')
|
||||
def test_call_with_n(self, difflib_mock):
|
||||
get_close_matches('', [], 1)
|
||||
assert difflib_mock.call_args[0][2] == 1
|
||||
|
||||
@patch('thefuck.utils.difflib_get_close_matches')
|
||||
def test_call_without_n(self, difflib_mock, settings):
|
||||
get_close_matches('', [])
|
||||
assert difflib_mock.call_args[0][2] == settings.get('num_close_matches')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def get_aliases(mocker):
|
||||
mocker.patch('thefuck.shells.shell.get_aliases',
|
||||
@ -64,6 +76,24 @@ def test_get_all_executables():
|
||||
assert 'fuck' not in all_callables
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def os_environ_pathsep(monkeypatch, path, pathsep):
|
||||
env = {'PATH': path}
|
||||
monkeypatch.setattr('os.environ', env)
|
||||
monkeypatch.setattr('os.pathsep', pathsep)
|
||||
return env
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep')
|
||||
@pytest.mark.parametrize('path, pathsep', [
|
||||
('/foo:/bar:/baz:/foo/bar', ':'),
|
||||
(r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar', ';')])
|
||||
def test_get_all_executables_pathsep(path, pathsep):
|
||||
with patch('thefuck.utils.Path') as Path_mock:
|
||||
get_all_executables()
|
||||
Path_mock.assert_has_calls([call(p) for p in path.split(pathsep)], True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args, result', [
|
||||
(('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'),
|
||||
(('git brnch', 'brnch', 'branch'), 'git branch')])
|
||||
@ -195,9 +225,12 @@ class TestGetValidHistoryWithoutCurrent(object):
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def history(self, mocker):
|
||||
return mocker.patch('thefuck.shells.shell.get_history',
|
||||
return_value=['le cat', 'fuck', 'ls cat',
|
||||
'diff x', 'nocommand x', u'café ô'])
|
||||
mock = mocker.patch('thefuck.shells.shell.get_history')
|
||||
# Passing as an argument causes `UnicodeDecodeError`
|
||||
# with newer py.test and python 2.7
|
||||
mock.return_value = ['le cat', 'fuck', 'ls cat',
|
||||
'diff x', 'nocommand x', u'café ô']
|
||||
return mock
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def alias(self, mocker):
|
||||
|
@ -55,7 +55,7 @@ class Parser(object):
|
||||
"""It's too dangerous to use `-y` and `-r` together."""
|
||||
group = self._parser.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
'-y', '--yes',
|
||||
'-y', '--yes', '--yeah', '--hard',
|
||||
action='store_true',
|
||||
help='execute fixed command without confirmation')
|
||||
group.add_argument(
|
||||
|
@ -95,7 +95,8 @@ class Settings(dict):
|
||||
return self._rules_from_env(val)
|
||||
elif attr == 'priority':
|
||||
return dict(self._priority_from_env(val))
|
||||
elif attr in ('wait_command', 'history_limit', 'wait_slow_command'):
|
||||
elif attr in ('wait_command', 'history_limit', 'wait_slow_command',
|
||||
'num_close_matches'):
|
||||
return int(val)
|
||||
elif attr in ('require_confirmation', 'no_colors', 'debug',
|
||||
'alter_history', 'instant_mode'):
|
||||
|
@ -42,6 +42,7 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
|
||||
'./gradlew', 'vagrant'],
|
||||
'repeat': False,
|
||||
'instant_mode': False,
|
||||
'num_close_matches': 3,
|
||||
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
|
||||
|
||||
ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
@ -56,7 +57,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
|
||||
'THEFUCK_WAIT_SLOW_COMMAND': 'wait_slow_command',
|
||||
'THEFUCK_SLOW_COMMANDS': 'slow_commands',
|
||||
'THEFUCK_REPEAT': 'repeat',
|
||||
'THEFUCK_INSTANT_MODE': 'instant_mode'}
|
||||
'THEFUCK_INSTANT_MODE': 'instant_mode',
|
||||
'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches'}
|
||||
|
||||
SETTINGS_HEADER = u"""# The Fuck settings file
|
||||
#
|
||||
@ -76,8 +78,12 @@ CONFIGURATION_TIMEOUT = 60
|
||||
|
||||
USER_COMMAND_MARK = u'\u200B' * 10
|
||||
|
||||
LOG_SIZE = 1000
|
||||
|
||||
LOG_SIZE_IN_BYTES = 1024 * 1024
|
||||
|
||||
LOG_SIZE_TO_CLEAN = 10 * 1024
|
||||
|
||||
DIFF_WITH_ALIAS = 0.5
|
||||
|
||||
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
|
||||
|
||||
SHELL_LOGGER_LIMIT = 5
|
||||
|
@ -8,6 +8,7 @@ import sys # noqa: E402
|
||||
from .. import logs # noqa: E402
|
||||
from ..argument_parser import Parser # noqa: E402
|
||||
from ..utils import get_installation_info # noqa: E402
|
||||
from ..shells import shell # noqa: E402
|
||||
from .alias import print_alias # noqa: E402
|
||||
from .fix_command import fix_command # noqa: E402
|
||||
|
||||
@ -20,11 +21,14 @@ def main():
|
||||
parser.print_help()
|
||||
elif known_args.version:
|
||||
logs.version(get_installation_info().version,
|
||||
sys.version.split()[0])
|
||||
elif known_args.command or 'TF_HISTORY' in os.environ:
|
||||
fix_command(known_args)
|
||||
sys.version.split()[0], shell.info())
|
||||
# It's important to check if an alias is being requested before checking if
|
||||
# `TF_HISTORY` is in `os.environ`, otherwise it might mess with subshells.
|
||||
# Check https://github.com/nvbn/thefuck/issues/921 for reference
|
||||
elif known_args.alias:
|
||||
print_alias(known_args)
|
||||
elif known_args.command or 'TF_HISTORY' in os.environ:
|
||||
fix_command(known_args)
|
||||
elif known_args.shell_logger:
|
||||
try:
|
||||
from .shell_logger import shell_logger # noqa: E402
|
||||
|
@ -3,6 +3,7 @@ from ..system import init_output
|
||||
|
||||
init_output()
|
||||
|
||||
import getpass # noqa: E402
|
||||
import os # noqa: E402
|
||||
import json # noqa: E402
|
||||
from tempfile import gettempdir # noqa: E402
|
||||
@ -27,7 +28,9 @@ def _get_shell_pid():
|
||||
|
||||
def _get_not_configured_usage_tracker_path():
|
||||
"""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():
|
||||
|
@ -1,19 +1,26 @@
|
||||
import array
|
||||
import fcntl
|
||||
from functools import partial
|
||||
import mmap
|
||||
import os
|
||||
import pty
|
||||
import signal
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
from ..logs import warn
|
||||
from .. import logs, const
|
||||
|
||||
|
||||
def _read(f, fd):
|
||||
data = os.read(fd, 1024)
|
||||
f.write(data)
|
||||
f.flush()
|
||||
try:
|
||||
f.write(data)
|
||||
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
|
||||
|
||||
|
||||
@ -61,10 +68,12 @@ def shell_logger(output):
|
||||
|
||||
"""
|
||||
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)
|
||||
|
||||
with open(output, 'wb') as f:
|
||||
return_code = _spawn(os.environ['SHELL'], partial(_read, f))
|
||||
fd = os.open(output, os.O_CREAT | os.O_TRUNC | os.O_RDWR)
|
||||
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)
|
||||
|
@ -106,7 +106,7 @@ def how_to_configure_alias(configuration_details):
|
||||
|
||||
if configuration_details.can_configure_automatically:
|
||||
print(
|
||||
u"Or run {bold}fuck{reset} second time for configuring"
|
||||
u"Or run {bold}fuck{reset} a second time to configure"
|
||||
u" it automatically.".format(
|
||||
bold=color(colorama.Style.BRIGHT),
|
||||
reset=color(colorama.Style.RESET_ALL)))
|
||||
@ -134,7 +134,8 @@ def configured_successfully(configuration_details):
|
||||
reload=configuration_details.reload))
|
||||
|
||||
|
||||
def version(thefuck_version, python_version):
|
||||
def version(thefuck_version, python_version, shell_info):
|
||||
sys.stderr.write(
|
||||
u'The Fuck {} using Python {}\n'.format(thefuck_version,
|
||||
python_version))
|
||||
u'The Fuck {} using Python {} and {}\n'.format(thefuck_version,
|
||||
python_version,
|
||||
shell_info))
|
||||
|
@ -1,5 +1,5 @@
|
||||
from ..conf import settings
|
||||
from . import read_log, rerun
|
||||
from . import read_log, rerun, shell_logger
|
||||
|
||||
|
||||
def get_output(script, expanded):
|
||||
@ -12,6 +12,8 @@ def get_output(script, expanded):
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if shell_logger.is_available():
|
||||
return shell_logger.get_output(script)
|
||||
if settings.instant_mode:
|
||||
return read_log.get_output(script)
|
||||
else:
|
||||
|
@ -1,5 +1,7 @@
|
||||
import os
|
||||
import shlex
|
||||
import mmap
|
||||
import re
|
||||
try:
|
||||
from shutil import get_terminal_size
|
||||
except ImportError:
|
||||
@ -18,11 +20,6 @@ def _group_by_calls(log):
|
||||
script_line = None
|
||||
lines = []
|
||||
for line in log:
|
||||
try:
|
||||
line = line.decode()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if const.USER_COMMAND_MARK in line or ps1_counter > 0:
|
||||
if script_line and ps1_counter == 0:
|
||||
yield script_line, lines
|
||||
@ -53,13 +50,14 @@ def _get_script_group_lines(grouped, script):
|
||||
|
||||
|
||||
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))
|
||||
script_lines = _get_script_group_lines(grouped, script)
|
||||
|
||||
screen = pyte.Screen(get_terminal_size().columns, len(script_lines))
|
||||
stream = pyte.Stream(screen)
|
||||
stream.feed(''.join(script_lines))
|
||||
stream.feed('\n'.join(script_lines))
|
||||
return screen.display
|
||||
|
||||
|
||||
@ -91,10 +89,11 @@ def get_output(script):
|
||||
return None
|
||||
|
||||
try:
|
||||
with logs.debug_time(u'Read output from log'), \
|
||||
open(os.environ['THEFUCK_OUTPUT_LOG'], 'rb') as log_file:
|
||||
_skip_old_lines(log_file)
|
||||
lines = _get_output_lines(script, log_file)
|
||||
with logs.debug_time(u'Read output from log'):
|
||||
fd = os.open(os.environ['THEFUCK_OUTPUT_LOG'], os.O_RDONLY)
|
||||
buffer = mmap.mmap(fd, const.LOG_SIZE_IN_BYTES, mmap.MAP_SHARED, mmap.PROT_READ)
|
||||
_skip_old_lines(buffer)
|
||||
lines = _get_output_lines(script, buffer)
|
||||
output = '\n'.join(lines).strip()
|
||||
logs.debug(u'Received output: {}'.format(output))
|
||||
return output
|
||||
|
@ -1,11 +1,25 @@
|
||||
import os
|
||||
import shlex
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from psutil import Process, TimeoutExpired
|
||||
from psutil import AccessDenied, Process, TimeoutExpired
|
||||
from .. import logs
|
||||
from ..conf import settings
|
||||
|
||||
|
||||
def _kill_process(proc):
|
||||
"""Tries to kill the process otherwise just logs a debug message, the
|
||||
process will be killed when thefuck terminates.
|
||||
|
||||
:type proc: Process
|
||||
|
||||
"""
|
||||
try:
|
||||
proc.kill()
|
||||
except AccessDenied:
|
||||
logs.debug(u'Rerun: process PID {} ({}) could not be terminated'.format(
|
||||
proc.pid, proc.exe()))
|
||||
|
||||
|
||||
def _wait_output(popen, is_slow):
|
||||
"""Returns `True` if we can get output of the command in the
|
||||
`settings.wait_command` time.
|
||||
@ -23,8 +37,8 @@ def _wait_output(popen, is_slow):
|
||||
return True
|
||||
except TimeoutExpired:
|
||||
for child in proc.children(recursive=True):
|
||||
child.kill()
|
||||
proc.kill()
|
||||
_kill_process(child)
|
||||
_kill_process(proc)
|
||||
return False
|
||||
|
||||
|
||||
@ -39,8 +53,9 @@ def get_output(script, expanded):
|
||||
env = dict(os.environ)
|
||||
env.update(settings.env)
|
||||
|
||||
is_slow = shlex.split(expanded) in settings.slow_commands
|
||||
with logs.debug_time(u'Call: {}; with env: {}; is slow: '.format(
|
||||
split_expand = shlex.split(expanded)
|
||||
is_slow = split_expand[0] in settings.slow_commands if split_expand else False
|
||||
with logs.debug_time(u'Call: {}; with env: {}; is slow: {}'.format(
|
||||
script, env, is_slow)):
|
||||
result = Popen(expanded, shell=True, stdin=PIPE,
|
||||
stdout=PIPE, stderr=STDOUT, env=env)
|
||||
|
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
|
@ -1,3 +1,4 @@
|
||||
from types import ModuleType
|
||||
from thefuck.specific.apt import apt_available
|
||||
from thefuck.utils import memoize, which
|
||||
from thefuck.shells import shell
|
||||
@ -5,8 +6,14 @@ from thefuck.shells import shell
|
||||
try:
|
||||
from CommandNotFound import CommandNotFound
|
||||
|
||||
command_not_found = CommandNotFound()
|
||||
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:
|
||||
enabled_by_default = False
|
||||
|
||||
@ -21,7 +28,7 @@ def _get_executable(command):
|
||||
@memoize
|
||||
def get_package(executable):
|
||||
try:
|
||||
packages = command_not_found.getPackages(executable)
|
||||
packages = _get_packages(executable)
|
||||
return packages[0][0]
|
||||
except IndexError:
|
||||
# IndexError is thrown when no matching package is found
|
||||
|
@ -8,7 +8,7 @@ enabled_by_default = apt_available
|
||||
@sudo_support
|
||||
@for_app('apt')
|
||||
def match(command):
|
||||
return "Run 'apt list --upgradable' to see them." in command.output
|
||||
return 'apt list --upgradable' in command.output
|
||||
|
||||
|
||||
@sudo_support
|
||||
|
@ -3,7 +3,7 @@ import re
|
||||
from thefuck.utils import for_app, replace_argument
|
||||
|
||||
INVALID_CHOICE = "(?<=Invalid choice: ')(.*)(?=', maybe you meant:)"
|
||||
OPTIONS = "^\s*\*\s(.*)"
|
||||
OPTIONS = "^\\s*\\*\\s(.*)"
|
||||
|
||||
|
||||
@for_app('aws')
|
||||
|
17
thefuck/rules/az_cli.py
Normal file
17
thefuck/rules/az_cli.py
Normal file
@ -0,0 +1,17 @@
|
||||
import re
|
||||
|
||||
from thefuck.utils import for_app, replace_argument
|
||||
|
||||
INVALID_CHOICE = "(?=az)(?:.*): '(.*)' is not in the '.*' command group."
|
||||
OPTIONS = "^The most similar choice to '.*' is:\n\\s*(.*)$"
|
||||
|
||||
|
||||
@for_app('az')
|
||||
def match(command):
|
||||
return "is not in the" in command.output and "command group" in command.output
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
mistake = re.search(INVALID_CHOICE, command.output).group(1)
|
||||
options = re.findall(OPTIONS, command.output, flags=re.MULTILINE)
|
||||
return [replace_argument(command.script, mistake, o) for o in options]
|
@ -20,7 +20,7 @@ def _get_formulas():
|
||||
|
||||
|
||||
def _get_similar_formula(formula_name):
|
||||
return get_closest(formula_name, _get_formulas(), 1, 0.85)
|
||||
return get_closest(formula_name, _get_formulas(), cutoff=0.85)
|
||||
|
||||
|
||||
def match(command):
|
||||
|
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')
|
14
thefuck/rules/cat_dir.py
Normal file
14
thefuck/rules/cat_dir.py
Normal file
@ -0,0 +1,14 @@
|
||||
import os
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('cat', at_least=1)
|
||||
def match(command):
|
||||
return (
|
||||
command.output.startswith('cat: ') and
|
||||
os.path.isdir(command.script_parts[1])
|
||||
)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return command.script.replace('cat', 'ls', 1)
|
@ -2,10 +2,9 @@
|
||||
|
||||
import os
|
||||
import six
|
||||
from difflib import get_close_matches
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
from thefuck.rules import cd_mkdir
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.utils import for_app, get_close_matches
|
||||
|
||||
__author__ = "mmussomele"
|
||||
|
||||
@ -21,9 +20,12 @@ def _get_sub_dirs(parent):
|
||||
@for_app('cd')
|
||||
def match(command):
|
||||
"""Match function copied from cd_mkdir.py"""
|
||||
return (command.script.startswith('cd ')
|
||||
and ('no such file or directory' in command.output.lower()
|
||||
or 'cd: can\'t cd to' in command.output.lower()))
|
||||
return (
|
||||
command.script.startswith('cd ') and any((
|
||||
'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
|
||||
@ -37,7 +39,11 @@ def get_new_command(command):
|
||||
dest = command.script_parts[1].split(os.sep)
|
||||
if dest[-1] == '':
|
||||
dest = dest[:-1]
|
||||
if six.PY2:
|
||||
|
||||
if dest[0] == '':
|
||||
cwd = os.sep
|
||||
dest = dest[1:]
|
||||
elif six.PY2:
|
||||
cwd = os.getcwdu()
|
||||
else:
|
||||
cwd = os.getcwd()
|
||||
|
@ -8,10 +8,11 @@ from thefuck.shells import shell
|
||||
@for_app('cd')
|
||||
def match(command):
|
||||
return (
|
||||
'no such file or directory' in command.output.lower()
|
||||
or 'cd: can\'t cd to' in command.output.lower()
|
||||
or 'the system cannot find the path specified.' in command.output.lower()
|
||||
)
|
||||
command.script.startswith('cd ') and any((
|
||||
'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
|
||||
|
25
thefuck/rules/choco_install.py
Normal file
25
thefuck/rules/choco_install.py
Normal file
@ -0,0 +1,25 @@
|
||||
from thefuck.utils import for_app, which
|
||||
|
||||
|
||||
@for_app("choco", "cinst")
|
||||
def match(command):
|
||||
return ((command.script.startswith('choco install') or 'cinst' in command.script_parts)
|
||||
and 'Installing the following packages' in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
# Find the argument that is the package name
|
||||
for script_part in command.script_parts:
|
||||
if (
|
||||
script_part not in ["choco", "cinst", "install"]
|
||||
# Need exact match (bc chocolatey is a package)
|
||||
and not script_part.startswith('-')
|
||||
# Leading hyphens are parameters; some packages contain them though
|
||||
and '=' not in script_part and '/' not in script_part
|
||||
# These are certainly parameters
|
||||
):
|
||||
return command.script.replace(script_part, script_part + ".install")
|
||||
return []
|
||||
|
||||
|
||||
enabled_by_default = bool(which("choco")) or bool(which("cinst"))
|
@ -8,8 +8,8 @@ from thefuck.specific.dnf import dnf_available
|
||||
regex = re.compile(r'No such command: (.*)\.')
|
||||
|
||||
|
||||
@for_app('dnf')
|
||||
@sudo_support
|
||||
@for_app('dnf')
|
||||
def match(command):
|
||||
return 'no such command' in command.output.lower()
|
||||
|
||||
|
20
thefuck/rules/docker_image_being_used_by_container.py
Normal file
20
thefuck/rules/docker_image_being_used_by_container.py
Normal file
@ -0,0 +1,20 @@
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.shells import shell
|
||||
|
||||
|
||||
@for_app('docker')
|
||||
def match(command):
|
||||
'''
|
||||
Matches a command's output with docker's output
|
||||
warning you that you need to remove a container before removing an image.
|
||||
'''
|
||||
return 'image is being used by running container' in command.output
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
'''
|
||||
Prepends docker container rm -f {container ID} to
|
||||
the previous docker image rm {image ID} command
|
||||
'''
|
||||
container_id = command.output.strip().split(' ')
|
||||
return shell.and_('docker container rm -f {}', '{}').format(container_id[-1], command.script)
|
12
thefuck/rules/docker_login.py
Normal file
12
thefuck/rules/docker_login.py
Normal file
@ -0,0 +1,12 @@
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('docker')
|
||||
def match(command):
|
||||
return ('docker' in command.script
|
||||
and "access denied" in command.output
|
||||
and "may require 'docker login'" in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return 'docker login && {}'.format(command.script)
|
@ -8,16 +8,30 @@ from thefuck.specific.sudo import sudo_support
|
||||
@sudo_support
|
||||
@for_app('docker')
|
||||
def match(command):
|
||||
return 'is not a docker command' in command.output
|
||||
return 'is not a docker command' in command.output or 'Usage: docker' in command.output
|
||||
|
||||
|
||||
def _parse_commands(lines, starts_with):
|
||||
lines = dropwhile(lambda line: not line.startswith(starts_with), lines)
|
||||
lines = islice(lines, 1, None)
|
||||
lines = list(takewhile(lambda line: line.strip(), lines))
|
||||
return [line.strip().split(' ')[0] for line in lines]
|
||||
|
||||
|
||||
def get_docker_commands():
|
||||
proc = subprocess.Popen('docker', stdout=subprocess.PIPE)
|
||||
lines = [line.decode('utf-8') for line in proc.stdout.readlines()]
|
||||
lines = dropwhile(lambda line: not line.startswith('Commands:'), lines)
|
||||
lines = islice(lines, 1, None)
|
||||
lines = list(takewhile(lambda line: line != '\n', lines))
|
||||
return [line.strip().split(' ')[0] for line in lines]
|
||||
proc = subprocess.Popen('docker', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Old version docker returns its output to stdout, while newer version returns to stderr.
|
||||
lines = proc.stdout.readlines() or proc.stderr.readlines()
|
||||
lines = [line.decode('utf-8') for line in lines]
|
||||
|
||||
# Only newer versions of docker have management commands in the help text.
|
||||
if 'Management Commands:\n' in lines:
|
||||
management_commands = _parse_commands(lines, 'Management Commands:')
|
||||
else:
|
||||
management_commands = []
|
||||
regular_commands = _parse_commands(lines, 'Commands:')
|
||||
return management_commands + regular_commands
|
||||
|
||||
|
||||
if which('docker'):
|
||||
@ -26,6 +40,10 @@ if which('docker'):
|
||||
|
||||
@sudo_support
|
||||
def get_new_command(command):
|
||||
if 'Usage:' in command.output and len(command.script_parts) > 1:
|
||||
management_subcommands = _parse_commands(command.output.split('\n'), 'Commands:')
|
||||
return replace_command(command, command.script_parts[2], management_subcommands)
|
||||
|
||||
wrong_command = re.findall(
|
||||
r"docker: '(\w+)' is not a docker command.", command.output)[0]
|
||||
return replace_command(command, wrong_command, get_docker_commands())
|
||||
|
19
thefuck/rules/git_branch_delete_checked_out.py
Normal file
19
thefuck/rules/git_branch_delete_checked_out.py
Normal file
@ -0,0 +1,19 @@
|
||||
from thefuck.shells import shell
|
||||
from thefuck.specific.git import git_support
|
||||
from thefuck.utils import replace_argument
|
||||
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return (
|
||||
("branch -d" in command.script or "branch -D" in command.script)
|
||||
and "error: Cannot delete branch '" in command.output
|
||||
and "' checked out at '" in command.output
|
||||
)
|
||||
|
||||
|
||||
@git_support
|
||||
def get_new_command(command):
|
||||
return shell.and_("git checkout master", "{}").format(
|
||||
replace_argument(command.script, "-d", "-D")
|
||||
)
|
@ -8,7 +8,7 @@ from thefuck.shells import shell
|
||||
|
||||
@git_support
|
||||
def match(command):
|
||||
return ('did not match any file(s) known to git.' in command.output
|
||||
return ('did not match any file(s) known to git' in command.output
|
||||
and "Did you forget to 'git add'?" not in command.output)
|
||||
|
||||
|
||||
@ -18,10 +18,12 @@ def get_branches():
|
||||
stdout=subprocess.PIPE)
|
||||
for line in proc.stdout.readlines():
|
||||
line = line.decode('utf-8')
|
||||
if '->' in line: # Remote HEAD like b' remotes/origin/HEAD -> origin/master'
|
||||
continue
|
||||
if line.startswith('*'):
|
||||
line = line.split(' ')[1]
|
||||
if '/' in line:
|
||||
line = line.split('/')[-1]
|
||||
if line.strip().startswith('remotes/'):
|
||||
line = '/'.join(line.split('/')[2:])
|
||||
yield line.strip()
|
||||
|
||||
|
||||
@ -29,7 +31,7 @@ def get_branches():
|
||||
def get_new_command(command):
|
||||
missing_file = re.findall(
|
||||
r"error: pathspec '([^']*)' "
|
||||
r"did not match any file\(s\) known to git.", command.output)[0]
|
||||
r"did not match any file\(s\) known to git", command.output)[0]
|
||||
closest_branch = utils.get_closest(missing_file, get_branches(),
|
||||
fallback_to_first=False)
|
||||
if closest_branch:
|
||||
|
11
thefuck/rules/git_commit_reset.py
Normal file
11
thefuck/rules/git_commit_reset.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 reset HEAD~'
|
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'
|
@ -6,7 +6,7 @@ from thefuck.specific.git import git_support
|
||||
@git_support
|
||||
def match(command):
|
||||
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):
|
||||
@ -39,6 +39,6 @@ def get_new_command(command):
|
||||
while len(command_parts) > push_idx and command_parts[len(command_parts) - 1][0] != '-':
|
||||
command_parts.pop(len(command_parts) - 1)
|
||||
|
||||
arguments = re.findall(r'git push (.*)', command.output)[0].replace("'", r"\'").strip()
|
||||
arguments = re.findall(r'git push (.*)', command.output)[-1].replace("'", r"\'").strip()
|
||||
return replace_argument(" ".join(command_parts), 'push',
|
||||
'push {}'.format(arguments))
|
||||
|
@ -1,4 +1,4 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.utils import get_close_matches
|
||||
from thefuck.specific.git import git_support
|
||||
|
||||
|
||||
|
28
thefuck/rules/go_unknown_command.py
Normal file
28
thefuck/rules/go_unknown_command.py
Normal file
@ -0,0 +1,28 @@
|
||||
from itertools import dropwhile, islice, takewhile
|
||||
import subprocess
|
||||
|
||||
from thefuck.utils import get_closest, replace_argument, for_app, which, cache
|
||||
|
||||
|
||||
def get_golang_commands():
|
||||
proc = subprocess.Popen('go', stderr=subprocess.PIPE)
|
||||
lines = [line.decode('utf-8').strip() for line in proc.stderr.readlines()]
|
||||
lines = dropwhile(lambda line: line != 'The commands are:', lines)
|
||||
lines = islice(lines, 2, None)
|
||||
lines = takewhile(lambda line: line, lines)
|
||||
return [line.split(' ')[0] for line in lines]
|
||||
|
||||
|
||||
if which('go'):
|
||||
get_docker_commands = cache(which('go'))(get_golang_commands)
|
||||
|
||||
|
||||
@for_app('go')
|
||||
def match(command):
|
||||
return 'unknown command' in command.output
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
closest_subcommand = get_closest(command.script_parts[1], get_golang_commands())
|
||||
return replace_argument(command.script, command.script_parts[1],
|
||||
closest_subcommand)
|
@ -8,5 +8,5 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
apps = re.findall('([^ ]*) \([^)]*\)', command.output)
|
||||
apps = re.findall('([^ ]*) \\([^)]*\\)', command.output)
|
||||
return [command.script + ' --app ' + app for app in apps]
|
||||
|
@ -1,5 +1,5 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.utils import get_closest, get_valid_history_without_current
|
||||
from thefuck.utils import get_close_matches, get_closest, \
|
||||
get_valid_history_without_current
|
||||
|
||||
|
||||
def match(command):
|
||||
|
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
|
@ -1,5 +1,4 @@
|
||||
from thefuck.utils import replace_command, for_app
|
||||
from difflib import get_close_matches
|
||||
from thefuck.utils import for_app, get_close_matches, replace_command
|
||||
import re
|
||||
|
||||
|
||||
@ -25,8 +24,7 @@ def get_new_command(command):
|
||||
available_lifecycles = _getavailable_lifecycles(command)
|
||||
if available_lifecycles and failed_lifecycle:
|
||||
selected_lifecycle = get_close_matches(
|
||||
failed_lifecycle.group(1), available_lifecycles.group(1).split(", "),
|
||||
3, 0.6)
|
||||
failed_lifecycle.group(1), available_lifecycles.group(1).split(", "))
|
||||
return replace_command(command, failed_lifecycle.group(1), selected_lifecycle)
|
||||
else:
|
||||
return []
|
||||
|
15
thefuck/rules/nixos_cmd_not_found.py
Normal file
15
thefuck/rules/nixos_cmd_not_found.py
Normal file
@ -0,0 +1,15 @@
|
||||
import re
|
||||
from thefuck.specific.nix import nix_available
|
||||
from thefuck.shells import shell
|
||||
|
||||
regex = re.compile(r'nix-env -iA ([^\s]*)')
|
||||
enabled_by_default = nix_available
|
||||
|
||||
|
||||
def match(command):
|
||||
return regex.findall(command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
name = regex.findall(command.output)[0]
|
||||
return shell.and_('nix-env -iA {}'.format(name), command.script)
|
@ -1,5 +1,4 @@
|
||||
from difflib import get_close_matches
|
||||
from thefuck.utils import get_all_executables, \
|
||||
from thefuck.utils import get_all_executables, get_close_matches, \
|
||||
get_valid_history_without_current, get_closest, which
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
@ -7,7 +6,8 @@ from thefuck.specific.sudo import sudo_support
|
||||
@sudo_support
|
||||
def match(command):
|
||||
return (not which(command.script_parts[0])
|
||||
and 'not found' in command.output
|
||||
and ('not found' in command.output
|
||||
or 'is not recognized as' in command.output)
|
||||
and bool(get_close_matches(command.script_parts[0],
|
||||
get_all_executables())))
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
""" Fixes wrong package names with pacman or yaourt.
|
||||
|
||||
For example the `llc` program is in package `llvm` so this:
|
||||
yaourt -S llc
|
||||
yay -S llc
|
||||
should be:
|
||||
yaourt -S llvm
|
||||
yay -S llvm
|
||||
"""
|
||||
|
||||
from thefuck.utils import replace_command
|
||||
@ -12,7 +12,7 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env
|
||||
|
||||
def match(command):
|
||||
return (command.script_parts
|
||||
and (command.script_parts[0] in ('pacman', 'yaourt')
|
||||
and (command.script_parts[0] in ('pacman', 'yay', 'yaourt')
|
||||
or command.script_parts[0:2] == ['sudo', 'pacman'])
|
||||
and 'error: target not found:' in command.output)
|
||||
|
||||
|
15
thefuck/rules/pip_install.py
Normal file
15
thefuck/rules/pip_install.py
Normal file
@ -0,0 +1,15 @@
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.specific.sudo import sudo_support
|
||||
|
||||
|
||||
@sudo_support
|
||||
@for_app('pip')
|
||||
def match(command):
|
||||
return ('pip install' in command.script and 'Permission denied' in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
if '--user' not in command.script: # add --user (attempt 1)
|
||||
return command.script.replace(' install ', ' install --user ')
|
||||
|
||||
return 'sudo {}'.format(command.script.replace(' --user', '')) # since --user didn't fix things, let's try sudo (attempt 2)
|
33
thefuck/rules/pyenv_no_such_command.py
Normal file
33
thefuck/rules/pyenv_no_such_command.py
Normal file
@ -0,0 +1,33 @@
|
||||
import re
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
from thefuck.utils import (cache, for_app, replace_argument, replace_command,
|
||||
which)
|
||||
|
||||
COMMON_TYPOS = {
|
||||
'list': ['versions', 'install --list'],
|
||||
'remove': ['uninstall'],
|
||||
}
|
||||
|
||||
|
||||
@for_app('pyenv')
|
||||
def match(command):
|
||||
return 'pyenv: no such command' in command.output
|
||||
|
||||
|
||||
def get_pyenv_commands():
|
||||
proc = Popen(['pyenv', 'commands'], stdout=PIPE)
|
||||
return [line.decode('utf-8').strip() for line in proc.stdout.readlines()]
|
||||
|
||||
|
||||
if which('pyenv'):
|
||||
get_pyenv_commands = cache(which('pyenv'))(get_pyenv_commands)
|
||||
|
||||
|
||||
@for_app('pyenv')
|
||||
def get_new_command(command):
|
||||
broken = re.findall(r"pyenv: no such command `([^']*)'", command.output)[0]
|
||||
matched = [replace_argument(command.script, broken, common_typo)
|
||||
for common_typo in COMMON_TYPOS.get(broken, [])]
|
||||
matched.extend(replace_command(command, broken, get_pyenv_commands()))
|
||||
return matched
|
22
thefuck/rules/remove_shell_prompt_literal.py
Normal file
22
thefuck/rules/remove_shell_prompt_literal.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""Fixes error for commands containing the shell prompt symbol '$'.
|
||||
|
||||
This usually happens when commands are copied from documentations
|
||||
including them in their code blocks.
|
||||
|
||||
Example:
|
||||
> $ git clone https://github.com/nvbn/thefuck.git
|
||||
bash: $: command not found...
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def match(command):
|
||||
return (
|
||||
"$: command not found" in command.output
|
||||
and re.search(r"^[\s]*\$ [\S]+", command.script) is not None
|
||||
)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return command.script.replace("$", "", 1).strip()
|
@ -21,7 +21,7 @@ patterns = ['permission denied',
|
||||
'edspermissionerror',
|
||||
'you don\'t have write permissions',
|
||||
'use `sudo`',
|
||||
'SudoRequiredError',
|
||||
'sudorequirederror',
|
||||
'error: insufficient privileges']
|
||||
|
||||
|
||||
|
@ -2,41 +2,116 @@
|
||||
from thefuck.utils import memoize, get_alias
|
||||
|
||||
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
|
||||
# any new keyboard layout must be appended
|
||||
|
||||
greek = u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''
|
||||
korean = u'''ㅂㅈㄷㄱㅅㅛㅕㅑㅐㅔ[]ㅁㄴㅇㄹㅎㅗㅓㅏㅣ;'ㅋㅌㅊㅍㅠㅜㅡ,./ㅃㅉㄸㄲㅆㅛㅕㅑㅒㅖ{}ㅁㄴㅇㄹㅎㅗㅓㅏㅣ:"ㅋㅌㅊㅍㅠㅜㅡ<>?'''
|
||||
|
||||
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
|
||||
u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
|
||||
u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
|
||||
u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''']
|
||||
u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''',
|
||||
greek,
|
||||
korean]
|
||||
|
||||
source_to_target = {
|
||||
greek: {u';': "q", u'ς': "w", u'ε': "e", u'ρ': "r", u'τ': "t", u'υ': "y",
|
||||
u'θ': "u", u'ι': "i", u'ο': "o", u'π': "p", u'[': "[", u']': "]",
|
||||
u'α': "a", u'σ': "s", u'δ': "d", u'φ': "f", u'γ': "g", u'η': "h",
|
||||
u'ξ': "j", u'κ': "k", u'λ': "l", u'΄': "'", u'ζ': "z", u'χ': "x",
|
||||
u'ψ': "c", u'ω': "v", u'β': "b", u'ν': "n", u'μ': "m", u',': ",",
|
||||
u'.': ".", u'/': "/", u':': "Q", u'΅': "W", u'Ε': "E", u'Ρ': "R",
|
||||
u'Τ': "T", u'Υ': "Y", u'Θ': "U", u'Ι': "I", u'Ο': "O", u'Π': "P",
|
||||
u'{': "{", u'}': "}", u'Α': "A", u'Σ': "S", u'Δ': "D", u'Φ': "F",
|
||||
u'Γ': "G", u'Η': "H", u'Ξ': "J", u'Κ': "K", u'Λ': "L", u'¨': ":",
|
||||
u'"': '"', u'Ζ': "Z", u'Χ': "X", u'Ψ': "C", u'Ω': "V", u'Β': "B",
|
||||
u'Ν': "N", u'Μ': "M", u'<': "<", u'>': ">", u'?': "?", u'ά': "a",
|
||||
u'έ': "e", u'ύ': "y", u'ί': "i", u'ό': "o", u'ή': 'h', u'ώ': u"v",
|
||||
u'Ά': "A", u'Έ': "E", u'Ύ': "Y", u'Ί': "I", u'Ό': "O", u'Ή': "H",
|
||||
u'Ώ': "V"},
|
||||
}
|
||||
|
||||
'''Lists used for decomposing korean letters.'''
|
||||
HEAD_LIST = [u'ㄱ', u'ㄲ', u'ㄴ', u'ㄷ', u'ㄸ', u'ㄹ', u'ㅁ', u'ㅂ', u'ㅃ', u'ㅅ', u'ㅆ',
|
||||
u'ㅇ', u'ㅈ', u'ㅉ', u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
|
||||
BODY_LIST = [u'ㅏ', u'ㅐ', u'ㅑ', u'ㅒ', u'ㅓ', u'ㅔ', u'ㅕ', u'ㅖ', u'ㅗ', u'ㅘ', u'ㅙ',
|
||||
u'ㅚ', u'ㅛ', u'ㅜ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅠ', u'ㅡ', u'ㅢ', u'ㅣ']
|
||||
TAIL_LIST = [u' ', u'ㄱ', u'ㄲ', u'ㄳ', u'ㄴ', u'ㄵ', u'ㄶ', u'ㄷ', u'ㄹ', u'ㄺ', u'ㄻ',
|
||||
u'ㄼ', u'ㄽ', u'ㄾ', u'ㄿ', u'ㅀ', u'ㅁ', u'ㅂ', u'ㅄ', u'ㅅ', u'ㅆ', u'ㅇ', u'ㅈ',
|
||||
u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
|
||||
DOUBLE_LIST = [u'ㅘ', u'ㅙ', u'ㅚ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅢ', u'ㄳ', u'ㄵ', u'ㄶ', u'ㄺ',
|
||||
u'ㄻ', u'ㄼ', u'ㄽ', u'ㄾ', u'ㅀ', u'ㅄ']
|
||||
DOUBLE_MOD_LIST = [u'ㅗㅏ', u'ㅗㅐ', u'ㅗㅣ', u'ㅜㅓ', u'ㅜㅔ', u'ㅜㅣ', u'ㅡㅣ', u'ㄱㅅ',
|
||||
u'ㄴㅈ', u'ㄴㅎ', u'ㄹㄱ', u'ㄹㅁ', u'ㄹㅂ', u'ㄹㅅ', u'ㄹㅌ', u'ㄹㅎ', u'ㅂㅅ']
|
||||
|
||||
|
||||
@memoize
|
||||
def _get_matched_layout(command):
|
||||
# don't use command.split_script here because a layout mismatch will likely
|
||||
# result in a non-splitable sript as per shlex
|
||||
# result in a non-splitable script as per shlex
|
||||
cmd = command.script.split(' ')
|
||||
for source_layout in source_layouts:
|
||||
if all([ch in source_layout or ch in '-_' for ch in cmd[0]]):
|
||||
is_all_match = True
|
||||
for cmd_part in cmd:
|
||||
if not all([ch in source_layout or ch in '-_' for ch in cmd_part]):
|
||||
is_all_match = False
|
||||
break
|
||||
|
||||
if is_all_match:
|
||||
return source_layout
|
||||
|
||||
|
||||
def _switch(ch, layout):
|
||||
if ch in layout:
|
||||
return target_layout[layout.index(ch)]
|
||||
else:
|
||||
return ch
|
||||
return ch
|
||||
|
||||
|
||||
def _switch_command(command, layout):
|
||||
# Layouts with different amount of characters than English
|
||||
if layout in source_to_target:
|
||||
return ''.join(source_to_target[layout].get(ch, ch)
|
||||
for ch in command.script)
|
||||
|
||||
return ''.join(_switch(ch, layout) for ch in command.script)
|
||||
|
||||
|
||||
def _decompose_korean(command):
|
||||
def _change_double(ch):
|
||||
if ch in DOUBLE_LIST:
|
||||
return DOUBLE_MOD_LIST[DOUBLE_LIST.index(ch)]
|
||||
return ch
|
||||
|
||||
hg_str = u''
|
||||
for ch in command.script:
|
||||
if u'가' <= ch <= u'힣':
|
||||
ord_ch = ord(ch) - ord(u'가')
|
||||
hd = ord_ch // 588
|
||||
bd = (ord_ch - 588 * hd) // 28
|
||||
tl = ord_ch - 588 * hd - 28 * bd
|
||||
for ch in [HEAD_LIST[hd], BODY_LIST[bd], TAIL_LIST[tl]]:
|
||||
if ch != ' ':
|
||||
hg_str += _change_double(ch)
|
||||
else:
|
||||
hg_str += _change_double(ch)
|
||||
return hg_str
|
||||
|
||||
|
||||
def match(command):
|
||||
if 'not found' not in command.output:
|
||||
return False
|
||||
if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
|
||||
for ch in command.script):
|
||||
return True
|
||||
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return (matched_layout and
|
||||
_switch_command(command, matched_layout) != get_alias())
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
|
||||
for ch in command.script):
|
||||
command.script = _decompose_korean(command)
|
||||
matched_layout = _get_matched_layout(command)
|
||||
return _switch_command(command, matched_layout)
|
||||
|
13
thefuck/rules/terraform_init.py
Normal file
13
thefuck/rules/terraform_init.py
Normal file
@ -0,0 +1,13 @@
|
||||
from thefuck.shells import shell
|
||||
from thefuck.utils import for_app
|
||||
|
||||
|
||||
@for_app('terraform')
|
||||
def match(command):
|
||||
return ('this module is not yet installed' in command.output.lower() or
|
||||
'initialization required' in command.output.lower()
|
||||
)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return shell.and_('terraform init', command.script)
|
@ -9,5 +9,6 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
path = re.findall(r"touch: cannot touch '(.+)/.+':", command.output)[0]
|
||||
path = re.findall(
|
||||
r"touch: (?:cannot touch ')?(.+)/.+'?:", command.output)[0]
|
||||
return shell.and_(u'mkdir -p {}'.format(path), command.script)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user