mirror of
				https://github.com/nvbn/thefuck.git
				synced 2025-11-04 00:52:04 +00:00 
			
		
		
		
	Compare commits
	
		
			57 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					59e1f7b122 | ||
| 
						 | 
					ff2944086d | ||
| 
						 | 
					ba949f7fd9 | ||
| 
						 | 
					5efcf1019f | ||
| 
						 | 
					70a13406f0 | ||
| 
						 | 
					201c01fc74 | ||
| 
						 | 
					78ef9eec88 | ||
| 
						 | 
					40ab4eb62d | ||
| 
						 | 
					55cb3546df | ||
| 
						 | 
					82902fb50d | ||
| 
						 | 
					828ae537da | ||
| 
						 | 
					1208faaabb | ||
| 
						 | 
					2d81166213 | ||
| 
						 | 
					8093f7cab8 | ||
| 
						 | 
					b946b7d319 | ||
| 
						 | 
					9354a977dd | ||
| 
						 | 
					1eb4ccbcc9 | ||
| 
						 | 
					ce5feaebf7 | ||
| 
						 | 
					ac343fb1bd | ||
| 
						 | 
					7bc619385b | ||
| 
						 | 
					d86dd5f179 | ||
| 
						 | 
					8c1591fbe3 | ||
| 
						 | 
					dfd31872a9 | ||
| 
						 | 
					1eaead4f70 | ||
| 
						 | 
					5b3350b2dd | ||
| 
						 | 
					81b05b9f88 | ||
| 
						 | 
					b5436a2c47 | ||
| 
						 | 
					b08aec02f5 | ||
| 
						 | 
					e6be00a63b | ||
| 
						 | 
					d226b8f258 | ||
| 
						 | 
					f06ebbf2ae | ||
| 
						 | 
					3e522ba787 | ||
| 
						 | 
					25142f81f8 | ||
| 
						 | 
					5fd4f74701 | ||
| 
						 | 
					a0286b402a | ||
| 
						 | 
					bb41f5c4e5 | ||
| 
						 | 
					926e9ef963 | ||
| 
						 | 
					9d46291944 | ||
| 
						 | 
					cc6d90963e | ||
| 
						 | 
					4e755e4799 | ||
| 
						 | 
					1dfd6373ee | ||
| 
						 | 
					fe0785bc42 | ||
| 
						 | 
					142ef6e66c | ||
| 
						 | 
					59745942b5 | ||
| 
						 | 
					692ee53a33 | ||
| 
						 | 
					534782414f | ||
| 
						 | 
					a6bb41e802 | ||
| 
						 | 
					86efc6a252 | ||
| 
						 | 
					89207d6d7c | ||
| 
						 | 
					f6e50bef82 | ||
| 
						 | 
					81042514c8 | ||
| 
						 | 
					2df1a5a45b | ||
| 
						 | 
					72e88d6ba3 | ||
| 
						 | 
					8db3cf6048 | ||
| 
						 | 
					68949a5922 | ||
| 
						 | 
					216d82b464 | ||
| 
						 | 
					97f2d743b3 | 
							
								
								
									
										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. -->
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								.travis.yml
									
									
									
									
									
								
							@@ -2,6 +2,21 @@ language: python
 | 
			
		||||
sudo: false
 | 
			
		||||
matrix:
 | 
			
		||||
  include:
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: xenial
 | 
			
		||||
      python: "nightly"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: xenial
 | 
			
		||||
      python: "3.8-dev"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: xenial
 | 
			
		||||
      python: "3.7-dev"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: xenial
 | 
			
		||||
      python: "3.7"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: trusty
 | 
			
		||||
      python: "3.6-dev"
 | 
			
		||||
    - os: linux
 | 
			
		||||
      dist: trusty
 | 
			
		||||
      python: "3.6"
 | 
			
		||||
@@ -16,6 +31,11 @@ matrix:
 | 
			
		||||
      python: "2.7"
 | 
			
		||||
    - os: osx
 | 
			
		||||
      language: generic
 | 
			
		||||
  allow_failures:
 | 
			
		||||
    - python: nightly
 | 
			
		||||
    - python: 3.8-dev
 | 
			
		||||
    - python: 3.7-dev
 | 
			
		||||
    - python: 3.6-dev
 | 
			
		||||
services:
 | 
			
		||||
  - docker
 | 
			
		||||
addons:
 | 
			
		||||
@@ -24,7 +44,8 @@ addons:
 | 
			
		||||
      - python-commandnotfound
 | 
			
		||||
      - python3-commandnotfound
 | 
			
		||||
before_install:
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update ; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then rm -rf /usr/local/include/c++; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
 | 
			
		||||
  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
 | 
			
		||||
@@ -39,7 +60,7 @@ script:
 | 
			
		||||
  - flake8
 | 
			
		||||
  - export COVERAGE_PYTHON_VERSION=python-${TRAVIS_PYTHON_VERSION:0:1}
 | 
			
		||||
  - export RUN_TESTS="coverage run --source=thefuck,tests -m py.test -v --capture=sys tests"
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION != 3.6 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION == 3.7 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION != 3.7 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
 | 
			
		||||
after_success:
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION == 3.6 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
 | 
			
		||||
  - if [[ $TRAVIS_PYTHON_VERSION == 3.7 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								README.md
									
									
									
									
									
								
							@@ -99,23 +99,27 @@ Reading package lists... Done
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
On OS X, you can install *The Fuck* via [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, install *The Fuck* with the following commands:
 | 
			
		||||
On Ubuntu / Mint, install *The Fuck* with the following commands:
 | 
			
		||||
```bash
 | 
			
		||||
sudo apt update
 | 
			
		||||
sudo apt install python3-dev python3-pip
 | 
			
		||||
sudo apt install python3-dev python3-pip python3-setuptools
 | 
			
		||||
sudo pip3 install thefuck
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
On FreeBSD, install *The Fuck* with the following commands:
 | 
			
		||||
```bash
 | 
			
		||||
sudo portsnap fetch update
 | 
			
		||||
cd /usr/ports/misc/thefuck && sudo make install clean
 | 
			
		||||
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`:
 | 
			
		||||
@@ -141,10 +145,10 @@ eval $(thefuck --alias FUCK)
 | 
			
		||||
Changes are only available in a new shell session. To make changes immediately
 | 
			
		||||
available, run `source ~/.bashrc` (or your shell config file like `.zshrc`).
 | 
			
		||||
 | 
			
		||||
To run fixed commands without confirmation, use the `-y` option:
 | 
			
		||||
To run fixed commands without confirmation, use the `--yeah` option (or just `-y` for short):
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
fuck -y
 | 
			
		||||
fuck --yeah
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To fix commands recursively until succeeding, use the `-r` option:
 | 
			
		||||
@@ -170,8 +174,10 @@ 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 ..`;
 | 
			
		||||
@@ -183,6 +189,7 @@ following rules are enabled by default:
 | 
			
		||||
* `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`;
 | 
			
		||||
* `dry` – fixes repetitions like `git git push`;
 | 
			
		||||
* `fab_command_not_found` – fix misspelled fabric commands;
 | 
			
		||||
@@ -197,6 +204,7 @@ following rules are enabled by default:
 | 
			
		||||
* `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`);
 | 
			
		||||
@@ -226,8 +234,8 @@ following rules are enabled by default:
 | 
			
		||||
* `go_run` – appends `.go` extension when compiling/running Go programs;
 | 
			
		||||
* `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;
 | 
			
		||||
@@ -239,6 +247,7 @@ following rules are enabled by default:
 | 
			
		||||
* `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;
 | 
			
		||||
@@ -247,7 +256,7 @@ following rules are enabled by default:
 | 
			
		||||
* `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`;
 | 
			
		||||
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
 | 
			
		||||
@@ -256,17 +265,19 @@ following rules are enabled by default:
 | 
			
		||||
* `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;
 | 
			
		||||
* `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`;
 | 
			
		||||
@@ -299,13 +310,14 @@ The following rules are enabled by default on specific platforms only:
 | 
			
		||||
* `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`.
 | 
			
		||||
* `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`.
 | 
			
		||||
 | 
			
		||||
The following commands are bundled with *The Fuck*, but are not enabled by
 | 
			
		||||
default:
 | 
			
		||||
@@ -381,7 +393,8 @@ Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefu
 | 
			
		||||
* `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`.
 | 
			
		||||
 | 
			
		||||
An example of `settings.py`:
 | 
			
		||||
 | 
			
		||||
@@ -396,6 +409,7 @@ debug = False
 | 
			
		||||
history_limit = 9999
 | 
			
		||||
wait_slow_command = 20
 | 
			
		||||
slow_commands = ['react-native', 'gradle']
 | 
			
		||||
num_close_matches = 5
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Or via environment variables:
 | 
			
		||||
@@ -411,7 +425,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:
 | 
			
		||||
 | 
			
		||||
@@ -423,6 +438,7 @@ export THEFUCK_WAIT_COMMAND=10
 | 
			
		||||
export THEFUCK_NO_COLORS='false'
 | 
			
		||||
export THEFUCK_PRIORITY='no_command=9999:apt_get=100'
 | 
			
		||||
export THEFUCK_HISTORY_LIMIT='2000'
 | 
			
		||||
export THEFUCK_NUM_CLOSE_MATCHES='5'
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Third-party packages with rules
 | 
			
		||||
@@ -452,7 +468,7 @@ then reading the log.
 | 
			
		||||
 | 
			
		||||
[![gif with instant mode][instant-mode-gif-link]][instant-mode-gif-link]
 | 
			
		||||
 | 
			
		||||
Currently, instant mode only supports Python 3 with bash or zsh.
 | 
			
		||||
Currently, instant mode only supports Python 3 with bash or zsh. zsh's autocorrect function also needs to be disabled in order for thefuck to work properly.
 | 
			
		||||
 | 
			
		||||
To enable instant mode, add `--enable-experimental-instant-mode`
 | 
			
		||||
to the alias initialization in `.bashrc`, `.bash_profile` or `.zshrc`.
 | 
			
		||||
@@ -482,4 +498,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/
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ environment:
 | 
			
		||||
    - 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/*')
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							@@ -31,7 +31,7 @@ elif (3, 0) < version < (3, 4):
 | 
			
		||||
          ' ({}.{} detected).'.format(*version))
 | 
			
		||||
    sys.exit(-1)
 | 
			
		||||
 | 
			
		||||
VERSION = '3.26'
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										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,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))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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' \
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
@@ -3,23 +3,21 @@ from thefuck.rules.git_merge_unrelated import match, get_new_command
 | 
			
		||||
from thefuck.types import Command
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def output():
 | 
			
		||||
    return 'fatal: refusing to merge unrelated histories'
 | 
			
		||||
output = 'fatal: refusing to merge unrelated histories'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_match(output):
 | 
			
		||||
def test_match():
 | 
			
		||||
    assert match(Command('git merge test', output))
 | 
			
		||||
    assert not match(Command('git merge master', ''))
 | 
			
		||||
    assert not match(Command('ls', output))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.parametrize('command, new_command', [
 | 
			
		||||
    (Command('git merge local', output()),
 | 
			
		||||
    (Command('git merge local', output),
 | 
			
		||||
     'git merge local --allow-unrelated-histories'),
 | 
			
		||||
    (Command('git merge -m "test" local', output()),
 | 
			
		||||
    (Command('git merge -m "test" local', output),
 | 
			
		||||
     'git merge -m "test" local --allow-unrelated-histories'),
 | 
			
		||||
    (Command('git merge -m "test local" local', output()),
 | 
			
		||||
    (Command('git merge -m "test local" local', output),
 | 
			
		||||
     'git merge -m "test local" local --allow-unrelated-histories')])
 | 
			
		||||
def test_get_new_command(command, new_command):
 | 
			
		||||
    assert get_new_command(command) == new_command
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,10 @@ def test_not_match(output, script, branch_name):
 | 
			
		||||
    ('git -c test=test push --quiet origin', 'master',
 | 
			
		||||
     'git -c test=test push --set-upstream origin master --quiet'),
 | 
			
		||||
    ('git push', "test's",
 | 
			
		||||
     "git push --set-upstream origin test\\'s")])
 | 
			
		||||
     "git push --set-upstream origin test\\'s"),
 | 
			
		||||
    ('git push --force', 'master',
 | 
			
		||||
     'git push --set-upstream origin master --force'),
 | 
			
		||||
    ('git push --force-with-lease', 'master',
 | 
			
		||||
     'git push --set-upstream origin master --force-with-lease')])
 | 
			
		||||
def test_get_new_command(output, script, branch_name, new_command):
 | 
			
		||||
    assert get_new_command(Command(script, output)) == new_command
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
@@ -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))
 | 
			
		||||
@@ -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'
 | 
			
		||||
 
 | 
			
		||||
@@ -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,7 +29,8 @@ 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',
 | 
			
		||||
        overridden = shell._get_overridden_aliases()
 | 
			
		||||
        assert set(overridden) == {'cd', 'cut', 'git', 'grep',
 | 
			
		||||
                                   'ls', 'man', 'open', 'sed'}
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('before, after', [
 | 
			
		||||
@@ -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')])
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ class Parser(object):
 | 
			
		||||
        """It's too dangerous to use `-y` and `-r` together."""
 | 
			
		||||
        group = self._parser.add_mutually_exclusive_group()
 | 
			
		||||
        group.add_argument(
 | 
			
		||||
            '-y', '--yes',
 | 
			
		||||
            '-y', '--yes', '--yeah',
 | 
			
		||||
            action='store_true',
 | 
			
		||||
            help='execute fixed command without confirmation')
 | 
			
		||||
        group.add_argument(
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
#
 | 
			
		||||
@@ -81,3 +83,7 @@ LOG_SIZE_IN_BYTES = 1024 * 1024
 | 
			
		||||
LOG_SIZE_TO_CLEAN = 10 * 1024
 | 
			
		||||
 | 
			
		||||
DIFF_WITH_ALIAS = 0.5
 | 
			
		||||
 | 
			
		||||
SHELL_LOGGER_SOCKET_ENV = 'SHELL_LOGGER_SOCKET'
 | 
			
		||||
 | 
			
		||||
SHELL_LOGGER_LIMIT = 5
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import sys  # noqa: E402
 | 
			
		||||
from .. import logs  # noqa: E402
 | 
			
		||||
from ..argument_parser import Parser  # noqa: E402
 | 
			
		||||
from ..utils import get_installation_info  # noqa: E402
 | 
			
		||||
from ..shells import shell  # noqa: E402
 | 
			
		||||
from .alias import print_alias  # noqa: E402
 | 
			
		||||
from .fix_command import fix_command  # noqa: E402
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +21,7 @@ def main():
 | 
			
		||||
        parser.print_help()
 | 
			
		||||
    elif known_args.version:
 | 
			
		||||
        logs.version(get_installation_info().version,
 | 
			
		||||
                     sys.version.split()[0])
 | 
			
		||||
                     sys.version.split()[0], shell.info())
 | 
			
		||||
    elif known_args.command or 'TF_HISTORY' in os.environ:
 | 
			
		||||
        fix_command(known_args)
 | 
			
		||||
    elif known_args.alias:
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
@@ -6,8 +6,8 @@ from thefuck.utils import for_app, eager, replace_command
 | 
			
		||||
enabled_by_default = apt_available
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@for_app('apt', 'apt-get', 'apt-cache')
 | 
			
		||||
@sudo_support
 | 
			
		||||
@for_app('apt', 'apt-get', 'apt-cache')
 | 
			
		||||
def match(command):
 | 
			
		||||
    return 'E: Invalid operation' in command.output
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
							
								
								
									
										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~'
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 []
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -2,20 +2,48 @@
 | 
			
		||||
from thefuck.utils import memoize, get_alias
 | 
			
		||||
 | 
			
		||||
target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?'''
 | 
			
		||||
# any new keyboard layout must be appended
 | 
			
		||||
 | 
			
		||||
greek = u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''
 | 
			
		||||
 | 
			
		||||
source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
 | 
			
		||||
                  u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
 | 
			
		||||
                  u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰٔء><؟''',
 | 
			
		||||
                  u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?''',
 | 
			
		||||
                  u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''']
 | 
			
		||||
                  u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''',
 | 
			
		||||
                  greek]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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"},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -27,12 +55,18 @@ def _switch(ch, layout):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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 match(command):
 | 
			
		||||
    if 'not found' not in command.output:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    matched_layout = _get_matched_layout(command)
 | 
			
		||||
    return (matched_layout and
 | 
			
		||||
            _switch_command(command, matched_layout) != get_alias())
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,6 @@ def match(command):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_new_command(command):
 | 
			
		||||
    path = re.findall(r"touch: cannot touch '(.+)/.+':", command.output)[0]
 | 
			
		||||
    path = path = re.findall(
 | 
			
		||||
        r"touch: (?:cannot touch ')?(.+)/.+'?:", command.output)[0]
 | 
			
		||||
    return shell.and_(u'mkdir -p {}'.format(path), command.script)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,8 @@ shells = {'bash': Bash,
 | 
			
		||||
          'zsh': Zsh,
 | 
			
		||||
          'csh': Tcsh,
 | 
			
		||||
          'tcsh': Tcsh,
 | 
			
		||||
          'powershell': Powershell}
 | 
			
		||||
          'powershell': Powershell,
 | 
			
		||||
          'pwsh': Powershell}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_shell_from_env():
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
import os
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from tempfile import gettempdir
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
 | 
			
		||||
from ..utils import memoize
 | 
			
		||||
from ..utils import DEVNULL, memoize
 | 
			
		||||
from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bash(Generic):
 | 
			
		||||
    friendly_name = 'Bash'
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the function
 | 
			
		||||
        return '''
 | 
			
		||||
@@ -19,8 +22,8 @@ class Bash(Generic):
 | 
			
		||||
                export TF_HISTORY=$(fc -ln -10);
 | 
			
		||||
                export PYTHONIOENCODING=utf-8;
 | 
			
		||||
                TF_CMD=$(
 | 
			
		||||
                    thefuck {argument_placeholder} $@
 | 
			
		||||
                ) && eval $TF_CMD;
 | 
			
		||||
                    thefuck {argument_placeholder} "$@"
 | 
			
		||||
                ) && eval "$TF_CMD";
 | 
			
		||||
                unset TF_HISTORY;
 | 
			
		||||
                export PYTHONIOENCODING=$TF_PYTHONIOENCODING;
 | 
			
		||||
                {alter_history}
 | 
			
		||||
@@ -78,6 +81,12 @@ class Bash(Generic):
 | 
			
		||||
            config = 'bash config'
 | 
			
		||||
 | 
			
		||||
        return self._create_shell_configuration(
 | 
			
		||||
            content=u'eval $(thefuck --alias)',
 | 
			
		||||
            content=u'eval "$(thefuck --alias)"',
 | 
			
		||||
            path=config,
 | 
			
		||||
            reload=u'source {}'.format(config))
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        proc = Popen(['bash', '-c', 'echo $BASH_VERSION'],
 | 
			
		||||
                     stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
        return proc.stdout.read().decode('utf-8').strip()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import sys
 | 
			
		||||
import six
 | 
			
		||||
from .. import logs
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..const import ARGUMENT_PLACEHOLDER
 | 
			
		||||
from ..utils import DEVNULL, cache
 | 
			
		||||
from .generic import Generic
 | 
			
		||||
 | 
			
		||||
@@ -20,22 +21,32 @@ def _get_functions(overridden):
 | 
			
		||||
def _get_aliases(overridden):
 | 
			
		||||
    aliases = {}
 | 
			
		||||
    proc = Popen(['fish', '-ic', 'alias'], stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
    alias_out = proc.stdout.read().decode('utf-8').strip().split('\n')
 | 
			
		||||
    for alias in alias_out:
 | 
			
		||||
        name, value = alias.replace('alias ', '', 1).split(' ', 1)
 | 
			
		||||
    alias_out = proc.stdout.read().decode('utf-8').strip()
 | 
			
		||||
    if not alias_out:
 | 
			
		||||
        return aliases
 | 
			
		||||
    for alias in alias_out.split('\n'):
 | 
			
		||||
        for separator in (' ', '='):
 | 
			
		||||
            split_alias = alias.replace('alias ', '', 1).split(separator, 1)
 | 
			
		||||
            if len(split_alias) == 2:
 | 
			
		||||
                name, value = split_alias
 | 
			
		||||
                break
 | 
			
		||||
        else:
 | 
			
		||||
            continue
 | 
			
		||||
        if name not in overridden:
 | 
			
		||||
            aliases[name] = value
 | 
			
		||||
    return aliases
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Fish(Generic):
 | 
			
		||||
    friendly_name = 'Fish Shell'
 | 
			
		||||
 | 
			
		||||
    def _get_overridden_aliases(self):
 | 
			
		||||
        overridden = os.environ.get('THEFUCK_OVERRIDDEN_ALIASES',
 | 
			
		||||
                                    os.environ.get('TF_OVERRIDDEN_ALIASES', ''))
 | 
			
		||||
        default = {'cd', 'grep', 'ls', 'man', 'open'}
 | 
			
		||||
        for alias in overridden.split(','):
 | 
			
		||||
            default.add(alias.strip())
 | 
			
		||||
        return default
 | 
			
		||||
        return sorted(default)
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        if settings.alter_history:
 | 
			
		||||
@@ -48,11 +59,11 @@ class Fish(Generic):
 | 
			
		||||
        return ('function {0} -d "Correct your previous console command"\n'
 | 
			
		||||
                '  set -l fucked_up_command $history[1]\n'
 | 
			
		||||
                '  env TF_SHELL=fish TF_ALIAS={0} PYTHONIOENCODING=utf-8'
 | 
			
		||||
                ' thefuck $fucked_up_command | read -l unfucked_command\n'
 | 
			
		||||
                ' thefuck $fucked_up_command {2} $argv | read -l unfucked_command\n'
 | 
			
		||||
                '  if [ "$unfucked_command" != "" ]\n'
 | 
			
		||||
                '    eval $unfucked_command\n{1}'
 | 
			
		||||
                '  end\n'
 | 
			
		||||
                'end').format(alias_name, alter_history)
 | 
			
		||||
                'end').format(alias_name, alter_history, ARGUMENT_PLACEHOLDER)
 | 
			
		||||
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        overridden = self._get_overridden_aliases()
 | 
			
		||||
@@ -95,6 +106,11 @@ class Fish(Generic):
 | 
			
		||||
            path='~/.config/fish/config.fish',
 | 
			
		||||
            reload='fish')
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        proc = Popen(['fish', '--version'], stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
        return proc.stdout.read().decode('utf-8').split()[-1]
 | 
			
		||||
 | 
			
		||||
    def put_to_history(self, command):
 | 
			
		||||
        try:
 | 
			
		||||
            return self._put_to_history(command)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ ShellConfiguration = namedtuple('ShellConfiguration', (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Generic(object):
 | 
			
		||||
    friendly_name = 'Generic Shell'
 | 
			
		||||
 | 
			
		||||
    def get_aliases(self):
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
@@ -34,8 +36,8 @@ class Generic(object):
 | 
			
		||||
        return command_script
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        return "alias {0}='eval $(TF_ALIAS={0} PYTHONIOENCODING=utf-8 " \
 | 
			
		||||
               "thefuck $(fc -ln -1))'".format(alias_name)
 | 
			
		||||
        return """alias {0}='eval "$(TF_ALIAS={0} PYTHONIOENCODING=utf-8 """ \
 | 
			
		||||
               """thefuck "$(fc -ln -1)")"'""".format(alias_name)
 | 
			
		||||
 | 
			
		||||
    def instant_mode_alias(self, alias_name):
 | 
			
		||||
        warn("Instant mode not supported by your shell")
 | 
			
		||||
@@ -82,7 +84,7 @@ class Generic(object):
 | 
			
		||||
        encoded = self.encode_utf8(command)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            splitted = [s.replace("??", "\ ") for s in shlex.split(encoded.replace('\ ', '??'))]
 | 
			
		||||
            splitted = [s.replace("??", "\\ ") for s in shlex.split(encoded.replace('\\ ', '??'))]
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            splitted = encoded.split(' ')
 | 
			
		||||
 | 
			
		||||
@@ -131,6 +133,19 @@ class Generic(object):
 | 
			
		||||
                'type', 'typeset', 'ulimit', 'umask', 'unalias', 'unset',
 | 
			
		||||
                'until', 'wait', 'while']
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def info(self):
 | 
			
		||||
        """Returns the name and version of the current shell"""
 | 
			
		||||
        try:
 | 
			
		||||
            version = self._get_version()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            warn(u'Could not determine shell version: {}'.format(e))
 | 
			
		||||
            version = ''
 | 
			
		||||
        return u'{} {}'.format(self.friendly_name, version).rstrip()
 | 
			
		||||
 | 
			
		||||
    def _create_shell_configuration(self, content, path, reload):
 | 
			
		||||
        return ShellConfiguration(
 | 
			
		||||
            content=content,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,11 @@
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from ..utils import DEVNULL
 | 
			
		||||
from .generic import Generic, ShellConfiguration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Powershell(Generic):
 | 
			
		||||
    friendly_name = 'PowerShell'
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        return 'function ' + alias_name + ' {\n' \
 | 
			
		||||
               '    $history = (Get-History -Count 1).CommandLine;\n' \
 | 
			
		||||
@@ -12,6 +16,7 @@ class Powershell(Generic):
 | 
			
		||||
               '            else { iex "$fuck"; }\n' \
 | 
			
		||||
               '        }\n' \
 | 
			
		||||
               '    }\n' \
 | 
			
		||||
               '    [Console]::ResetColor() \n' \
 | 
			
		||||
               '}\n'
 | 
			
		||||
 | 
			
		||||
    def and_(self, *commands):
 | 
			
		||||
@@ -23,3 +28,16 @@ class Powershell(Generic):
 | 
			
		||||
            path='$profile',
 | 
			
		||||
            reload='& $profile',
 | 
			
		||||
            can_configure_automatically=False)
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        try:
 | 
			
		||||
            proc = Popen(
 | 
			
		||||
                ['powershell.exe', '$PSVersionTable.PSVersion'],
 | 
			
		||||
                stdout=PIPE,
 | 
			
		||||
                stderr=DEVNULL)
 | 
			
		||||
            version = proc.stdout.read().decode('utf-8').rstrip().split('\n')
 | 
			
		||||
            return '.'.join(version[-1].split())
 | 
			
		||||
        except IOError:
 | 
			
		||||
            proc = Popen(['pwsh', '--version'], stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
            return proc.stdout.read().decode('utf-8').split()[-1]
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tcsh(Generic):
 | 
			
		||||
    friendly_name = 'Tcsh'
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        return ("alias {0} 'setenv TF_SHELL tcsh && setenv TF_ALIAS {0} && "
 | 
			
		||||
                "set fucked_cmd=`history -h 2 | head -n 1` && "
 | 
			
		||||
@@ -35,3 +37,8 @@ class Tcsh(Generic):
 | 
			
		||||
            content=u'eval `thefuck --alias`',
 | 
			
		||||
            path='~/.tcshrc',
 | 
			
		||||
            reload='tcsh')
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        proc = Popen(['tcsh', '--version'], stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
        return proc.stdout.read().decode('utf-8').split()[1]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
from time import time
 | 
			
		||||
import os
 | 
			
		||||
from subprocess import Popen, PIPE
 | 
			
		||||
from tempfile import gettempdir
 | 
			
		||||
from uuid import uuid4
 | 
			
		||||
from ..conf import settings
 | 
			
		||||
from ..const import ARGUMENT_PLACEHOLDER, USER_COMMAND_MARK
 | 
			
		||||
from ..utils import memoize
 | 
			
		||||
from ..utils import DEVNULL, memoize
 | 
			
		||||
from .generic import Generic
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Zsh(Generic):
 | 
			
		||||
    friendly_name = 'ZSH'
 | 
			
		||||
 | 
			
		||||
    def app_alias(self, alias_name):
 | 
			
		||||
        # It is VERY important to have the variables declared WITHIN the function
 | 
			
		||||
        return '''
 | 
			
		||||
@@ -16,8 +19,10 @@ class Zsh(Generic):
 | 
			
		||||
                TF_PYTHONIOENCODING=$PYTHONIOENCODING;
 | 
			
		||||
                export TF_SHELL=zsh;
 | 
			
		||||
                export TF_ALIAS={name};
 | 
			
		||||
                export TF_SHELL_ALIASES=$(alias);
 | 
			
		||||
                export TF_HISTORY="$(fc -ln -10)";
 | 
			
		||||
                TF_SHELL_ALIASES=$(alias);
 | 
			
		||||
                export TF_SHELL_ALIASES;
 | 
			
		||||
                TF_HISTORY="$(fc -ln -10)";
 | 
			
		||||
                export TF_HISTORY;
 | 
			
		||||
                export PYTHONIOENCODING=utf-8;
 | 
			
		||||
                TF_CMD=$(
 | 
			
		||||
                    thefuck {argument_placeholder} $@
 | 
			
		||||
@@ -49,7 +54,7 @@ class Zsh(Generic):
 | 
			
		||||
                export THEFUCK_INSTANT_MODE=True;
 | 
			
		||||
                export THEFUCK_OUTPUT_LOG={log};
 | 
			
		||||
                thefuck --shell-logger {log};
 | 
			
		||||
                rm {log};
 | 
			
		||||
                rm -f {log};
 | 
			
		||||
                exit
 | 
			
		||||
            '''.format(log=log_path)
 | 
			
		||||
 | 
			
		||||
@@ -83,3 +88,9 @@ class Zsh(Generic):
 | 
			
		||||
            content=u'eval $(thefuck --alias)',
 | 
			
		||||
            path='~/.zshrc',
 | 
			
		||||
            reload='source ~/.zshrc')
 | 
			
		||||
 | 
			
		||||
    def _get_version(self):
 | 
			
		||||
        """Returns the version of the current shell"""
 | 
			
		||||
        proc = Popen(['zsh', '-c', 'echo $ZSH_VERSION'],
 | 
			
		||||
                     stdout=PIPE, stderr=DEVNULL)
 | 
			
		||||
        return proc.stdout.read().decode('utf-8').strip()
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,9 @@ def get_pkgfile(command):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def archlinux_env():
 | 
			
		||||
    if utils.which('yaourt'):
 | 
			
		||||
    if utils.which('yay'):
 | 
			
		||||
        pacman = 'yay'
 | 
			
		||||
    elif utils.which('yaourt'):
 | 
			
		||||
        pacman = 'yaourt'
 | 
			
		||||
    elif utils.which('pacman'):
 | 
			
		||||
        pacman = 'sudo pacman'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import msvcrt
 | 
			
		||||
import win_unicode_console
 | 
			
		||||
from .. import const
 | 
			
		||||
@@ -12,20 +11,18 @@ def init_output():
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_key():
 | 
			
		||||
    ch = msvcrt.getch()
 | 
			
		||||
    if ch in (b'\x00', b'\xe0'):  # arrow or function key prefix?
 | 
			
		||||
        ch = msvcrt.getch()  # second call returns the actual key code
 | 
			
		||||
    ch = msvcrt.getwch()
 | 
			
		||||
    if ch in ('\x00', '\xe0'):  # arrow or function key prefix?
 | 
			
		||||
        ch = msvcrt.getwch()  # second call returns the actual key code
 | 
			
		||||
 | 
			
		||||
    if ch in const.KEY_MAPPING:
 | 
			
		||||
        return const.KEY_MAPPING[ch]
 | 
			
		||||
    if ch == b'H':
 | 
			
		||||
    if ch == 'H':
 | 
			
		||||
        return const.KEY_UP
 | 
			
		||||
    if ch == b'P':
 | 
			
		||||
    if ch == 'P':
 | 
			
		||||
        return const.KEY_DOWN
 | 
			
		||||
 | 
			
		||||
    encoding = (sys.stdout.encoding
 | 
			
		||||
                or os.environ.get('PYTHONIOENCODING', 'utf-8'))
 | 
			
		||||
    return ch.decode(encoding)
 | 
			
		||||
    return ch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def open_command(arg):
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ class CommandSelector(object):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def value(self):
 | 
			
		||||
        """:rtype hefuck.types.CorrectedCommand"""
 | 
			
		||||
        """:rtype thefuck.types.CorrectedCommand"""
 | 
			
		||||
        return self._commands[self._index]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,12 @@ import os
 | 
			
		||||
import pickle
 | 
			
		||||
import re
 | 
			
		||||
import shelve
 | 
			
		||||
import sys
 | 
			
		||||
import six
 | 
			
		||||
from decorator import decorator
 | 
			
		||||
from difflib import get_close_matches
 | 
			
		||||
from difflib import get_close_matches as difflib_get_close_matches
 | 
			
		||||
from functools import wraps
 | 
			
		||||
from .logs import warn
 | 
			
		||||
from .logs import warn, exception
 | 
			
		||||
from .conf import settings
 | 
			
		||||
from .system import Path
 | 
			
		||||
 | 
			
		||||
@@ -86,16 +87,23 @@ def default_settings(params):
 | 
			
		||||
    return decorator(_default_settings)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_closest(word, possibilities, n=3, cutoff=0.6, fallback_to_first=True):
 | 
			
		||||
def get_closest(word, possibilities, cutoff=0.6, fallback_to_first=True):
 | 
			
		||||
    """Returns closest match or just first from possibilities."""
 | 
			
		||||
    possibilities = list(possibilities)
 | 
			
		||||
    try:
 | 
			
		||||
        return get_close_matches(word, possibilities, n, cutoff)[0]
 | 
			
		||||
        return difflib_get_close_matches(word, possibilities, 1, cutoff)[0]
 | 
			
		||||
    except IndexError:
 | 
			
		||||
        if fallback_to_first:
 | 
			
		||||
            return possibilities[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_close_matches(word, possibilities, n=None, cutoff=0.6):
 | 
			
		||||
    """Overrides `difflib.get_close_match` to controle argument `n`."""
 | 
			
		||||
    if n is None:
 | 
			
		||||
        n = settings.num_close_matches
 | 
			
		||||
    return difflib_get_close_matches(word, possibilities, n, cutoff)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@memoize
 | 
			
		||||
def get_all_executables():
 | 
			
		||||
    from thefuck.shells import shell
 | 
			
		||||
@@ -110,7 +118,7 @@ def get_all_executables():
 | 
			
		||||
    tf_entry_points = ['thefuck', 'fuck']
 | 
			
		||||
 | 
			
		||||
    bins = [exe.name.decode('utf8') if six.PY2 else exe.name
 | 
			
		||||
            for path in os.environ.get('PATH', '').split(':')
 | 
			
		||||
            for path in os.environ.get('PATH', '').split(os.pathsep)
 | 
			
		||||
            for exe in _safe(lambda: list(Path(path).iterdir()), [])
 | 
			
		||||
            if not _safe(exe.is_dir, True)
 | 
			
		||||
            and exe.name not in tf_entry_points]
 | 
			
		||||
@@ -190,6 +198,13 @@ class Cache(object):
 | 
			
		||||
        self._db = None
 | 
			
		||||
 | 
			
		||||
    def _init_db(self):
 | 
			
		||||
        try:
 | 
			
		||||
            self._setup_db()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            exception("Unable to init cache", sys.exc_info())
 | 
			
		||||
            self._db = {}
 | 
			
		||||
 | 
			
		||||
    def _setup_db(self):
 | 
			
		||||
        cache_dir = self._get_cache_dir()
 | 
			
		||||
        cache_path = Path(cache_dir).joinpath('thefuck').as_posix()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user