diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..c068257
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,10 @@
+# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/python-3/.devcontainer/base.Dockerfile
+
+# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
+ARG VARIANT="3"
+FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
+
+# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
+COPY requirements.txt /tmp/pip-tmp/
+RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
+   && rm -rf /tmp/pip-tmp
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..8bf7e46
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,39 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
+// https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/python-3
+{
+	"name": "Python 3",
+	"build": {
+		"dockerfile": "Dockerfile",
+		"context": ".."
+	},
+
+	// Set *default* container specific settings.json values on container create.
+	"settings": {
+		"terminal.integrated.profiles.linux": {
+			"bash (login)": {
+				"path": "bash",
+				"args": ["-l"]
+			}
+		},
+		"python.pythonPath": "/usr/local/bin/python",
+		"python.linting.enabled": true,
+		"python.linting.pylintEnabled": true,
+		"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
+		"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
+		"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
+		"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
+		"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
+		"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+		"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
+		"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
+		"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
+	},
+
+	// Add the IDs of extensions you want installed when the container is created.
+	"extensions": [
+		"ms-python.python"
+	],
+
+	// Use 'postCreateCommand' to run commands after the container is created.
+	"postCreateCommand": "pip3 install -r requirements.txt && python3 setup.py develop"
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f65e633
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+
+[*.py]
+max_line_length = 119
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..7bffe10
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,49 @@
+name: Tests
+
+on: [push, pull_request]
+
+env:
+  PYTHON_LATEST: 3.9
+
+jobs:
+  test:
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10-dev]
+      fail-fast: false
+    runs-on: ${{ matrix.os }}
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Cache dependencies
+        id: cache-deps
+        uses: actions/cache@v2
+        with:
+          path: |
+            ${{ env.pythonLocation }}/bin/*
+            ${{ env.pythonLocation }}/lib/*
+            ${{ env.pythonLocation }}/scripts/*
+          key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'requirements.txt') }}
+      - name: Install The Fuck with all dependencies
+        if: steps.cache-deps.outputs.cache-hit != 'true'
+        run: |
+          pip install -Ur requirements.txt coveralls
+          python setup.py develop
+      - name: Lint
+        if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
+        run: flake8
+      - name: Run tests
+        if: matrix.os != 'ubuntu-latest' || matrix.python-version != env.PYTHON_LATEST
+        run: coverage run --source=thefuck,tests -m pytest -v --capture=sys tests
+      - name: Run tests (including functional)
+        if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
+        run: coverage run --source=thefuck,tests -m pytest -v --capture=sys tests --enable-functional
+      - name: Post coverage results
+        if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: coveralls --service=github
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5adac37..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-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"
-    - os: linux
-      dist: trusty
-      python: "3.5"
-    - os: linux
-      dist: trusty
-      python: "3.4"
-    - os: linux
-      dist: trusty
-      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:
-  apt:
-    packages:
-      - python-commandnotfound
-      - python3-commandnotfound
-before_install:
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then rm -rf /usr/local/include/c++; fi
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew update; fi
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then brew upgrade python; fi
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then pip3 install virtualenv; fi
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then virtualenv venv -p python3; fi
-  - if [[ $TRAVIS_OS_NAME == "osx" ]]; then source venv/bin/activate; fi
-  - pip install -U pip
-  - pip install -U coveralls
-install:
-  - pip install -Ur requirements.txt
-  - python setup.py develop
-  - rm -rf build
-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.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.7 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 592a8ac..0e65253 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,6 +26,15 @@ fixes, etc.
 
 # Developing
 
+In order to develop locally, there are two options:
+
+- Develop using a local installation of Python 3 and setting up a virtual environment
+- Develop using an automated VSCode Dev Container.
+
+## Develop using local Python installation
+
+[Create and activate a Python 3 virtual environment.](https://docs.python.org/3/tutorial/venv.html)
+
 Install `The Fuck` for development:
 
 ```bash
@@ -57,3 +66,27 @@ For sending package to pypi:
 sudo apt-get install pandoc
 ./release.py
 ```
+
+## Develop using Dev Container
+
+To make local development easier a [VSCode Devcontainer](https://code.visualstudio.com/docs/remote/remote-overview) is included with this repository. This will allows you to spin up a Docker container with all the necessary prerequisites for this project pre-installed ready to go, no local Python install/setup required.
+
+### Prerequisites
+
+To use the container you require:
+- [Docker](https://www.docker.com/products/docker-desktop)
+- [VSCode](https://code.visualstudio.com/)
+- [VSCode Remote Development Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
+- [Windows Users Only]: [Installation of WSL2 and configuration of Docker to use it](https://docs.docker.com/docker-for-windows/wsl/)
+
+Full notes about [installation are here](https://code.visualstudio.com/docs/remote/containers#_installation)
+
+### Running the container
+
+Assuming you have the prerequisites:
+
+1. Open VSCode
+1. Open command palette (CMD+SHIFT+P (mac) or CTRL+SHIFT+P (windows))
+1. Select `Remote-Containers: Reopen in Container`.
+1. Container will be built, install all pip requirements and your VSCode will mount into it automagically.
+1. Your VSCode and container now essentially become a throw away environment.
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
index ec1d1e2..2d466f2 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,7 +1,7 @@
 The MIT License (MIT)
 =====================
 
-Copyright (c) 2015-2018 Vladimir Iakovlev
+Copyright (c) 2015-2021 Vladimir Iakovlev
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index a678856..182acce 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# The Fuck [![Version][version-badge]][version-link] [![Build Status][travis-badge]][travis-link] [![Windows Build Status][appveyor-badge]][appveyor-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
+# The Fuck [![Version][version-badge]][version-link] [![Build Status][workflow-badge]][workflow-link] [![Coverage][coverage-badge]][coverage-link] [![MIT License][license-badge]](LICENSE.md)
 
 *The Fuck* is a magnificent app, inspired by a [@liamosaur](https://twitter.com/liamosaur/)
 [tweet](https://twitter.com/liamosaur/status/506975850596536320),
@@ -91,15 +91,30 @@ Reading package lists... Done
 ...
 ```
 
+## Contents
+
+1. [Requirements](#requirements)
+2. [Installations](#installation)
+3. [Updating](#updating)
+4. [How it works](#how-it-works)
+5. [Creating your own rules](#creating-your-own-rules)
+6. [Settings](#settings)
+7. [Third party packages with rules](#third-party-packages-with-rules)
+8. [Experimental instant mode](#experimental-instant-mode)
+9. [Developing](#developing)
+10. [License](#license-mit)
+
 ## Requirements
 
 - python (3.4+)
 - pip
 - python-dev
 
+##### [Back to Contents](#contents)
+
 ## Installation
 
-On OS X, you can install *The Fuck* via [Homebrew][homebrew] (or via [Linuxbrew][linuxbrew] on Linux):
+On macOS or Linux, you can install *The Fuck* via [Homebrew][homebrew]:
 
 ```bash
 brew install thefuck
@@ -109,7 +124,7 @@ On Ubuntu / Mint, install *The Fuck* with the following commands:
 ```bash
 sudo apt update
 sudo apt install python3-dev python3-pip python3-setuptools
-sudo pip3 install thefuck
+pip3 install thefuck --user
 ```
 
 On FreeBSD, install *The Fuck* with the following commands:
@@ -145,7 +160,7 @@ 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 `--yeah` option (or just `-y` for short):
+To run fixed commands without confirmation, use the `--yeah` option (or just `-y` for short, or `--hard` if you're especially frustrated):
 
 ```bash
 fuck --yeah
@@ -157,6 +172,8 @@ To fix commands recursively until succeeding, use the `-r` option:
 fuck -r
 ```
 
+##### [Back to Contents](#contents)
+
 ## Updating
 
 ```bash
@@ -165,6 +182,12 @@ pip3 install thefuck --upgrade
 
 **Note: Alias functionality was changed in v1.34 of *The Fuck***
 
+## Uninstall
+
+To remove *The Fuck*, reverse the installation process:
+- erase or comment *thefuck* alias line from your Bash, Zsh, Fish, Powershell, tcsh, ... shell config
+- use your package manager (brew, pip3, pkg, crew, pip) to uninstall the binaries
+
 ## How it works
 
 *The Fuck* attempts to match the previous command with a rule. If a match is
@@ -179,10 +202,14 @@ following rules are enabled by default:
 * `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_cs` – changes `cs` to `cd`;
 * `cd_mkdir` – creates directories before cd'ing into them;
 * `cd_parent` – changes `cd..` to `cd ..`;
 * `chmod_x` – add execution bit;
+* `choco_install` – append common suffixes for chocolatey packages;
 * `composer_not_command` – fixes composer command name;
+* `conda_mistype` – fixes conda commands;
+* `cp_create_destination` – creates a new directory when you attempt to `cp` or `mv` to a non existent one
 * `cp_omitting_directory` – adds `-a` when you `cp` directory;
 * `cpp11` – adds missing `-std=c++11` to `g++` or `clang++`;
 * `dirty_untar` – fixes `tar x` command that untarred in the current directory;
@@ -191,6 +218,7 @@ following rules are enabled by default:
 * `django_south_merge` – adds `--merge` to inconsistent django south migration;
 * `docker_login` – executes a `docker login` and repeats the previous command;
 * `docker_not_command` – fixes wrong docker commands like `docker tags`;
+* `docker_image_being_used_by_container` ‐ removes the container that is using the image before removing the image;
 * `dry` – fixes repetitions like `git git push`;
 * `fab_command_not_found` – fix misspelled fabric commands;
 * `fix_alt_space` – replaces Alt+Space with Space character;
@@ -200,9 +228,13 @@ following rules are enabled by default:
 * `git_add_force` &ndash; adds `--force` to `git add <pathspec>...` when paths are .gitignore'd;
 * `git_bisect_usage` &ndash; fixes `git bisect strt`, `git bisect goood`, `git bisect rset`, etc. when bisecting;
 * `git_branch_delete` &ndash; changes `git branch -d` to `git branch -D`;
+* `git_branch_delete_checked_out` &ndash; changes `git branch -d` to `git checkout master && git branch -D` when trying to delete a checked out branch;
 * `git_branch_exists` &ndash; offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
 * `git_branch_list` &ndash; catches `git branch list` in place of `git branch` and removes created branch;
+* `git_branch_0flag` &ndash; fixes commands such as `git branch 0v` and `git branch 0r` removing the created branch;
 * `git_checkout` &ndash; fixes branch name or creates new branch;
+* `git_clone_git_clone` &ndash; replaces `git clone git clone ...` with `git clone ...`
+* `git_commit_add` &ndash; offers `git commit -a ...` or `git commit -p ...` after previous commit if it failed because nothing was staged;
 * `git_commit_amend` &ndash; offers `git commit --amend` after previous commit;
 * `git_commit_reset` &ndash; offers `git reset HEAD~` after previous commit;
 * `git_diff_no_index` &ndash; adds `--no-index` to previous `git diff` on untracked files;
@@ -210,6 +242,9 @@ following rules are enabled by default:
 * `git_fix_stash` &ndash; fixes `git stash` commands (misspelled subcommand and missing `save`);
 * `git_flag_after_filename` &ndash; fixes `fatal: bad flag '...' after filename`
 * `git_help_aliased` &ndash; fixes `git help <alias>` commands replacing <alias> with the aliased command;
+* `git_hook_bypass` &ndash; adds `--no-verify` flag previous to `git am`, `git commit`, or `git push` command;
+* `git_lfs_mistype` &ndash; fixes mistyped `git lfs <command>` commands;
+* `git_main_master` &ndash; fixes incorrect branch name between `main` and `master`
 * `git_merge` &ndash; adds remote to branch names;
 * `git_merge_unrelated` &ndash; adds `--allow-unrelated-histories` when required
 * `git_not_command` &ndash; fixes wrong git commands like `git brnch`;
@@ -217,7 +252,7 @@ following rules are enabled by default:
 * `git_pull_clone` &ndash; clones instead of pulling when the repo does not exist;
 * `git_pull_uncommitted_changes` &ndash; stashes changes before pulling and pops them afterwards;
 * `git_push` &ndash; adds `--set-upstream origin $branch` to previous failed `git push`;
-* `git_push_different_branch_names` &ndash; fixes pushes when local brach name does not match remote branch name;
+* `git_push_different_branch_names` &ndash; fixes pushes when local branch name does not match remote branch name;
 * `git_push_pull` &ndash; runs `git pull` when `push` was rejected;
 * `git_push_without_commits` &ndash; Creates an initial commit if you forget and only `git add .`, when setting up a new project;
 * `git_rebase_no_changes` &ndash; runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
@@ -226,12 +261,13 @@ following rules are enabled by default:
 * `git_rm_recursive` &ndash; adds `-r` when you try to `rm` a directory;
 * `git_rm_staged` &ndash;  adds `-f` or `--cached` when you try to `rm` a file with staged changes
 * `git_rebase_merge_dir` &ndash; offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
-* `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistant remote;
+* `git_remote_seturl_add` &ndash; runs `git remote add` when `git remote set_url` on nonexistent remote;
 * `git_stash` &ndash; stashes your local modifications before rebasing or switching branch;
 * `git_stash_pop` &ndash; adds your local modifications before popping stash, then resets;
 * `git_tag_force` &ndash; adds `--force` to `git tag <tagname>` when the tag already exists;
 * `git_two_dashes` &ndash; adds a missing dash to commands like `git commit -amend` or `git rebase -continue`;
 * `go_run` &ndash; appends `.go` extension when compiling/running Go programs;
+* `go_unknown_command` &ndash; fixes wrong `go` commands, for example `go bulid`;
 * `gradle_no_task` &ndash; fixes not found or ambiguous `gradle` task;
 * `gradle_wrapper` &ndash; replaces `gradle` with `./gradlew`;
 * `grep_arguments_order` &ndash; fixes `grep` arguments order for situations like `grep -lir . test`;
@@ -241,7 +277,7 @@ following rules are enabled by default:
 * `has_exists_script` &ndash; prepends `./` when script/binary exists;
 * `heroku_multiple_apps` &ndash; add `--app <app>` to `heroku` commands like `heroku pg`;
 * `heroku_not_command` &ndash; fixes wrong `heroku` commands like `heroku log`;
-* `history` &ndash; tries to replace command with most similar command from history;
+* `history` &ndash; tries to replace command with the most similar command from history;
 * `hostscli` &ndash; tries to fix `hostscli` usage;
 * `ifconfig_device_not_found` &ndash; fixes wrong device names like `wlan0` to `wlp2s0`;
 * `java` &ndash; removes `.java` extension when running Java programs;
@@ -256,51 +292,58 @@ following rules are enabled by default:
 * `man_no_space` &ndash; fixes man commands without spaces, for example `mandiff`;
 * `mercurial` &ndash; fixes wrong `hg` commands;
 * `missing_space_before_subcommand` &ndash; fixes command with missing space like `npminstall`;
-* `mkdir_p` &ndash; adds `-p` when you try to create a directory without parent;
+* `mkdir_p` &ndash; adds `-p` when you try to create a directory without a parent;
 * `mvn_no_command` &ndash; adds `clean package` to `mvn`;
-* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled lifecycle phases with `mvn`;
+* `mvn_unknown_lifecycle_phase` &ndash; fixes misspelled life cycle phases with `mvn`;
 * `npm_missing_script` &ndash; fixes `npm` custom script name in `npm run-script <script>`;
 * `npm_run_script` &ndash; adds missing `run-script` for custom `npm` scripts;
 * `npm_wrong_command` &ndash; fixes wrong npm commands like `npm urgrade`;
 * `no_command` &ndash; fixes wrong console commands, for example `vom/vim`;
 * `no_such_file` &ndash; creates missing directories with `mv` and `cp` commands;
+* `omnienv_no_such_command` &ndash; fixes wrong commands for `goenv`, `nodenv`, `pyenv` and `rbenv` (eg.: `pyenv isntall` or `goenv list`);
 * `open` &ndash; either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
 * `pip_install` &ndash; fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
 * `pip_unknown_command` &ndash; fixes wrong `pip` commands, for example `pip instatl/pip install`;
 * `php_s` &ndash; replaces `-s` by `-S` when trying to run a local php server;
 * `port_already_in_use` &ndash; kills process that bound port;
 * `prove_recursively` &ndash; adds `-r` when called with directory;
-* `pyenv_no_such_command` &ndash; fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`;
 * `python_command` &ndash; prepends `python` when you try to run non-executable/without `./` python script;
 * `python_execute` &ndash; appends missing `.py` when executing Python files;
+* `python_module_error` &ndash; fixes ModuleNotFoundError by trying to `pip install` that module;
 * `quotation_marks` &ndash; fixes uneven usage of `'` and `"` when containing args';
-* `path_from_history` &ndash; replaces not found path with similar absolute path from history;
+* `path_from_history` &ndash; replaces not found path with a similar absolute path from history;
+* `rails_migrations_pending` &ndash; runs pending migrations;
 * `react_native_command_unrecognized` &ndash; fixes unrecognized `react-native` commands;
-* `remove_trailing_cedilla` &ndash; remove trailling cedillas `ç`, a common typo for european keyboard layouts;
+* `remove_shell_prompt_literal` &ndash; remove leading shell prompt symbol `$`, common when copying commands from documentations;
+* `remove_trailing_cedilla` &ndash; remove trailing cedillas `ç`, a common typo for European keyboard layouts;
 * `rm_dir` &ndash; adds `-rf` when you try to remove a directory;
 * `scm_correction` &ndash; corrects wrong scm like `hg log` to `git log`;
 * `sed_unterminated_s` &ndash; adds missing '/' to `sed`'s `s` commands;
 * `sl_ls` &ndash; changes `sl` to `ls`;
 * `ssh_known_hosts` &ndash; removes host from `known_hosts` on warning;
-* `sudo` &ndash; prepends `sudo` to previous command if it failed because of permissions;
+* `sudo` &ndash; prepends `sudo` to the previous command if it failed because of permissions;
 * `sudo_command_from_user_path` &ndash; runs commands from users `$PATH` with `sudo`;
 * `switch_lang` &ndash; switches command from your local layout to en;
 * `systemctl` &ndash; correctly orders parameters of confusing `systemctl`;
+* `terraform_init.py` &ndash; run `terraform init` before plan or apply;
 * `test.py` &ndash; runs `py.test` instead of `test.py`;
 * `touch` &ndash; creates missing directories before "touching";
 * `tsuru_login` &ndash; runs `tsuru login` if not authenticated or session expired;
 * `tsuru_not_command` &ndash; fixes wrong `tsuru` commands like `tsuru shell`;
 * `tmux` &ndash; fixes `tmux` commands;
 * `unknown_command` &ndash; fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
-* `unsudo` &ndash; removes `sudo` from previous command if a process refuses to run on super user privilege.
+* `unsudo` &ndash; removes `sudo` from previous command if a process refuses to run on superuser privilege.
 * `vagrant_up` &ndash; starts up the vagrant instance;
 * `whois` &ndash; fixes `whois` command;
 * `workon_doesnt_exists` &ndash; fixes `virtualenvwrapper` env name os suggests to create new.
+* `wrong_hyphen_before_subcommand` &ndash; removes an improperly placed hyphen (`apt-install` -> `apt install`, `git-log` -> `git log`, etc.)
 * `yarn_alias` &ndash; fixes aliased `yarn` commands like `yarn ls`;
 * `yarn_command_not_found` &ndash; fixes misspelled `yarn` commands;
 * `yarn_command_replaced` &ndash; fixes replaced `yarn` commands;
 * `yarn_help` &ndash; makes it easier to open `yarn` documentation;
 
+##### [Back to Contents](#contents)
+
 The following rules are enabled by default on specific platforms only:
 
 * `apt_get` &ndash; installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
@@ -316,8 +359,11 @@ The following rules are enabled by default on specific platforms only:
 * `brew_unknown_command` &ndash; fixes wrong brew commands, for example `brew docto/brew doctor`;
 * `brew_update_formula` &ndash; turns `brew update <formula>` into `brew upgrade <formula>`;
 * `dnf_no_such_command` &ndash; fixes mistyped DNF commands;
+* `nixos_cmd_not_found` &ndash; installs apps on NixOS;
 * `pacman` &ndash; installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
+* `pacman_invalid_option` &ndash; replaces lowercase `pacman` options with uppercase.
 * `pacman_not_found` &ndash; fixes package name with `pacman`, `yay` or `yaourt`.
+* `yum_invalid_operation` &ndash; fixes invalid `yum` calls, like `yum isntall vim`;
 
 The following commands are bundled with *The Fuck*, but are not enabled by
 default:
@@ -325,6 +371,8 @@ default:
 * `git_push_force` &ndash; adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
 * `rm_root` &ndash; adds `--no-preserve-root` to `rm -rf /` command.
 
+##### [Back to Contents](#contents)
+
 ## Creating your own rules
 
 To add your own rule, create a file named `your-rule-name.py`
@@ -348,8 +396,8 @@ Your rule should not change `Command`.
 
 **Rules api changed in 3.0:** To access a rule's settings, import it with
  `from thefuck.conf import settings`
-  
-`settings` is a special object assembled from `~/.config/thefuck/settings.py`, 
+
+`settings` is a special object assembled from `~/.config/thefuck/settings.py`,
 and values from env ([see more below](#settings)).
 
 A simple example rule for running a script with `sudo`:
@@ -378,23 +426,26 @@ requires_output = True
 [utility functions for rules](https://github.com/nvbn/thefuck/tree/master/thefuck/utils.py),
 [app/os-specific helpers](https://github.com/nvbn/thefuck/tree/master/thefuck/specific/).
 
+##### [Back to Contents](#contents)
+
 ## Settings
 
 Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefuck/settings.py`
 (`$XDG_CONFIG_HOME` defaults to `~/.config`):
 
-* `rules` &ndash; list of enabled rules, by default `thefuck.conf.DEFAULT_RULES`;
+* `rules` &ndash; list of enabled rules, by default `thefuck.const.DEFAULT_RULES`;
 * `exclude_rules` &ndash; list of disabled rules, by default `[]`;
 * `require_confirmation` &ndash; requires confirmation before running new command, by default `True`;
-* `wait_command` &ndash; max amount of time in seconds for getting previous command output;
+* `wait_command` &ndash; the max amount of time in seconds for getting previous command output;
 * `no_colors` &ndash; disable colored output;
 * `priority` &ndash; dict with rules priorities, rule with lower `priority` will be matched first;
 * `debug` &ndash; enables debug output, by default `False`;
-* `history_limit` &ndash; numeric value of how many history commands will be scanned, like `2000`;
+* `history_limit` &ndash; the numeric value of how many history commands will be scanned, like `2000`;
 * `alter_history` &ndash; push fixed command to history, by default `True`;
 * `wait_slow_command` &ndash; max amount of time in seconds for getting previous command output if it in `slow_commands` list;
 * `slow_commands` &ndash; list of slow commands;
-* `num_close_matches` &ndash; maximum number of close matches to suggest, by default `3`.
+* `num_close_matches` &ndash; the maximum number of close matches to suggest, by default `3`.
+* `excluded_search_path_prefixes` &ndash; path prefixes to ignore when searching for commands, by default `[]`.
 
 An example of `settings.py`:
 
@@ -417,16 +468,17 @@ Or via environment variables:
 * `THEFUCK_RULES` &ndash; list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
 * `THEFUCK_EXCLUDE_RULES` &ndash; list of disabled rules, like `git_pull:git_push`;
 * `THEFUCK_REQUIRE_CONFIRMATION` &ndash; require confirmation before running new command, `true/false`;
-* `THEFUCK_WAIT_COMMAND` &ndash; max amount of time in seconds for getting previous command output;
+* `THEFUCK_WAIT_COMMAND` &ndash; the max amount of time in seconds for getting previous command output;
 * `THEFUCK_NO_COLORS` &ndash; disable colored output, `true/false`;
 * `THEFUCK_PRIORITY` &ndash; priority of the rules, like `no_command=9999:apt_get=100`,
 rule with lower `priority` will be matched first;
 * `THEFUCK_DEBUG` &ndash; enables debug output, `true/false`;
 * `THEFUCK_HISTORY_LIMIT` &ndash; how many history commands will be scanned, like `2000`;
 * `THEFUCK_ALTER_HISTORY` &ndash; push fixed command to history `true/false`;
-* `THEFUCK_WAIT_SLOW_COMMAND` &ndash; max amount of time in seconds for getting previous command output if it in `slow_commands` list;
+* `THEFUCK_WAIT_SLOW_COMMAND` &ndash; the max amount of time in seconds for getting previous command output if it in `slow_commands` list;
 * `THEFUCK_SLOW_COMMANDS` &ndash; list of slow commands, like `lein:gradle`;
-* `THEFUCK_NUM_CLOSE_MATCHES` &ndash; maximum number of close matches to suggest, like `5`.
+* `THEFUCK_NUM_CLOSE_MATCHES` &ndash; the maximum number of close matches to suggest, like `5`.
+* `THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES` &ndash; path prefixes to ignore when searching for commands, by default `[]`.
 
 For example:
 
@@ -441,6 +493,8 @@ export THEFUCK_HISTORY_LIMIT='2000'
 export THEFUCK_NUM_CLOSE_MATCHES='5'
 ```
 
+##### [Back to Contents](#contents)
+
 ## Third-party packages with rules
 
 If you'd like to make a specific set of non-public rules, but would still like
@@ -460,6 +514,8 @@ thefuck_contrib_foo
 
 *The Fuck* will find rules located in the `rules` module.
 
+##### [Back to Contents](#contents)
+
 ## Experimental instant mode
 
 The default behavior of *The Fuck* requires time to re-run previous commands.
@@ -479,6 +535,8 @@ For example:
 eval $(thefuck --alias --enable-experimental-instant-mode)
 ```
 
+##### [Back to Contents](#contents)
+
 ## Developing
 
 See [CONTRIBUTING.md](CONTRIBUTING.md)
@@ -489,14 +547,13 @@ Project License can be found [here](LICENSE.md).
 
 [version-badge]:   https://img.shields.io/pypi/v/thefuck.svg?label=version
 [version-link]:    https://pypi.python.org/pypi/thefuck/
-[travis-badge]:    https://travis-ci.org/nvbn/thefuck.svg?branch=master
-[travis-link]:     https://travis-ci.org/nvbn/thefuck
-[appveyor-badge]:  https://ci.appveyor.com/api/projects/status/1sskj4imj02um0gu/branch/master?svg=true
-[appveyor-link]:   https://ci.appveyor.com/project/nvbn/thefuck
+[workflow-badge]:  https://github.com/nvbn/thefuck/workflows/Tests/badge.svg
+[workflow-link]:   https://github.com/nvbn/thefuck/actions?query=workflow%3ATests
 [coverage-badge]:  https://img.shields.io/coveralls/nvbn/thefuck.svg
 [coverage-link]:   https://coveralls.io/github/nvbn/thefuck
 [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]:        https://brew.sh/
-[linuxbrew]:       https://linuxbrew.sh/
+
+##### [Back to Contents](#contents)
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index a688243..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-build: false
-
-environment:
-  matrix:
-    - PYTHON: "C:/Python27"
-    - PYTHON: "C:/Python34"
-    - PYTHON: "C:/Python35"
-    - PYTHON: "C:/Python36"
-    - PYTHON: "C:/Python37"
-
-init:
-  - "ECHO %PYTHON%"
-  - ps: "ls C:/Python*"
-
-install:
-  - "curl -fsS -o C:/get-pip.py https://bootstrap.pypa.io/get-pip.py"
-  - "%PYTHON%/python.exe C:/get-pip.py"
-  - "%PYTHON%/Scripts/pip.exe install -U setuptools"
-  - "%PYTHON%/python.exe setup.py develop"
-  - "%PYTHON%/Scripts/pip.exe install -U -r requirements.txt"
-
-test_script:
-  - "%PYTHON%/python.exe -m flake8"
-  - "%PYTHON%/Scripts/py.test.exe -sv"
diff --git a/debian/changelog b/debian/changelog
index 778308c..50fbb79 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+thefuck (3.32-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * Drop patch 0002-CVE-2021-34363.patch, present upstream.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 29 Apr 2022 19:18:02 -0000
+
 thefuck (3.29-0.3) unstable; urgency=medium
 
   * Non-maintainer upload.
diff --git a/debian/patches/0001-clean_scripts_install.patch b/debian/patches/0001-clean_scripts_install.patch
index c23051d..3f4c03c 100644
--- a/debian/patches/0001-clean_scripts_install.patch
+++ b/debian/patches/0001-clean_scripts_install.patch
@@ -2,9 +2,11 @@
  setup.py |    2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
 
---- a/setup.py
-+++ b/setup.py
-@@ -53,5 +53,5 @@ setup(name='thefuck',
+Index: thefuck/setup.py
+===================================================================
+--- thefuck.orig/setup.py
++++ thefuck/setup.py
+@@ -54,5 +54,5 @@ setup(name='thefuck',
        install_requires=install_requires,
        extras_require=extras_require,
        entry_points={'console_scripts': [
diff --git a/debian/patches/0002-CVE-2021-34363.patch b/debian/patches/0002-CVE-2021-34363.patch
deleted file mode 100644
index 61da0c5..0000000
--- a/debian/patches/0002-CVE-2021-34363.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-Description: Fix CVE-2021-34363.
- Fix possible changes in files outside of working directory.
-Author: Vladimir Iakovlev <nvbn.rm@gmail.com>
-Origin: upstream, https://github.com/nvbn/thefuck/commit/e343c577cd7da4d304b837d4a07ab4df1e023092.patch
-Bug: https://github.com/nvbn/thefuck/pull/1206
-Bug-Debian: https://bugs.debian.org/989989
-Forwarded: not-needed
-Reviewed-By: Francisco Vilmar Cardoso Ruviaro <francisco.ruviaro@riseup.net>
-Last-Update: 2021-06-18
-
---- a/thefuck/rules/dirty_untar.py
-+++ b/thefuck/rules/dirty_untar.py
-@@ -41,6 +41,10 @@ def get_new_command(command):
- def side_effect(old_cmd, command):
-     with tarfile.TarFile(_tar_file(old_cmd.script_parts)[0]) as archive:
-         for file in archive.getnames():
-+            if not os.path.abspath(file).startswith(os.getcwd()):
-+                # it's unsafe to overwrite files outside of the current directory
-+                continue
-+
-             try:
-                 os.remove(file)
-             except OSError:
---- a/thefuck/rules/dirty_unzip.py
-+++ b/thefuck/rules/dirty_unzip.py
-@@ -45,6 +45,10 @@ def get_new_command(command):
- def side_effect(old_cmd, command):
-     with zipfile.ZipFile(_zip_file(old_cmd), 'r') as archive:
-         for file in archive.namelist():
-+            if not os.path.abspath(file).startswith(os.getcwd()):
-+                # it's unsafe to overwrite files outside of the current directory
-+                continue
-+
-             try:
-                 os.remove(file)
-             except OSError:
diff --git a/debian/patches/series b/debian/patches/series
index 4ee432e..ac435fd 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,2 +1 @@
 0001-clean_scripts_install.patch
-0002-CVE-2021-34363.patch
diff --git a/example.gif b/example.gif
index e6663fc..5d9f19b 100644
Binary files a/example.gif and b/example.gif differ
diff --git a/example_instant_mode.gif b/example_instant_mode.gif
index 43bfcc3..b0fd62a 100644
Binary files a/example_instant_mode.gif and b/example_instant_mode.gif differ
diff --git a/release.py b/release.py
index da29cec..82c07f3 100755
--- a/release.py
+++ b/release.py
@@ -32,6 +32,6 @@ call('git push --tags', shell=True)
 
 env = os.environ
 env['CONVERT_README'] = 'true'
-call('rm -rf dist/*')
+call('rm -rf dist/*', shell=True, env=env)
 call('python setup.py sdist bdist_wheel', shell=True, env=env)
 call('twine upload dist/*', shell=True, env=env)
diff --git a/setup.py b/setup.py
index 7beb16f..36b13be 100755
--- a/setup.py
+++ b/setup.py
@@ -26,16 +26,17 @@ if version < (2, 7):
     print('thefuck requires Python version 2.7 or later' +
           ' ({}.{} detected).'.format(*version))
     sys.exit(-1)
-elif (3, 0) < version < (3, 4):
-    print('thefuck requires Python version 3.4 or later' +
+elif (3, 0) < version < (3, 5):
+    print('thefuck requires Python version 3.5 or later' +
           ' ({}.{} detected).'.format(*version))
     sys.exit(-1)
 
-VERSION = '3.29'
+VERSION = '3.32'
 
 install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
 extras_require = {':python_version<"3.4"': ['pathlib2'],
                   ':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
+                  ':python_version<="2.7"': ['decorator<5'],
                   ":sys_platform=='win32'": ['win_unicode_console']}
 
 setup(name='thefuck',
diff --git a/tests/conftest.py b/tests/conftest.py
index 3d09715..964458b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,6 +7,10 @@ from thefuck.system import Path
 shells.shell = shells.Generic()
 
 
+def pytest_configure(config):
+    config.addinivalue_line("markers", "functional: mark test as functional")
+
+
 def pytest_addoption(parser):
     """Adds `--enable-functional` argument."""
     group = parser.getgroup("thefuck")
diff --git a/tests/entrypoints/test_alias.py b/tests/entrypoints/test_alias.py
index 2260c75..ddb13e1 100644
--- a/tests/entrypoints/test_alias.py
+++ b/tests/entrypoints/test_alias.py
@@ -1,6 +1,6 @@
 from mock import Mock
 import pytest
-from thefuck.entrypoints.alias import _get_alias
+from thefuck.entrypoints.alias import _get_alias, print_alias
 
 
 @pytest.mark.parametrize(
@@ -28,3 +28,12 @@ def test_get_alias(monkeypatch, mocker, py2,
         assert alias == 'instant_mode_alias'
     else:
         assert alias == 'app_alias'
+
+
+def test_print_alias(mocker):
+    settings_mock = mocker.patch('thefuck.entrypoints.alias.settings')
+    _get_alias_mock = mocker.patch('thefuck.entrypoints.alias._get_alias')
+    known_args = Mock()
+    print_alias(known_args)
+    settings_mock.init.assert_called_once_with(known_args)
+    _get_alias_mock.assert_called_once_with(known_args)
diff --git a/tests/entrypoints/test_fix_command.py b/tests/entrypoints/test_fix_command.py
index 18431c4..3012bb4 100644
--- a/tests/entrypoints/test_fix_command.py
+++ b/tests/entrypoints/test_fix_command.py
@@ -5,8 +5,8 @@ from thefuck.entrypoints.fix_command import _get_raw_command
 
 class TestGetRawCommand(object):
     def test_from_force_command_argument(self):
-        known_args = Mock(force_command=['git', 'brunch'])
-        assert _get_raw_command(known_args) == ['git', 'brunch']
+        known_args = Mock(force_command='git brunch')
+        assert _get_raw_command(known_args) == ['git brunch']
 
     def test_from_command_argument(self, os_environ):
         os_environ['TF_HISTORY'] = None
diff --git a/tests/output_readers/test_rerun.py b/tests/output_readers/test_rerun.py
index 02dbd40..1d3a2c8 100644
--- a/tests/output_readers/test_rerun.py
+++ b/tests/output_readers/test_rerun.py
@@ -22,6 +22,19 @@ class TestRerun(object):
         assert rerun.get_output('', '') is None
         wait_output_mock.assert_called_once()
 
+    @patch('thefuck.output_readers.rerun.Popen')
+    def test_get_output_invalid_continuation_byte(self, popen_mock):
+        output = b'ls: illegal option -- \xc3\nusage: ls [-@ABC...] [file ...]\n'
+        expected = u'ls: illegal option -- \ufffd\nusage: ls [-@ABC...] [file ...]\n'
+        popen_mock.return_value.stdout.read.return_value = output
+        actual = rerun.get_output('', '')
+        assert actual == expected
+
+    @patch('thefuck.output_readers.rerun._wait_output')
+    def test_get_output_unicode_misspell(self, wait_output_mock):
+        rerun.get_output(u'pácman', u'pácman')
+        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)
diff --git a/tests/rules/test_apt_invalid_operation.py b/tests/rules/test_apt_invalid_operation.py
index 7b9fcde..e4ccc84 100644
--- a/tests/rules/test_apt_invalid_operation.py
+++ b/tests/rules/test_apt_invalid_operation.py
@@ -76,6 +76,45 @@ apt_get_operations = ['update', 'upgrade', 'install', 'remove', 'autoremove',
                       'dselect-upgrade', 'clean', 'autoclean', 'check',
                       'changelog', 'download']
 
+new_apt_get_help = b'''apt 1.6.12 (amd64)
+Usage: apt-get [options] command
+       apt-get [options] install|remove pkg1 [pkg2 ...]
+       apt-get [options] source pkg1 [pkg2 ...]
+
+apt-get is a command line interface for retrieval of packages
+and information about them from authenticated sources and
+for installation, upgrade and removal of packages together
+with their dependencies.
+
+Most used commands:
+  update - Retrieve new lists of packages
+  upgrade - Perform an upgrade
+  install - Install new packages (pkg is libc6 not libc6.deb)
+  remove - Remove packages
+  purge - Remove packages and config files
+  autoremove - Remove automatically all unused packages
+  dist-upgrade - Distribution upgrade, see apt-get(8)
+  dselect-upgrade - Follow dselect selections
+  build-dep - Configure build-dependencies for source packages
+  clean - Erase downloaded archive files
+  autoclean - Erase old downloaded archive files
+  check - Verify that there are no broken dependencies
+  source - Download source archives
+  download - Download the binary package into the current directory
+  changelog - Download and display the changelog for the given package
+
+See apt-get(8) for more information about the available commands.
+Configuration options and syntax is detailed in apt.conf(5).
+Information about how to configure sources can be found in sources.list(5).
+Package and version choices can be expressed via apt_preferences(5).
+Security details are available in apt-secure(8).
+                                        This APT has Super Cow Powers.
+'''
+new_apt_get_operations = ['update', 'upgrade', 'install', 'remove', 'purge',
+                          'autoremove', 'dist-upgrade', 'dselect-upgrade',
+                          'build-dep', 'clean', 'autoclean', 'check',
+                          'source', 'download', 'changelog']
+
 
 @pytest.mark.parametrize('script, output', [
     ('apt', invalid_operation('saerch')),
@@ -104,7 +143,8 @@ def set_help(mocker):
 
 @pytest.mark.parametrize('app, help_text, operations', [
     ('apt', apt_help, apt_operations),
-    ('apt-get', apt_get_help, apt_get_operations)
+    ('apt-get', apt_get_help, apt_get_operations),
+    ('apt-get', new_apt_get_help, new_apt_get_operations)
 ])
 def test_get_operations(set_help, app, help_text, operations):
     set_help(help_text)
@@ -116,6 +156,8 @@ def test_get_operations(set_help, app, help_text, operations):
      apt_get_help, 'apt-get install vim'),
     ('apt saerch vim', invalid_operation('saerch'),
      apt_help, 'apt search vim'),
+    ('apt uninstall vim', invalid_operation('uninstall'),
+     apt_help, 'apt remove vim'),
 ])
 def test_get_new_command(set_help, output, script, help_text, result):
     set_help(help_text)
diff --git a/tests/rules/test_apt_list_upgradable.py b/tests/rules/test_apt_list_upgradable.py
index 257a92a..fdb9168 100644
--- a/tests/rules/test_apt_list_upgradable.py
+++ b/tests/rules/test_apt_list_upgradable.py
@@ -1,8 +1,10 @@
+# -*- coding: utf-8 -*-
+
 import pytest
 from thefuck.rules.apt_list_upgradable import get_new_command, match
 from thefuck.types import Command
 
-match_output = '''
+full_english_output = '''
 Hit:1 http://us.archive.ubuntu.com/ubuntu zesty InRelease
 Hit:2 http://us.archive.ubuntu.com/ubuntu zesty-updates InRelease
 Get:3 http://us.archive.ubuntu.com/ubuntu zesty-backports InRelease [89.2 kB]
@@ -17,6 +19,11 @@ Reading state information... Done
 8 packages can be upgraded. Run 'apt list --upgradable' to see them.
 '''
 
+match_output = [
+    full_english_output,
+    'Führen Sie »apt list --upgradable« aus, um sie anzuzeigen.'  # German
+]
+
 no_match_output = '''
 Hit:1 http://us.archive.ubuntu.com/ubuntu zesty InRelease
 Get:2 http://us.archive.ubuntu.com/ubuntu zesty-updates InRelease [89.2 kB]
@@ -48,8 +55,9 @@ All packages are up to date.
 '''
 
 
-def test_match():
-    assert match(Command('sudo apt update', match_output))
+@pytest.mark.parametrize('output', match_output)
+def test_match(output):
+    assert match(Command('sudo apt update', output))
 
 
 @pytest.mark.parametrize('command', [
@@ -67,9 +75,10 @@ def test_not_match(command):
     assert not match(command)
 
 
-def test_get_new_command():
-    new_command = get_new_command(Command('sudo apt update', match_output))
+@pytest.mark.parametrize('output', match_output)
+def test_get_new_command(output):
+    new_command = get_new_command(Command('sudo apt update', output))
     assert new_command == 'sudo apt list --upgradable'
 
-    new_command = get_new_command(Command('apt update', match_output))
+    new_command = get_new_command(Command('apt update', output))
     assert new_command == 'apt list --upgradable'
diff --git a/tests/rules/test_cd_cs.py b/tests/rules/test_cd_cs.py
new file mode 100644
index 0000000..204c651
--- /dev/null
+++ b/tests/rules/test_cd_cs.py
@@ -0,0 +1,11 @@
+from thefuck.rules.cd_cs import match, get_new_command
+from thefuck.types import Command
+
+
+def test_match():
+    assert match(Command('cs', 'cs: command not found'))
+    assert match(Command('cs /etc/', 'cs: command not found'))
+
+
+def test_get_new_command():
+    assert get_new_command(Command('cs /etc/', 'cs: command not found')) == 'cd /etc/'
diff --git a/tests/rules/test_choco_install.py b/tests/rules/test_choco_install.py
new file mode 100644
index 0000000..0bfbb0d
--- /dev/null
+++ b/tests/rules/test_choco_install.py
@@ -0,0 +1,86 @@
+import pytest
+from thefuck.rules.choco_install import match, get_new_command
+from thefuck.types import Command
+
+
+package_not_found_error = (
+    'Chocolatey v0.10.15\n'
+    'Installing the following packages:\n'
+    'logstitcher\n'
+    'By installing you accept licenses for the packages.\n'
+    'logstitcher not installed. The package was not found with the source(s) listed.\n'
+    ' Source(s): \'https://chocolatey.org/api/v2/\'\n'
+    ' NOTE: When you specify explicit sources, it overrides default sources.\n'
+    'If the package version is a prerelease and you didn\'t specify `--pre`,\n'
+    ' the package may not be found.\n'
+    'Please see https://chocolatey.org/docs/troubleshooting for more\n'
+    ' assistance.\n'
+    '\n'
+    'Chocolatey installed 0/1 packages. 1 packages failed.\n'
+    ' See the log for details (C:\\ProgramData\\chocolatey\\logs\\chocolatey.log).\n'
+    '\n'
+    'Failures\n'
+    ' - logstitcher - logstitcher not installed. The package was not found with the source(s) listed.\n'
+    ' Source(s): \'https://chocolatey.org/api/v2/\'\n'
+    ' NOTE: When you specify explicit sources, it overrides default sources.\n'
+    'If the package version is a prerelease and you didn\'t specify `--pre`,\n'
+    ' the package may not be found.\n'
+    'Please see https://chocolatey.org/docs/troubleshooting for more\n'
+    ' assistance.\n'
+)
+
+
+@pytest.mark.parametrize('command', [
+    Command('choco install logstitcher', package_not_found_error),
+    Command('cinst logstitcher', package_not_found_error),
+    Command('choco install logstitcher -y', package_not_found_error),
+    Command('cinst logstitcher -y', package_not_found_error),
+    Command('choco install logstitcher -y -n=test', package_not_found_error),
+    Command('cinst logstitcher -y -n=test', package_not_found_error),
+    Command('choco install logstitcher -y -n=test /env', package_not_found_error),
+    Command('cinst logstitcher -y -n=test /env', package_not_found_error),
+    Command('choco install chocolatey -y', package_not_found_error),
+    Command('cinst chocolatey -y', package_not_found_error)])
+def test_match(command):
+    assert match(command)
+
+
+@pytest.mark.parametrize('command', [
+    Command('choco /?', ''),
+    Command('choco upgrade logstitcher', ''),
+    Command('cup logstitcher', ''),
+    Command('choco upgrade logstitcher -y', ''),
+    Command('cup logstitcher -y', ''),
+    Command('choco upgrade logstitcher -y -n=test', ''),
+    Command('cup logstitcher -y -n=test', ''),
+    Command('choco upgrade logstitcher -y -n=test /env', ''),
+    Command('cup logstitcher -y -n=test /env', ''),
+    Command('choco upgrade chocolatey -y', ''),
+    Command('cup chocolatey -y', ''),
+    Command('choco uninstall logstitcher', ''),
+    Command('cuninst logstitcher', ''),
+    Command('choco uninstall logstitcher -y', ''),
+    Command('cuninst logstitcher -y', ''),
+    Command('choco uninstall logstitcher -y -n=test', ''),
+    Command('cuninst logstitcher -y -n=test', ''),
+    Command('choco uninstall logstitcher -y -n=test /env', ''),
+    Command('cuninst logstitcher -y -n=test /env', ''),
+    Command('choco uninstall chocolatey -y', ''),
+    Command('cuninst chocolatey -y', '')])
+def not_test_match(command):
+    assert not match(command)
+
+
+@pytest.mark.parametrize('before, after', [
+    ('choco install logstitcher', 'choco install logstitcher.install'),
+    ('cinst logstitcher', 'cinst logstitcher.install'),
+    ('choco install logstitcher -y', 'choco install logstitcher.install -y'),
+    ('cinst logstitcher -y', 'cinst logstitcher.install -y'),
+    ('choco install logstitcher -y -n=test', 'choco install logstitcher.install -y -n=test'),
+    ('cinst logstitcher -y -n=test', 'cinst logstitcher.install -y -n=test'),
+    ('choco install logstitcher -y -n=test /env', 'choco install logstitcher.install -y -n=test /env'),
+    ('cinst logstitcher -y -n=test /env', 'cinst logstitcher.install -y -n=test /env'),
+    ('choco install chocolatey -y', 'choco install chocolatey.install -y'),
+    ('cinst chocolatey -y', 'cinst chocolatey.install -y'), ])
+def test_get_new_command(before, after):
+    assert (get_new_command(Command(before, '')) == after)
diff --git a/tests/rules/test_composer_not_command.py b/tests/rules/test_composer_not_command.py
index 33823cf..2bae920 100644
--- a/tests/rules/test_composer_not_command.py
+++ b/tests/rules/test_composer_not_command.py
@@ -39,18 +39,28 @@ def composer_not_command_one_of_this():
     )
 
 
-def test_match(composer_not_command, composer_not_command_one_of_this):
+@pytest.fixture
+def composer_require_instead_of_install():
+    return 'Invalid argument package. Use "composer require package" instead to add packages to your composer.json.'
+
+
+def test_match(composer_not_command, composer_not_command_one_of_this, composer_require_instead_of_install):
     assert match(Command('composer udpate',
                          composer_not_command))
     assert match(Command('composer pdate',
                          composer_not_command_one_of_this))
+    assert match(Command('composer install package',
+                         composer_require_instead_of_install))
     assert not match(Command('ls update', composer_not_command))
 
 
-def test_get_new_command(composer_not_command, composer_not_command_one_of_this):
+def test_get_new_command(composer_not_command, composer_not_command_one_of_this, composer_require_instead_of_install):
     assert (get_new_command(Command('composer udpate',
                                     composer_not_command))
             == 'composer update')
     assert (get_new_command(Command('composer pdate',
                                     composer_not_command_one_of_this))
             == 'composer selfupdate')
+    assert (get_new_command(Command('composer install package',
+                                    composer_require_instead_of_install))
+            == 'composer require package')
diff --git a/tests/rules/test_conda_mistype.py b/tests/rules/test_conda_mistype.py
new file mode 100644
index 0000000..0f05d82
--- /dev/null
+++ b/tests/rules/test_conda_mistype.py
@@ -0,0 +1,24 @@
+import pytest
+
+from thefuck.rules.conda_mistype import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def mistype_response():
+    return """
+
+CommandNotFoundError: No command 'conda lst'.
+Did you mean 'conda list'?
+
+    """
+
+
+def test_match(mistype_response):
+    assert match(Command('conda lst', mistype_response))
+    err_response = 'bash: codna: command not found'
+    assert not match(Command('codna list', err_response))
+
+
+def test_get_new_command(mistype_response):
+    assert (get_new_command(Command('conda lst', mistype_response)) == ['conda list'])
diff --git a/tests/rules/test_cp_create_destination.py b/tests/rules/test_cp_create_destination.py
new file mode 100644
index 0000000..1c40946
--- /dev/null
+++ b/tests/rules/test_cp_create_destination.py
@@ -0,0 +1,30 @@
+import pytest
+from thefuck.rules.cp_create_destination import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.mark.parametrize(
+    "script, output",
+    [("cp", "cp: directory foo does not exist\n"), ("mv", "No such file or directory")],
+)
+def test_match(script, output):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, output", [("cp", ""), ("mv", ""), ("ls", "No such file or directory")]
+)
+def test_not_match(script, output):
+    assert not match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, output, new_command",
+    [
+        ("cp foo bar/", "cp: directory foo does not exist\n", "mkdir -p bar/ && cp foo bar/"),
+        ("mv foo bar/", "No such file or directory", "mkdir -p bar/ && mv foo bar/"),
+        ("cp foo bar/baz/", "cp: directory foo does not exist\n", "mkdir -p bar/baz/ && cp foo bar/baz/"),
+    ],
+)
+def test_get_new_command(script, output, new_command):
+    assert get_new_command(Command(script, output)) == new_command
diff --git a/tests/rules/test_docker_image_being_used_by_container.py b/tests/rules/test_docker_image_being_used_by_container.py
new file mode 100644
index 0000000..00d9e67
--- /dev/null
+++ b/tests/rules/test_docker_image_being_used_by_container.py
@@ -0,0 +1,27 @@
+from thefuck.rules.docker_image_being_used_by_container import match, get_new_command
+from thefuck.types import Command
+
+
+def test_match():
+    err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
+    assert match(Command('docker image rm -f cd809b04b6ff', err_response))
+
+
+def test_not_match():
+    err_response = 'bash: docker: command not found'
+    assert not match(Command('docker image rm -f cd809b04b6ff', err_response))
+
+
+def test_not_docker_command():
+    err_response = """Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image is being used by running container e5e2591040d1"""
+    assert not match(Command('git image rm -f cd809b04b6ff', err_response))
+
+
+def test_get_new_command():
+    err_response = """
+        Error response from daemon: conflict: unable to delete cd809b04b6ff (cannot be forced) - image
+        is being used by running container e5e2591040d1
+        """
+    result = get_new_command(Command('docker image rm -f cd809b04b6ff', err_response))
+    expected = 'docker container rm -f e5e2591040d1 && docker image rm -f cd809b04b6ff'
+    assert result == expected
diff --git a/tests/rules/test_docker_not_command.py b/tests/rules/test_docker_not_command.py
index 697c0f3..d13ecb9 100644
--- a/tests/rules/test_docker_not_command.py
+++ b/tests/rules/test_docker_not_command.py
@@ -4,6 +4,46 @@ from thefuck.types import Command
 from thefuck.rules.docker_not_command import get_new_command, match
 
 
+_DOCKER_SWARM_OUTPUT = '''
+Usage:	docker swarm COMMAND
+
+Manage Swarm
+
+Commands:
+  ca          Display and rotate the root CA
+  init        Initialize a swarm
+  join        Join a swarm as a node and/or manager
+  join-token  Manage join tokens
+  leave       Leave the swarm
+  unlock      Unlock swarm
+  unlock-key  Manage the unlock key
+  update      Update the swarm
+
+Run 'docker swarm COMMAND --help' for more information on a command.
+'''
+_DOCKER_IMAGE_OUTPUT = '''
+Usage:	docker image COMMAND
+
+Manage images
+
+Commands:
+  build       Build an image from a Dockerfile
+  history     Show the history of an image
+  import      Import the contents from a tarball to create a filesystem image
+  inspect     Display detailed information on one or more images
+  load        Load an image from a tar archive or STDIN
+  ls          List images
+  prune       Remove unused images
+  pull        Pull an image or a repository from a registry
+  push        Push an image or a repository to a registry
+  rm          Remove one or more images
+  save        Save one or more images to a tar archive (streamed to STDOUT by default)
+  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
+
+Run 'docker image COMMAND --help' for more information on a command.
+'''
+
+
 @pytest.fixture
 def docker_help(mocker):
     help = b'''Usage: docker [OPTIONS] COMMAND [arg...]
@@ -104,6 +144,94 @@ Run 'docker COMMAND --help' for more information on a command.
     return mock
 
 
+@pytest.fixture
+def docker_help_new(mocker):
+    helptext_new = b'''
+Usage:	docker [OPTIONS] COMMAND
+
+A self-sufficient runtime for containers
+
+Options:
+      --config string      Location of client config files (default "/Users/ik1ne/.docker")
+  -c, --context string     Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var
+                           and default context set with "docker context use")
+  -D, --debug              Enable debug mode
+  -H, --host list          Daemon socket(s) to connect to
+  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
+      --tls                Use TLS; implied by --tlsverify
+      --tlscacert string   Trust certs signed only by this CA (default "/Users/ik1ne/.docker/ca.pem")
+      --tlscert string     Path to TLS certificate file (default "/Users/ik1ne/.docker/cert.pem")
+      --tlskey string      Path to TLS key file (default "/Users/ik1ne/.docker/key.pem")
+      --tlsverify          Use TLS and verify the remote
+  -v, --version            Print version information and quit
+
+Management Commands:
+  builder     Manage builds
+  config      Manage Docker configs
+  container   Manage containers
+  context     Manage contexts
+  image       Manage images
+  network     Manage networks
+  node        Manage Swarm nodes
+  plugin      Manage plugins
+  secret      Manage Docker secrets
+  service     Manage services
+  stack       Manage Docker stacks
+  swarm       Manage Swarm
+  system      Manage Docker
+  trust       Manage trust on Docker images
+  volume      Manage volumes
+
+Commands:
+  attach      Attach local standard input, output, and error streams to a running container
+  build       Build an image from a Dockerfile
+  commit      Create a new image from a container's changes
+  cp          Copy files/folders between a container and the local filesystem
+  create      Create a new container
+  diff        Inspect changes to files or directories on a container's filesystem
+  events      Get real time events from the server
+  exec        Run a command in a running container
+  export      Export a container's filesystem as a tar archive
+  history     Show the history of an image
+  images      List images
+  import      Import the contents from a tarball to create a filesystem image
+  info        Display system-wide information
+  inspect     Return low-level information on Docker objects
+  kill        Kill one or more running containers
+  load        Load an image from a tar archive or STDIN
+  login       Log in to a Docker registry
+  logout      Log out from a Docker registry
+  logs        Fetch the logs of a container
+  pause       Pause all processes within one or more containers
+  port        List port mappings or a specific mapping for the container
+  ps          List containers
+  pull        Pull an image or a repository from a registry
+  push        Push an image or a repository to a registry
+  rename      Rename a container
+  restart     Restart one or more containers
+  rm          Remove one or more containers
+  rmi         Remove one or more images
+  run         Run a command in a new container
+  save        Save one or more images to a tar archive (streamed to STDOUT by default)
+  search      Search the Docker Hub for images
+  start       Start one or more stopped containers
+  stats       Display a live stream of container(s) resource usage statistics
+  stop        Stop one or more running containers
+  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
+  top         Display the running processes of a container
+  unpause     Unpause all processes within one or more containers
+  update      Update configuration of one or more containers
+  version     Show the Docker version information
+  wait        Block until one or more containers stop, then print their exit codes
+
+Run 'docker COMMAND --help' for more information on a command.
+'''
+    mock = mocker.patch('subprocess.Popen')
+    mock.return_value.stdout = BytesIO(b'')
+    mock.return_value.stderr = BytesIO(helptext_new)
+    return mock
+
+
 def output(cmd):
     return "docker: '{}' is not a docker command.\n" \
            "See 'docker --help'.".format(cmd)
@@ -113,6 +241,24 @@ def test_match():
     assert match(Command('docker pes', output('pes')))
 
 
+# tests docker (management command)
+@pytest.mark.usefixtures('no_memoize')
+@pytest.mark.parametrize('script, output', [
+    ('docker swarn', output('swarn')),
+    ('docker imge', output('imge'))])
+def test_match_management_cmd(script, output):
+    assert match(Command(script, output))
+
+
+# tests docker (management cmd) (management subcmd)
+@pytest.mark.usefixtures('no_memoize')
+@pytest.mark.parametrize('script, output', [
+    ('docker swarm int', _DOCKER_SWARM_OUTPUT),
+    ('docker image la', _DOCKER_IMAGE_OUTPUT)])
+def test_match_management_subcmd(script, output):
+    assert match(Command(script, output))
+
+
 @pytest.mark.parametrize('script, output', [
     ('docker ps', ''),
     ('cat pes', output('pes'))])
@@ -120,10 +266,28 @@ def test_not_match(script, output):
     assert not match(Command(script, output))
 
 
-@pytest.mark.usefixtures('docker_help')
+@pytest.mark.usefixtures('no_memoize', 'docker_help')
 @pytest.mark.parametrize('wrong, fixed', [
     ('pes', ['ps', 'push', 'pause']),
     ('tags', ['tag', 'stats', 'images'])])
 def test_get_new_command(wrong, fixed):
     command = Command('docker {}'.format(wrong), output(wrong))
     assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
+
+
+@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
+@pytest.mark.parametrize('wrong, fixed', [
+    ('swarn', ['swarm', 'start', 'search']),
+    ('inage', ['image', 'images', 'rename'])])
+def test_get_new_management_command(wrong, fixed):
+    command = Command('docker {}'.format(wrong), output(wrong))
+    assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
+
+
+@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
+@pytest.mark.parametrize('wrong, fixed, output', [
+    ('swarm int', ['swarm init', 'swarm join', 'swarm join-token'], _DOCKER_SWARM_OUTPUT),
+    ('image la', ['image load', 'image ls', 'image tag'], _DOCKER_IMAGE_OUTPUT)])
+def test_get_new_management_command_subcommand(wrong, fixed, output):
+    command = Command('docker {}'.format(wrong), output)
+    assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
diff --git a/tests/rules/test_fix_file.py b/tests/rules/test_fix_file.py
index c4cec0f..caeb1c6 100644
--- a/tests/rules/test_fix_file.py
+++ b/tests/rules/test_fix_file.py
@@ -2,62 +2,54 @@
 
 import pytest
 import os
+from collections import namedtuple
 from thefuck.rules.fix_file import match, get_new_command
 from thefuck.types import Command
 
+FixFileTest = namedtuple('FixFileTest', ['script', 'file', 'line', 'col', 'output'])
 
-# (script, file, line, col (or None), output)
 tests = (
-('gcc a.c', 'a.c', 3, 1,
-"""
+    FixFileTest('gcc a.c', 'a.c', 3, 1, """
 a.c: In function 'main':
 a.c:3:1: error: expected expression before '}' token
  }
   ^
 """),
 
-('clang a.c', 'a.c', 3, 1,
-"""
+    FixFileTest('clang a.c', 'a.c', 3, 1, """
 a.c:3:1: error: expected expression
 }
 ^
 """),
 
-('perl a.pl', 'a.pl', 3, None,
-"""
+    FixFileTest('perl a.pl', 'a.pl', 3, None, """
 syntax error at a.pl line 3, at EOF
 Execution of a.pl aborted due to compilation errors.
 """),
 
-('perl a.pl', 'a.pl', 2, None,
-"""
+    FixFileTest('perl a.pl', 'a.pl', 2, None, """
 Search pattern not terminated at a.pl line 2.
 """),
 
-('sh a.sh', 'a.sh', 2, None,
-"""
+    FixFileTest('sh a.sh', 'a.sh', 2, None, """
 a.sh: line 2: foo: command not found
 """),
 
-('zsh a.sh', 'a.sh', 2, None,
-"""
+    FixFileTest('zsh a.sh', 'a.sh', 2, None, """
 a.sh:2: command not found: foo
 """),
 
-('bash a.sh', 'a.sh', 2, None,
-"""
+    FixFileTest('bash a.sh', 'a.sh', 2, None, """
 a.sh: line 2: foo: command not found
 """),
 
-('rustc a.rs', 'a.rs', 2, 5,
-"""
+    FixFileTest('rustc a.rs', 'a.rs', 2, 5, """
 a.rs:2:5: 2:6 error: unexpected token: `+`
 a.rs:2     +
            ^
 """),
 
-('cargo build', 'src/lib.rs', 3, 5,
-"""
+    FixFileTest('cargo build', 'src/lib.rs', 3, 5, """
    Compiling test v0.1.0 (file:///tmp/fix-error/test)
    src/lib.rs:3:5: 3:6 error: unexpected token: `+`
    src/lib.rs:3     +
@@ -67,16 +59,14 @@ Could not compile `test`.
 To learn more, run the command again with --verbose.
 """),
 
-('python a.py', 'a.py', 2, None,
-"""
+    FixFileTest('python a.py', 'a.py', 2, None, """
   File "a.py", line 2
       +
           ^
 SyntaxError: invalid syntax
 """),
 
-('python a.py', 'a.py', 8, None,
-"""
+    FixFileTest('python a.py', 'a.py', 8, None, """
 Traceback (most recent call last):
   File "a.py", line 8, in <module>
     match("foo")
@@ -89,8 +79,7 @@ Traceback (most recent call last):
 TypeError: first argument must be string or compiled pattern
 """),
 
-(u'python café.py', u'café.py', 8, None,
-u"""
+    FixFileTest(u'python café.py', u'café.py', 8, None, u"""
 Traceback (most recent call last):
   File "café.py", line 8, in <module>
     match("foo")
@@ -103,57 +92,48 @@ Traceback (most recent call last):
 TypeError: first argument must be string or compiled pattern
 """),
 
-('ruby a.rb', 'a.rb', 3, None,
-"""
+    FixFileTest('ruby a.rb', 'a.rb', 3, None, """
 a.rb:3: syntax error, unexpected keyword_end
 """),
 
-('lua a.lua', 'a.lua', 2, None,
-"""
+    FixFileTest('lua a.lua', 'a.lua', 2, None, """
 lua: a.lua:2: unexpected symbol near '+'
 """),
 
-('fish a.sh', '/tmp/fix-error/a.sh', 2, None,
-"""
+    FixFileTest('fish a.sh', '/tmp/fix-error/a.sh', 2, None, """
 fish: Unknown command 'foo'
 /tmp/fix-error/a.sh (line 2): foo
                               ^
 """),
 
-('./a', './a', 2, None,
-"""
+    FixFileTest('./a', './a', 2, None, """
 awk: ./a:2: BEGIN { print "Hello, world!" + }
 awk: ./a:2:                                 ^ syntax error
 """),
 
-('llc a.ll', 'a.ll', 1, 2,
-"""
+    FixFileTest('llc a.ll', 'a.ll', 1, 2, """
 llc: a.ll:1:2: error: expected top-level entity
 +
 ^
 """),
 
-('go build a.go', 'a.go', 1, 2,
-"""
+    FixFileTest('go build a.go', 'a.go', 1, 2, """
 can't load package:
 a.go:1:2: expected 'package', found '+'
 """),
 
-('make', 'Makefile', 2, None,
-"""
+    FixFileTest('make', 'Makefile', 2, None, """
 bidule
 make: bidule: Command not found
 Makefile:2: recipe for target 'target' failed
 make: *** [target] Error 127
 """),
 
-('git st', '/home/martin/.config/git/config', 1, None,
-"""
+    FixFileTest('git st', '/home/martin/.config/git/config', 1, None, """
 fatal: bad config file line 1 in /home/martin/.config/git/config
 """),
 
-('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5,
-"""
+    FixFileTest('node fuck.js asdf qwer', '/Users/pablo/Workspace/barebones/fuck.js', '2', 5, """
 /Users/pablo/Workspace/barebones/fuck.js:2
 conole.log(arg);  // this should read console.log(arg);
 ^
@@ -170,16 +150,14 @@ ReferenceError: conole is not defined
     at node.js:814:3
 """),
 
-('pep8', './tests/rules/test_systemctl.py', 17, 80,
-"""
+    FixFileTest('pep8', './tests/rules/test_systemctl.py', 17, 80, """
 ./tests/rules/test_systemctl.py:17:80: E501 line too long (93 > 79 characters)
 ./tests/rules/test_systemctl.py:18:80: E501 line too long (103 > 79 characters)
 ./tests/rules/test_whois.py:20:80: E501 line too long (89 > 79 characters)
 ./tests/rules/test_whois.py:22:80: E501 line too long (83 > 79 characters)
 """),
 
-('py.test', '/home/thefuck/tests/rules/test_fix_file.py', 218, None,
-"""
+    FixFileTest('py.test', '/home/thefuck/tests/rules/test_fix_file.py', 218, None, """
 monkeypatch = <_pytest.monkeypatch.monkeypatch object at 0x7fdb76a25b38>
 test = ('fish a.sh', '/tmp/fix-error/a.sh', 2, None, '', "\\nfish: Unknown command 'foo'\\n/tmp/fix-error/a.sh (line 2): foo\\n                              ^\\n")
 
@@ -191,7 +169,7 @@ E       NameError: name 'mocker' is not defined
 
 /home/thefuck/tests/rules/test_fix_file.py:218: NameError
 """),
-)  # noqa
+)
 
 
 @pytest.mark.parametrize('test', tests)
@@ -199,7 +177,7 @@ E       NameError: name 'mocker' is not defined
 def test_match(mocker, monkeypatch, test):
     mocker.patch('os.path.isfile', return_value=True)
     monkeypatch.setenv('EDITOR', 'dummy_editor')
-    assert match(Command('', test[4]))
+    assert match(Command('', test.output))
 
 
 @pytest.mark.parametrize('test', tests)
@@ -209,7 +187,7 @@ def test_no_editor(mocker, monkeypatch, test):
     if 'EDITOR' in os.environ:
         monkeypatch.delenv('EDITOR')
 
-    assert not match(Command('', test[4]))
+    assert not match(Command('', test.output))
 
 
 @pytest.mark.parametrize('test', tests)
@@ -218,7 +196,7 @@ def test_not_file(mocker, monkeypatch, test):
     mocker.patch('os.path.isfile', return_value=False)
     monkeypatch.setenv('EDITOR', 'dummy_editor')
 
-    assert not match(Command('', test[4]))
+    assert not match(Command('', test.output))
 
 
 @pytest.mark.parametrize('test', tests)
@@ -234,12 +212,12 @@ def test_get_new_command_with_settings(mocker, monkeypatch, test, settings):
     mocker.patch('os.path.isfile', return_value=True)
     monkeypatch.setenv('EDITOR', 'dummy_editor')
 
-    cmd = Command(test[0], test[4])
+    cmd = Command(test.script, test.output)
     settings.fixcolcmd = '{editor} {file} +{line}:{col}'
 
-    if test[3]:
+    if test.col:
         assert (get_new_command(cmd) ==
-                u'dummy_editor {} +{}:{} && {}'.format(test[1], test[2], test[3], test[0]))
+                u'dummy_editor {} +{}:{} && {}'.format(test.file, test.line, test.col, test.script))
     else:
         assert (get_new_command(cmd) ==
-                u'dummy_editor {} +{} && {}'.format(test[1], test[2], test[0]))
+                u'dummy_editor {} +{} && {}'.format(test.file, test.line, test.script))
diff --git a/tests/rules/test_git_branch_0flag.py b/tests/rules/test_git_branch_0flag.py
new file mode 100644
index 0000000..1c96e51
--- /dev/null
+++ b/tests/rules/test_git_branch_0flag.py
@@ -0,0 +1,70 @@
+import pytest
+
+from thefuck.rules.git_branch_0flag import get_new_command, match
+from thefuck.types import Command
+
+
+@pytest.fixture
+def output_branch_exists():
+    return "fatal: A branch named 'bar' already exists."
+
+
+@pytest.mark.parametrize(
+    "script",
+    [
+        "git branch 0a",
+        "git branch 0d",
+        "git branch 0f",
+        "git branch 0r",
+        "git branch 0v",
+        "git branch 0d foo",
+        "git branch 0D foo",
+    ],
+)
+def test_match(script, output_branch_exists):
+    assert match(Command(script, output_branch_exists))
+
+
+@pytest.mark.parametrize(
+    "script",
+    [
+        "git branch -a",
+        "git branch -r",
+        "git branch -v",
+        "git branch -d foo",
+        "git branch -D foo",
+    ],
+)
+def test_not_match(script, output_branch_exists):
+    assert not match(Command(script, ""))
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [
+        ("git branch 0a", "git branch -D 0a && git branch -a"),
+        ("git branch 0v", "git branch -D 0v && git branch -v"),
+        ("git branch 0d foo", "git branch -D 0d && git branch -d foo"),
+        ("git branch 0D foo", "git branch -D 0D && git branch -D foo"),
+        ("git branch 0l 'maint-*'", "git branch -D 0l && git branch -l 'maint-*'"),
+        ("git branch 0u upstream", "git branch -D 0u && git branch -u upstream"),
+    ],
+)
+def test_get_new_command_branch_exists(script, output_branch_exists, new_command):
+    assert get_new_command(Command(script, output_branch_exists)) == new_command
+
+
+@pytest.fixture
+def output_not_valid_object():
+    return "fatal: Not a valid object name: 'bar'."
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [
+        ("git branch 0l 'maint-*'", "git branch -l 'maint-*'"),
+        ("git branch 0u upstream", "git branch -u upstream"),
+    ],
+)
+def test_get_new_command_not_valid_object(script, output_not_valid_object, new_command):
+    assert get_new_command(Command(script, output_not_valid_object)) == new_command
diff --git a/tests/rules/test_git_branch_delete_checked_out.py b/tests/rules/test_git_branch_delete_checked_out.py
new file mode 100644
index 0000000..2bf551b
--- /dev/null
+++ b/tests/rules/test_git_branch_delete_checked_out.py
@@ -0,0 +1,29 @@
+import pytest
+from thefuck.rules.git_branch_delete_checked_out import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def output():
+    return "error: Cannot delete branch 'foo' checked out at '/bar/foo'"
+
+
+@pytest.mark.parametrize("script", ["git branch -d foo", "git branch -D foo"])
+def test_match(script, output):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize("script", ["git branch -d foo", "git branch -D foo"])
+def test_not_match(script):
+    assert not match(Command(script, "Deleted branch foo (was a1b2c3d)."))
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [
+        ("git branch -d foo", "git checkout master && git branch -D foo"),
+        ("git branch -D foo", "git checkout master && git branch -D foo"),
+    ],
+)
+def test_get_new_command(script, new_command, output):
+    assert get_new_command(Command(script, output)) == new_command
diff --git a/tests/rules/test_git_checkout.py b/tests/rules/test_git_checkout.py
index 20fff03..c72b1dc 100644
--- a/tests/rules/test_git_checkout.py
+++ b/tests/rules/test_git_checkout.py
@@ -39,6 +39,11 @@ def test_not_match(command):
     (b'', []),
     (b'* master', ['master']),
     (b'  remotes/origin/master', ['master']),
+    (b'  remotes/origin/test/1', ['test/1']),
+    (b'  remotes/origin/test/1/2/3', ['test/1/2/3']),
+    (b'  test/1', ['test/1']),
+    (b'  test/1/2/3', ['test/1/2/3']),
+    (b'  remotes/origin/HEAD -> origin/master', []),
     (b'  just-another-branch', ['just-another-branch']),
     (b'* master\n  just-another-branch', ['master', 'just-another-branch']),
     (b'* master\n  remotes/origin/master\n  just-another-branch',
@@ -51,18 +56,18 @@ def test_get_branches(branches, branch_list, git_branch):
 @pytest.mark.parametrize('branches, command, new_command', [
     (b'',
      Command('git checkout unknown', did_not_match('unknown')),
-     'git checkout -b unknown'),
+     ['git checkout -b unknown']),
     (b'',
      Command('git commit unknown', did_not_match('unknown')),
-     'git branch unknown && git commit unknown'),
+     ['git branch unknown && git commit unknown']),
     (b'  test-random-branch-123',
      Command('git checkout tst-rdm-brnch-123',
              did_not_match('tst-rdm-brnch-123')),
-     'git checkout test-random-branch-123'),
+     ['git checkout test-random-branch-123', 'git checkout -b tst-rdm-brnch-123']),
     (b'  test-random-branch-123',
      Command('git commit tst-rdm-brnch-123',
              did_not_match('tst-rdm-brnch-123')),
-     'git commit test-random-branch-123')])
+     ['git commit test-random-branch-123'])])
 def test_get_new_command(branches, command, new_command, git_branch):
     git_branch(branches)
     assert get_new_command(command) == new_command
diff --git a/tests/rules/test_git_clone_git_clone.py b/tests/rules/test_git_clone_git_clone.py
new file mode 100644
index 0000000..2efc689
--- /dev/null
+++ b/tests/rules/test_git_clone_git_clone.py
@@ -0,0 +1,24 @@
+from thefuck.rules.git_clone_git_clone import match, get_new_command
+from thefuck.types import Command
+
+
+output_clean = """
+fatal: Too many arguments.
+
+usage: git clone [<options>] [--] <repo> [<dir>]
+"""
+
+
+def test_match():
+    assert match(Command('git clone git clone foo', output_clean))
+
+
+def test_not_match():
+    assert not match(Command('', ''))
+    assert not match(Command('git branch', ''))
+    assert not match(Command('git clone foo', ''))
+    assert not match(Command('git clone foo bar baz', output_clean))
+
+
+def test_get_new_command():
+    assert get_new_command(Command('git clone git clone foo', output_clean)) == 'git clone foo'
diff --git a/tests/rules/test_git_commit_add.py b/tests/rules/test_git_commit_add.py
new file mode 100644
index 0000000..1a244f3
--- /dev/null
+++ b/tests/rules/test_git_commit_add.py
@@ -0,0 +1,38 @@
+import pytest
+from thefuck.rules.git_commit_add import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.mark.parametrize(
+    "script, output",
+    [
+        ('git commit -m "test"', "no changes added to commit"),
+        ("git commit", "no changes added to commit"),
+    ],
+)
+def test_match(output, script):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, output",
+    [
+        ('git commit -m "test"', " 1 file changed, 15 insertions(+), 14 deletions(-)"),
+        ("git branch foo", ""),
+        ("git checkout feature/test_commit", ""),
+        ("git push", ""),
+    ],
+)
+def test_not_match(output, script):
+    assert not match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [
+        ("git commit", ["git commit -a", "git commit -p"]),
+        ('git commit -m "foo"', ['git commit -a -m "foo"', 'git commit -p -m "foo"']),
+    ],
+)
+def test_get_new_command(script, new_command):
+    assert get_new_command(Command(script, "")) == new_command
diff --git a/tests/rules/test_git_hook_bypass.py b/tests/rules/test_git_hook_bypass.py
new file mode 100644
index 0000000..8cd8715
--- /dev/null
+++ b/tests/rules/test_git_hook_bypass.py
@@ -0,0 +1,43 @@
+import pytest
+from thefuck.rules.git_hook_bypass import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        Command("git am", ""),
+        Command("git commit", ""),
+        Command("git commit -m 'foo bar'", ""),
+        Command("git push", ""),
+        Command("git push -u foo bar", ""),
+    ],
+)
+def test_match(command):
+    assert match(command)
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        Command("git add foo", ""),
+        Command("git status", ""),
+        Command("git diff foo bar", ""),
+    ],
+)
+def test_not_match(command):
+    assert not match(command)
+
+
+@pytest.mark.parametrize(
+    "command, new_command",
+    [
+        (Command("git am", ""), "git am --no-verify"),
+        (Command("git commit", ""), "git commit --no-verify"),
+        (Command("git commit -m 'foo bar'", ""), "git commit --no-verify -m 'foo bar'"),
+        (Command("git push", ""), "git push --no-verify"),
+        (Command("git push -p", ""), "git push --no-verify -p"),
+    ],
+)
+def test_get_new_command(command, new_command):
+    assert get_new_command(command) == new_command
diff --git a/tests/rules/test_git_lfs_mistype.py b/tests/rules/test_git_lfs_mistype.py
new file mode 100644
index 0000000..1aae66f
--- /dev/null
+++ b/tests/rules/test_git_lfs_mistype.py
@@ -0,0 +1,29 @@
+import pytest
+
+from thefuck.rules.git_lfs_mistype import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def mistype_response():
+    return """
+Error: unknown command "evn" for "git-lfs"
+
+Did you mean this?
+        env
+        ext
+
+Run 'git-lfs --help' for usage.
+    """
+
+
+def test_match(mistype_response):
+    assert match(Command('git lfs evn', mistype_response))
+    err_response = 'bash: git: command not found'
+    assert not match(Command('git lfs env', err_response))
+    assert not match(Command('docker lfs env', mistype_response))
+
+
+def test_get_new_command(mistype_response):
+    assert (get_new_command(Command('git lfs evn', mistype_response))
+            == ['git lfs env', 'git lfs ext'])
diff --git a/tests/rules/test_git_main_master.py b/tests/rules/test_git_main_master.py
new file mode 100644
index 0000000..bf6f5b4
--- /dev/null
+++ b/tests/rules/test_git_main_master.py
@@ -0,0 +1,47 @@
+import pytest
+from thefuck.rules.git_main_master import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def output(branch_name):
+    if not branch_name:
+        return ""
+    output_str = u"error: pathspec '{}' did not match any file(s) known to git"
+    return output_str.format(branch_name)
+
+
+@pytest.mark.parametrize(
+    "script, branch_name",
+    [
+        ("git checkout main", "main"),
+        ("git checkout master", "master"),
+        ("git show main", "main"),
+    ],
+)
+def test_match(script, branch_name, output):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, branch_name",
+    [
+        ("git checkout master", ""),
+        ("git checkout main", ""),
+        ("git checkout wibble", "wibble"),
+    ],
+)
+def test_not_match(script, branch_name, output):
+    assert not match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "script, branch_name, new_command",
+    [
+        ("git checkout main", "main", "git checkout master"),
+        ("git checkout master", "master", "git checkout main"),
+        ("git checkout wibble", "wibble", "git checkout wibble"),
+    ],
+)
+def test_get_new_command(script, branch_name, new_command, output):
+    assert get_new_command(Command(script, output)) == new_command
diff --git a/tests/rules/test_git_push_without_commits.py b/tests/rules/test_git_push_without_commits.py
index 75ea973..7ea4823 100644
--- a/tests/rules/test_git_push_without_commits.py
+++ b/tests/rules/test_git_push_without_commits.py
@@ -1,27 +1,20 @@
-import pytest
-
 from thefuck.types import Command
-from thefuck.rules.git_push_without_commits import (
-    fix,
-    get_new_command,
-    match,
-)
+from thefuck.rules.git_push_without_commits import get_new_command, match
+
 
-command = 'git push -u origin master'
-expected_error = '''
-error: src refspec master does not match any.
-error: failed to push some refs to 'git@github.com:User/repo.git'
-'''
+def test_match():
+    script = "git push -u origin master"
+    output = "error: src refspec master does not match any\nerror: failed to..."
+    assert match(Command(script, output))
 
 
-@pytest.mark.parametrize('command', [Command(command, expected_error)])
-def test_match(command):
-    assert match(command)
+def test_not_match():
+    script = "git push -u origin master"
+    assert not match(Command(script, "Everything up-to-date"))
 
 
-@pytest.mark.parametrize('command, result', [(
-    Command(command, expected_error),
-    fix.format(command=command),
-)])
-def test_get_new_command(command, result):
-    assert get_new_command(command) == result
+def test_get_new_command():
+    script = "git push -u origin master"
+    output = "error: src refspec master does not match any\nerror: failed to..."
+    new_command = 'git commit -m "Initial commit" && git push -u origin master'
+    assert get_new_command(Command(script, output)) == new_command
diff --git a/tests/rules/test_go_unknown_command.py b/tests/rules/test_go_unknown_command.py
new file mode 100644
index 0000000..2118c3c
--- /dev/null
+++ b/tests/rules/test_go_unknown_command.py
@@ -0,0 +1,82 @@
+import pytest
+from io import BytesIO
+from thefuck.rules.go_unknown_command import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def build_misspelled_output():
+    return '''go bulid: unknown command
+Run 'go help' for usage.'''
+
+
+@pytest.fixture
+def go_stderr(mocker):
+    stderr = b'''Go is a tool for managing Go source code.
+
+Usage:
+
+\tgo <command> [arguments]
+
+The commands are:
+
+\tbug         start a bug report
+\tbuild       compile packages and dependencies
+\tclean       remove object files and cached files
+\tdoc         show documentation for package or symbol
+\tenv         print Go environment information
+\tfix         update packages to use new APIs
+\tfmt         gofmt (reformat) package sources
+\tgenerate    generate Go files by processing source
+\tget         add dependencies to current module and install them
+\tinstall     compile and install packages and dependencies
+\tlist        list packages or modules
+\tmod         module maintenance
+\trun         compile and run Go program
+\ttest        test packages
+\ttool        run specified go tool
+\tversion     print Go version
+\tvet         report likely mistakes in packages
+
+Use "go help <command>" for more information about a command.
+
+Additional help topics:
+
+\tbuildconstraint build constraints
+\tbuildmode       build modes
+\tc               calling between Go and C
+\tcache           build and test caching
+\tenvironment     environment variables
+\tfiletype        file types
+\tgo.mod          the go.mod file
+\tgopath          GOPATH environment variable
+\tgopath-get      legacy GOPATH go get
+\tgoproxy         module proxy protocol
+\timportpath      import path syntax
+\tmodules         modules, module versions, and more
+\tmodule-get      module-aware go get
+\tmodule-auth     module authentication using go.sum
+\tmodule-private  module configuration for non-public modules
+\tpackages        package lists and patterns
+\ttestflag        testing flags
+\ttestfunc        testing functions
+
+Use "go help <topic>" for more information about that topic.
+
+'''
+    mock = mocker.patch('subprocess.Popen')
+    mock.return_value.stderr = BytesIO(stderr)
+    return mock
+
+
+def test_match(build_misspelled_output):
+    assert match(Command('go bulid', build_misspelled_output))
+
+
+def test_not_match():
+    assert not match(Command('go run', 'go run: no go files listed'))
+
+
+@pytest.mark.usefixtures('no_memoize', 'go_stderr')
+def test_get_new_command(build_misspelled_output):
+    assert get_new_command(Command('go bulid', build_misspelled_output)) == 'go build'
diff --git a/tests/rules/test_missing_space_before_subcommand.py b/tests/rules/test_missing_space_before_subcommand.py
index 7455b9e..53b6009 100644
--- a/tests/rules/test_missing_space_before_subcommand.py
+++ b/tests/rules/test_missing_space_before_subcommand.py
@@ -8,11 +8,11 @@ from thefuck.types import Command
 def all_executables(mocker):
     return mocker.patch(
         'thefuck.rules.missing_space_before_subcommand.get_all_executables',
-        return_value=['git', 'ls', 'npm'])
+        return_value=['git', 'ls', 'npm', 'w', 'watch'])
 
 
 @pytest.mark.parametrize('script', [
-    'gitbranch', 'ls-la', 'npminstall'])
+    'gitbranch', 'ls-la', 'npminstall', 'watchls'])
 def test_match(script):
     assert match(Command(script, ''))
 
@@ -25,6 +25,7 @@ def test_not_match(script):
 @pytest.mark.parametrize('script, result', [
     ('gitbranch', 'git branch'),
     ('ls-la', 'ls -la'),
-    ('npminstall webpack', 'npm install webpack')])
+    ('npminstall webpack', 'npm install webpack'),
+    ('watchls', 'watch ls')])
 def test_get_new_command(script, result):
     assert get_new_command(Command(script, '')) == result
diff --git a/tests/rules/test_nixos_cmd_not_found.py b/tests/rules/test_nixos_cmd_not_found.py
new file mode 100644
index 0000000..4370334
--- /dev/null
+++ b/tests/rules/test_nixos_cmd_not_found.py
@@ -0,0 +1,25 @@
+import pytest
+from thefuck.rules.nixos_cmd_not_found import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.mark.parametrize('command', [
+    Command('vim', 'nix-env -iA nixos.vim')])
+def test_match(mocker, command):
+    mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
+    assert match(command)
+
+
+@pytest.mark.parametrize('command', [
+    Command('vim', ''),
+    Command('', '')])
+def test_not_match(mocker, command):
+    mocker.patch('thefuck.rules.nixos_cmd_not_found', return_value=None)
+    assert not match(command)
+
+
+@pytest.mark.parametrize('command, new_command', [
+    (Command('vim', 'nix-env -iA nixos.vim'), 'nix-env -iA nixos.vim && vim'),
+    (Command('pacman', 'nix-env -iA nixos.pacman'), 'nix-env -iA nixos.pacman && pacman')])
+def test_get_new_command(mocker, command, new_command):
+    assert get_new_command(command) == new_command
diff --git a/tests/rules/test_pyenv_no_such_command.py b/tests/rules/test_omnienv_no_such_command.py
similarity index 84%
rename from tests/rules/test_pyenv_no_such_command.py
rename to tests/rules/test_omnienv_no_such_command.py
index 298620f..45f84a6 100644
--- a/tests/rules/test_pyenv_no_such_command.py
+++ b/tests/rules/test_omnienv_no_such_command.py
@@ -1,6 +1,6 @@
 import pytest
 
-from thefuck.rules.pyenv_no_such_command import get_new_command, match
+from thefuck.rules.omnienv_no_such_command import get_new_command, match
 from thefuck.types import Command
 
 
@@ -11,7 +11,7 @@ def output(pyenv_cmd):
 
 @pytest.fixture(autouse=True)
 def Popen(mocker):
-    mock = mocker.patch('thefuck.rules.pyenv_no_such_command.Popen')
+    mock = mocker.patch('thefuck.rules.omnienv_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'
@@ -33,6 +33,11 @@ def test_match(script, pyenv_cmd, output):
     assert match(Command(script, output=output))
 
 
+def test_match_goenv_output_quote():
+    """test goenv's specific output with quotes (')"""
+    assert match(Command('goenv list', output="goenv: no such command 'list'"))
+
+
 @pytest.mark.parametrize('script, output', [
     ('pyenv global', 'system'),
     ('pyenv versions', '  3.7.0\n  3.7.1\n* 3.7.2\n'),
diff --git a/tests/rules/test_pacman_invalid_option.py b/tests/rules/test_pacman_invalid_option.py
new file mode 100644
index 0000000..0e04209
--- /dev/null
+++ b/tests/rules/test_pacman_invalid_option.py
@@ -0,0 +1,30 @@
+import pytest
+from thefuck.rules.pacman_invalid_option import get_new_command, match
+from thefuck.types import Command
+
+good_output = """community/shared_meataxe 1.0-3
+    A set of programs for working with matrix representations over finite fields
+"""
+
+bad_output = "error: invalid option '-"
+
+
+@pytest.mark.parametrize("option", "SURQFDVT")
+def test_not_match_good_output(option):
+    assert not match(Command("pacman -{}s meat".format(option), good_output))
+
+
+@pytest.mark.parametrize("option", "azxcbnm")
+def test_not_match_bad_output(option):
+    assert not match(Command("pacman -{}v meat".format(option), bad_output))
+
+
+@pytest.mark.parametrize("option", "surqfdvt")
+def test_match(option):
+    assert match(Command("pacman -{}v meat".format(option), bad_output))
+
+
+@pytest.mark.parametrize("option", "surqfdvt")
+def test_get_new_command(option):
+    new_command = get_new_command(Command("pacman -{}v meat".format(option), ""))
+    assert new_command == "pacman -{}v meat".format(option.upper())
diff --git a/tests/rules/test_pip_unknown_command.py b/tests/rules/test_pip_unknown_command.py
index 22abef2..36d23da 100644
--- a/tests/rules/test_pip_unknown_command.py
+++ b/tests/rules/test_pip_unknown_command.py
@@ -4,13 +4,23 @@ from thefuck.types import Command
 
 
 @pytest.fixture
-def pip_unknown_cmd():
-    return '''ERROR: unknown command "instatl" - maybe you meant "install"'''
+def pip_unknown_cmd_without_recommend():
+    return '''ERROR: unknown command "i"'''
 
 
 @pytest.fixture
-def pip_unknown_cmd_without_recommend():
-    return '''ERROR: unknown command "i"'''
+def broken():
+    return 'instatl'
+
+
+@pytest.fixture
+def suggested():
+    return 'install'
+
+
+@pytest.fixture
+def pip_unknown_cmd(broken, suggested):
+    return 'ERROR: unknown command "{}" - maybe you meant "{}"'.format(broken, suggested)
 
 
 def test_match(pip_unknown_cmd, pip_unknown_cmd_without_recommend):
@@ -19,6 +29,9 @@ def test_match(pip_unknown_cmd, pip_unknown_cmd_without_recommend):
                              pip_unknown_cmd_without_recommend))
 
 
-def test_get_new_command(pip_unknown_cmd):
-    assert get_new_command(Command('pip instatl',
-                                   pip_unknown_cmd)) == 'pip install'
+@pytest.mark.parametrize('script, broken, suggested, new_cmd', [
+    ('pip un+install thefuck', 'un+install', 'uninstall', 'pip uninstall thefuck'),
+    ('pip instatl', 'instatl', 'install', 'pip install')])
+def test_get_new_command(script, new_cmd, pip_unknown_cmd):
+    assert get_new_command(Command(script,
+                                   pip_unknown_cmd)) == new_cmd
diff --git a/tests/rules/test_python_module_error.py b/tests/rules/test_python_module_error.py
new file mode 100644
index 0000000..838f956
--- /dev/null
+++ b/tests/rules/test_python_module_error.py
@@ -0,0 +1,63 @@
+import pytest
+
+from thefuck.rules.python_module_error import get_new_command, match
+from thefuck.types import Command
+
+
+@pytest.fixture
+def module_error_output(filename, module_name):
+    return """Traceback (most recent call last):
+  File "{0}", line 1, in <module>
+    import {1}
+ModuleNotFoundError: No module named '{1}'""".format(
+        filename, module_name
+    )
+
+
+@pytest.mark.parametrize(
+    "test",
+    [
+        Command("python hello_world.py", "Hello World"),
+        Command(
+            "./hello_world.py",
+            """Traceback (most recent call last):
+  File "hello_world.py", line 1, in <module>
+    pritn("Hello World")
+NameError: name 'pritn' is not defined""",
+        ),
+    ],
+)
+def test_not_match(test):
+    assert not match(test)
+
+
+positive_tests = [
+    (
+        "python some_script.py",
+        "some_script.py",
+        "more_itertools",
+        "pip install more_itertools && python some_script.py",
+    ),
+    (
+        "./some_other_script.py",
+        "some_other_script.py",
+        "a_module",
+        "pip install a_module && ./some_other_script.py",
+    ),
+]
+
+
+@pytest.mark.parametrize(
+    "script, filename, module_name, corrected_script", positive_tests
+)
+def test_match(script, filename, module_name, corrected_script, module_error_output):
+    assert match(Command(script, module_error_output))
+
+
+@pytest.mark.parametrize(
+    "script, filename, module_name, corrected_script", positive_tests
+)
+def test_get_new_command(
+    script, filename, module_name, corrected_script, module_error_output
+):
+    assert get_new_command(Command(script, module_error_output)) == corrected_script
diff --git a/tests/rules/test_rails_migrations_pending.py b/tests/rules/test_rails_migrations_pending.py
new file mode 100644
index 0000000..0600234
--- /dev/null
+++ b/tests/rules/test_rails_migrations_pending.py
@@ -0,0 +1,46 @@
+import pytest
+from thefuck.rules.rails_migrations_pending import match, get_new_command
+from thefuck.types import Command
+
+output_env_development = '''
+Migrations are pending. To resolve this issue, run:
+
+        rails db:migrate RAILS_ENV=development
+'''
+output_env_test = '''
+Migrations are pending. To resolve this issue, run:
+
+        bin/rails db:migrate RAILS_ENV=test
+'''
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        Command("", output_env_development),
+        Command("", output_env_test),
+    ],
+)
+def test_match(command):
+    assert match(command)
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        Command("Environment data not found in the schema. To resolve this issue, run: \n\n", ""),
+    ],
+)
+def test_not_match(command):
+    assert not match(command)
+
+
+@pytest.mark.parametrize(
+    "command, new_command",
+    [
+        (Command("bin/rspec", output_env_development), "rails db:migrate RAILS_ENV=development && bin/rspec"),
+        (Command("bin/rspec", output_env_test), "bin/rails db:migrate RAILS_ENV=test && bin/rspec"),
+    ],
+)
+def test_get_new_command(command, new_command):
+    assert get_new_command(command) == new_command
diff --git a/tests/rules/test_remove_shell_prompt_literal.py b/tests/rules/test_remove_shell_prompt_literal.py
new file mode 100644
index 0000000..7eba66b
--- /dev/null
+++ b/tests/rules/test_remove_shell_prompt_literal.py
@@ -0,0 +1,48 @@
+import pytest
+from thefuck.rules.remove_shell_prompt_literal import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture
+def output():
+    return "$: command not found"
+
+
+@pytest.mark.parametrize(
+    "script",
+    [
+        "$ cd newdir",
+        " $ cd newdir",
+        "$ $ cd newdir"
+        " $ $ cd newdir",
+    ],
+)
+def test_match(script, output):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize(
+    "command",
+    [
+        Command("$", "$: command not found"),
+        Command(" $", "$: command not found"),
+        Command("$?", "127: command not found"),
+        Command(" $?", "127: command not found"),
+        Command("", ""),
+    ],
+)
+def test_not_match(command):
+    assert not match(command)
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [
+        ("$ cd newdir", "cd newdir"),
+        ("$ $ cd newdir", "cd newdir"),
+        ("$ python3 -m virtualenv env", "python3 -m virtualenv env"),
+        (" $ $ $ python3 -m virtualenv env", "python3 -m virtualenv env"),
+    ],
+)
+def test_get_new_command(script, new_command, output):
+    assert get_new_command(Command(script, output)) == new_command
diff --git a/tests/rules/test_switch_lang.py b/tests/rules/test_switch_lang.py
index e2da837..81634d1 100644
--- a/tests/rules/test_switch_lang.py
+++ b/tests/rules/test_switch_lang.py
@@ -1,6 +1,7 @@
 # -*- encoding: utf-8 -*-
 
 import pytest
+
 from thefuck.rules import switch_lang
 from thefuck.types import Command
 
@@ -9,7 +10,8 @@ from thefuck.types import Command
     Command(u'фзе-пуе', 'command not found: фзе-пуе'),
     Command(u'λσ', 'command not found: λσ'),
     Command(u'שפא-עקא', 'command not found: שפא-עקא'),
-    Command(u'ךד', 'command not found: ךד')])
+    Command(u'ךד', 'command not found: ךד'),
+    Command(u'녀애 ㅣㄴ', 'command not found: 녀애 ㅣㄴ')])
 def test_match(command):
     assert switch_lang.match(command)
 
@@ -19,7 +21,8 @@ def test_match(command):
     Command(u'ls', 'command not found: ls'),
     Command(u'агсл', 'command not found: агсл'),
     Command(u'фзе-пуе', 'some info'),
-    Command(u'שפא-עקא', 'some info')])
+    Command(u'שפא-עקא', 'some info'),
+    Command(u'녀애 ㅣㄴ', 'some info')])
 def test_not_match(command):
     assert not switch_lang.match(command)
 
@@ -28,6 +31,9 @@ def test_not_match(command):
     (Command(u'фзе-пуе штыефдд мшь', ''), 'apt-get install vim'),
     (Command(u'λσ -λα', ''), 'ls -la'),
     (Command(u'שפא-עקא ןמדאשךך הןצ', ''), 'apt-get install vim'),
-    (Command(u'ךד -ךש', ''), 'ls -la')])
+    (Command(u'ךד -ךש', ''), 'ls -la'),
+    (Command(u'멧-ㅎㄷㅅ ㅑㅜㄴㅅ미ㅣ 퍄ㅡ', ''), 'apt-get install vim'),
+    (Command(u'ㅣㄴ -ㅣㅁ', ''), 'ls -la'),
+    (Command(u'ㅔㅁㅅ촤', ''), 'patchk'), ])
 def test_get_new_command(command, new_command):
     assert switch_lang.get_new_command(command) == new_command
diff --git a/tests/rules/test_terraform_init.py b/tests/rules/test_terraform_init.py
new file mode 100644
index 0000000..1bd744c
--- /dev/null
+++ b/tests/rules/test_terraform_init.py
@@ -0,0 +1,33 @@
+import pytest
+from thefuck.rules.terraform_init import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.mark.parametrize('script, output', [
+    ('terraform plan', 'Error: Initialization required. '
+                       'Please see the error message above.'),
+    ('terraform plan', 'This module is not yet installed. Run "terraform init" '
+                       'to install all modules required by this configuration.'),
+    ('terraform apply', 'Error: Initialization required. '
+                        'Please see the error message above.'),
+    ('terraform apply', 'This module is not yet installed. Run "terraform init" '
+                        'to install all modules required by this configuration.')])
+def test_match(script, output):
+    assert match(Command(script, output))
+
+
+@pytest.mark.parametrize('script, output', [
+    ('terraform --version', 'Terraform v0.12.2'),
+    ('terraform plan', 'No changes. Infrastructure is up-to-date.'),
+    ('terraform apply', 'Apply complete! Resources: 0 added, 0 changed, 0 destroyed.'),
+])
+def test_not_match(script, output):
+    assert not match(Command(script, output=output))
+
+
+@pytest.mark.parametrize('command, new_command', [
+    (Command('terraform plan', ''), 'terraform init && terraform plan'),
+    (Command('terraform apply', ''), 'terraform init && terraform apply'),
+])
+def test_get_new_command(command, new_command):
+    assert get_new_command(command) == new_command
diff --git a/tests/rules/test_wrong_hyphen_before_subcommand.py b/tests/rules/test_wrong_hyphen_before_subcommand.py
new file mode 100644
index 0000000..30c91ab
--- /dev/null
+++ b/tests/rules/test_wrong_hyphen_before_subcommand.py
@@ -0,0 +1,30 @@
+import pytest
+
+from thefuck.rules.wrong_hyphen_before_subcommand import match, get_new_command
+from thefuck.types import Command
+
+
+@pytest.fixture(autouse=True)
+def get_all_executables(mocker):
+    mocker.patch(
+        "thefuck.rules.wrong_hyphen_before_subcommand.get_all_executables",
+        return_value=["git", "apt", "apt-get", "ls", "pwd"],
+    )
+
+
+@pytest.mark.parametrize("script", ["git-log", "apt-install python"])
+def test_match(script):
+    assert match(Command(script, ""))
+
+
+@pytest.mark.parametrize("script", ["ls -la", "git2-make", "apt-get install python"])
+def test_not_match(script):
+    assert not match(Command(script, ""))
+
+
+@pytest.mark.parametrize(
+    "script, new_command",
+    [("git-log", "git log"), ("apt-install python", "apt install python")],
+)
+def test_get_new_command(script, new_command):
+    assert get_new_command(Command(script, "")) == new_command
diff --git a/tests/rules/test_yum_invalid_operation.py b/tests/rules/test_yum_invalid_operation.py
new file mode 100644
index 0000000..dbbe44a
--- /dev/null
+++ b/tests/rules/test_yum_invalid_operation.py
@@ -0,0 +1,173 @@
+from io import BytesIO
+
+import pytest
+
+from thefuck.rules.yum_invalid_operation import match, get_new_command, _get_operations
+from thefuck.types import Command
+
+yum_help_text = '''Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
+Usage: yum [options] COMMAND
+
+List of Commands:
+
+check          Check for problems in the rpmdb
+check-update   Check for available package updates
+clean          Remove cached data
+deplist        List a package's dependencies
+distribution-synchronization Synchronize installed packages to the latest available versions
+downgrade      downgrade a package
+erase          Remove a package or packages from your system
+fs             Acts on the filesystem data of the host, mainly for removing docs/lanuages for minimal hosts.
+fssnapshot     Creates filesystem snapshots, or lists/deletes current snapshots.
+groups         Display, or use, the groups information
+help           Display a helpful usage message
+history        Display, or use, the transaction history
+info           Display details about a package or group of packages
+install        Install a package or packages on your system
+langavailable  Check available languages
+langinfo       List languages information
+langinstall    Install appropriate language packs for a language
+langlist       List installed languages
+langremove     Remove installed language packs for a language
+list           List a package or groups of packages
+load-transaction load a saved transaction from filename
+makecache      Generate the metadata cache
+provides       Find what package provides the given value
+reinstall      reinstall a package
+repo-pkgs      Treat a repo. as a group of packages, so we can install/remove all of them
+repolist       Display the configured software repositories
+search         Search package details for the given string
+shell          Run an interactive yum shell
+swap           Simple way to swap packages, instead of using shell
+update         Update a package or packages on your system
+update-minimal Works like upgrade, but goes to the 'newest' package match which fixes a problem that affects your system
+updateinfo     Acts on repository update information
+upgrade        Update packages taking obsoletes into account
+version        Display a version for the machine and/or available repos.
+
+
+Options:
+  -h, --help            show this help message and exit
+  -t, --tolerant        be tolerant of errors
+  -C, --cacheonly       run entirely from system cache, don't update cache
+  -c [config file], --config=[config file]
+                        config file location
+  -R [minutes], --randomwait=[minutes]
+                        maximum command wait time
+  -d [debug level], --debuglevel=[debug level]
+                        debugging output level
+  --showduplicates      show duplicates, in repos, in list/search commands
+  -e [error level], --errorlevel=[error level]
+                        error output level
+  --rpmverbosity=[debug level name]
+                        debugging output level for rpm
+  -q, --quiet           quiet operation
+  -v, --verbose         verbose operation
+  -y, --assumeyes       answer yes for all questions
+  --assumeno            answer no for all questions
+  --version             show Yum version and exit
+  --installroot=[path]  set install root
+  --enablerepo=[repo]   enable one or more repositories (wildcards allowed)
+  --disablerepo=[repo]  disable one or more repositories (wildcards allowed)
+  -x [package], --exclude=[package]
+                        exclude package(s) by name or glob
+  --disableexcludes=[repo]
+                        disable exclude from main, for a repo or for
+                        everything
+  --disableincludes=[repo]
+                        disable includepkgs for a repo or for everything
+  --obsoletes           enable obsoletes processing during updates
+  --noplugins           disable Yum plugins
+  --nogpgcheck          disable gpg signature checking
+  --disableplugin=[plugin]
+                        disable plugins by name
+  --enableplugin=[plugin]
+                        enable plugins by name
+  --skip-broken         skip packages with depsolving problems
+  --color=COLOR         control whether color is used
+  --releasever=RELEASEVER
+                        set value of $releasever in yum config and repo files
+  --downloadonly        don't update, just download
+  --downloaddir=DLDIR   specifies an alternate directory to store packages
+  --setopt=SETOPTS      set arbitrary config and repo options
+  --bugfix              Include bugfix relevant packages, in updates
+  --security            Include security relevant packages, in updates
+  --advisory=ADVS, --advisories=ADVS
+                        Include packages needed to fix the given advisory, in
+                        updates
+  --bzs=BZS             Include packages needed to fix the given BZ, in
+                        updates
+  --cves=CVES           Include packages needed to fix the given CVE, in
+                        updates
+  --sec-severity=SEVS, --secseverity=SEVS
+                        Include security relevant packages matching the
+                        severity, in updates
+
+  Plugin Options:
+    --samearch-priorities
+                        Priority-exclude packages based on name + arch
+'''
+yum_unsuccessful_search_text = '''Warning: No matches found for: {}
+No matches found
+'''
+yum_successful_vim_search_text = '''================================================== N/S matched: vim ===================================================
+protobuf-vim.x86_64 : Vim syntax highlighting for Google Protocol Buffers descriptions
+vim-X11.x86_64 : The VIM version of the vi editor for the X Window System - GVim
+vim-common.x86_64 : The common files needed by any version of the VIM editor
+vim-enhanced.x86_64 : A version of the VIM editor which includes recent enhancements
+vim-filesystem.x86_64 : VIM filesystem layout
+vim-filesystem.noarch : VIM filesystem layout
+vim-minimal.x86_64 : A minimal version of the VIM editor
+
+  Name and summary matches only, use "search all" for everything.
+'''
+
+yum_invalid_op_text = '''Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
+No such command: {}. Please use /usr/bin/yum --help
+'''
+
+yum_operations = [
+    'check', 'check-update', 'clean', 'deplist', 'distribution-synchronization', 'downgrade', 'erase', 'fs',
+    'fssnapshot', 'groups', 'help', 'history', 'info', 'install', 'langavailable', 'langinfo', 'langinstall',
+    'langlist', 'langremove', 'list', 'load-transaction', 'makecache', 'provides', 'reinstall', 'repo-pkgs', 'repolist',
+    'search', 'shell', 'swap', 'update', 'update-minimal', 'updateinfo', 'upgrade', 'version', ]
+
+
+@pytest.mark.parametrize('command', [
+    'saerch', 'uninstall',
+])
+def test_match(command):
+    assert match(Command('yum {}'.format(command), yum_invalid_op_text.format(command)))
+
+
+@pytest.mark.parametrize('command, output', [
+    ('vim', ''),
+    ('yum', yum_help_text,),
+    ('yum help', yum_help_text,),
+    ('yum search asdf', yum_unsuccessful_search_text.format('asdf'),),
+    ('yum search vim', yum_successful_vim_search_text)
+])
+def test_not_match(command, output):
+    assert not match(Command(command, output))
+
+
+@pytest.fixture
+def yum_help(mocker):
+    mock = mocker.patch('subprocess.Popen')
+    mock.return_value.stdout = BytesIO(bytes(yum_help_text.encode('utf-8')))
+    return mock
+
+
+@pytest.mark.usefixtures('no_memoize', 'yum_help')
+def test_get_operations():
+    assert _get_operations() == yum_operations
+
+
+@pytest.mark.usefixtures('no_memoize', 'yum_help')
+@pytest.mark.parametrize('script, output, result', [
+    ('yum uninstall', yum_invalid_op_text.format('uninstall'), 'yum remove',),
+    ('yum saerch asdf', yum_invalid_op_text.format('saerch'), 'yum search asdf',),
+    ('yum hlep', yum_invalid_op_text.format('hlep'), 'yum help',),
+])
+def test_get_new_command(script, output, result):
+    assert get_new_command(Command(script, output))[0] == result
diff --git a/tests/shells/test_fish.py b/tests/shells/test_fish.py
index ff627a4..2384dad 100644
--- a/tests/shells/test_fish.py
+++ b/tests/shells/test_fish.py
@@ -87,8 +87,11 @@ class TestFish(object):
 
     def test_app_alias_alter_history(self, settings, shell):
         settings.alter_history = True
-        assert 'builtin history delete' in shell.app_alias('FUCK')
-        assert 'builtin history merge' in shell.app_alias('FUCK')
+        assert (
+            'builtin history delete --exact --case-sensitive -- $fucked_up_command\n'
+            in shell.app_alias('FUCK')
+        )
+        assert 'builtin history merge\n' in shell.app_alias('FUCK')
         settings.alter_history = False
         assert 'builtin history delete' not in shell.app_alias('FUCK')
         assert 'builtin history merge' not in shell.app_alias('FUCK')
diff --git a/tests/specific/test_git.py b/tests/specific/test_git.py
index ab1b57b..10acb6c 100644
--- a/tests/specific/test_git.py
+++ b/tests/specific/test_git.py
@@ -6,7 +6,11 @@ from thefuck.types import Command
 @pytest.mark.parametrize('called, command, output', [
     ('git co', 'git checkout', "19:22:36.299340 git.c:282   trace: alias expansion: co => 'checkout'"),
     ('git com file', 'git commit --verbose file',
-     "19:23:25.470911 git.c:282   trace: alias expansion: com => 'commit' '--verbose'")])
+     "19:23:25.470911 git.c:282   trace: alias expansion: com => 'commit' '--verbose'"),
+    ('git com -m "Initial commit"', 'git commit -m "Initial commit"',
+     "19:22:36.299340 git.c:282   trace: alias expansion: com => 'commit'"),
+    ('git br -d some_branch', 'git branch -d some_branch',
+     "19:22:36.299340 git.c:282   trace: alias expansion: br => 'branch'")])
 def test_git_support(called, command, output):
     @git_support
     def fn(command):
@@ -23,9 +27,10 @@ def test_git_support(called, command, output):
     ('ls', False),
     ('cat git', False),
     ('cat hub', False)])
-def test_git_support_match(command, is_git):
+@pytest.mark.parametrize('output', ['', None])
+def test_git_support_match(command, is_git, output):
     @git_support
     def fn(command):
         return True
 
-    assert fn(Command(command, '')) == is_git
+    assert fn(Command(command, output)) == is_git
diff --git a/tests/test_conf.py b/tests/test_conf.py
index 7d0fe4b..e03473a 100644
--- a/tests/test_conf.py
+++ b/tests/test_conf.py
@@ -43,7 +43,7 @@ class TestSettingsFromFile(object):
         assert settings.rules == const.DEFAULT_RULES + ['test']
 
 
-@pytest.mark.usefixture('load_source')
+@pytest.mark.usefixtures('load_source')
 class TestSettingsFromEnv(object):
     def test_from_env(self, os_environ, settings):
         os_environ.update({'THEFUCK_RULES': 'bash:lisp',
@@ -54,7 +54,8 @@ class TestSettingsFromEnv(object):
                            'THEFUCK_PRIORITY': 'bash=10:lisp=wrong:vim=15',
                            'THEFUCK_WAIT_SLOW_COMMAND': '999',
                            'THEFUCK_SLOW_COMMANDS': 'lein:react-native:./gradlew',
-                           'THEFUCK_NUM_CLOSE_MATCHES': '359'})
+                           'THEFUCK_NUM_CLOSE_MATCHES': '359',
+                           'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': '/media/:/mnt/'})
         settings.init()
         assert settings.rules == ['bash', 'lisp']
         assert settings.exclude_rules == ['git', 'vim']
@@ -65,6 +66,7 @@ class TestSettingsFromEnv(object):
         assert settings.wait_slow_command == 999
         assert settings.slow_commands == ['lein', 'react-native', './gradlew']
         assert settings.num_close_matches == 359
+        assert settings.excluded_search_path_prefixes == ['/media/', '/mnt/']
 
     def test_from_env_with_DEFAULT(self, os_environ, settings):
         os_environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'})
diff --git a/tests/test_corrector.py b/tests/test_corrector.py
index fb8cb94..d28819d 100644
--- a/tests/test_corrector.py
+++ b/tests/test_corrector.py
@@ -8,14 +8,15 @@ from thefuck.types import Command
 from thefuck.corrector import get_corrected_commands, organize_commands
 
 
-class TestGetRules(object):
-    @pytest.fixture
-    def glob(self, mocker):
-        results = {}
-        mocker.patch('thefuck.system.Path.glob',
-                     new_callable=lambda: lambda *_: results.pop('value', []))
-        return lambda value: results.update({'value': value})
+@pytest.fixture
+def glob(mocker):
+    results = {}
+    mocker.patch('thefuck.system.Path.glob',
+                 new_callable=lambda: lambda *_: results.pop('value', []))
+    return lambda value: results.update({'value': value})
+
 
+class TestGetRules(object):
     @pytest.fixture(autouse=True)
     def load_source(self, monkeypatch):
         monkeypatch.setattr('thefuck.types.load_source',
@@ -39,6 +40,14 @@ class TestGetRules(object):
         self._compare_names(rules, loaded_rules)
 
 
+def test_get_rules_rule_exception(mocker, glob):
+    load_source = mocker.patch('thefuck.types.load_source',
+                               side_effect=ImportError("No module named foo..."))
+    glob([Path('git.py')])
+    assert not corrector.get_rules()
+    load_source.assert_called_once_with('git', 'git.py')
+
+
 def test_get_corrected_commands(mocker):
     command = Command('test', 'test')
     rules = [Rule(match=lambda _: False),
diff --git a/tests/test_types.py b/tests/test_types.py
index f946a8b..a322d21 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -41,10 +41,16 @@ class TestCorrectedCommand(object):
         settings.update(override_settings)
         CorrectedCommand(script, None, 1000).run(Command(script, ''))
         out, _ = capsys.readouterr()
-        assert out[:-1] == printed
+        assert out == printed
 
 
 class TestRule(object):
+    def test_from_path_rule_exception(self, mocker):
+        load_source = mocker.patch('thefuck.types.load_source',
+                                   side_effect=ImportError("No module named foo..."))
+        assert Rule.from_path(Path('git.py')) is None
+        load_source.assert_called_once_with('git', 'git.py')
+
     def test_from_path(self, mocker):
         match = object()
         get_new_command = object()
@@ -60,20 +66,22 @@ class TestRule(object):
                 == Rule('bash', match, get_new_command, priority=900))
         load_source.assert_called_once_with('bash', rule_path)
 
-    @pytest.mark.parametrize('rules, exclude_rules, rule, is_enabled', [
-        (const.DEFAULT_RULES, [], Rule('git', enabled_by_default=True), True),
-        (const.DEFAULT_RULES, [], Rule('git', enabled_by_default=False), False),
-        ([], [], Rule('git', enabled_by_default=False), False),
-        ([], [], Rule('git', enabled_by_default=True), False),
-        (const.DEFAULT_RULES + ['git'], [], Rule('git', enabled_by_default=False), True),
-        (['git'], [], Rule('git', enabled_by_default=False), True),
-        (const.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=True), False),
-        (const.DEFAULT_RULES, ['git'], Rule('git', enabled_by_default=False), False),
-        ([], ['git'], Rule('git', enabled_by_default=True), False),
-        ([], ['git'], Rule('git', enabled_by_default=False), False)])
-    def test_is_enabled(self, settings, rules, exclude_rules, rule, is_enabled):
-        settings.update(rules=rules,
-                        exclude_rules=exclude_rules)
+    def test_from_path_excluded_rule(self, mocker, settings):
+        load_source = mocker.patch('thefuck.types.load_source')
+        settings.update(exclude_rules=['git'])
+        rule_path = os.path.join(os.sep, 'rules', 'git.py')
+        assert Rule.from_path(Path(rule_path)) is None
+        assert not load_source.called
+
+    @pytest.mark.parametrize('rules, rule, is_enabled', [
+        (const.DEFAULT_RULES, Rule('git', enabled_by_default=True), True),
+        (const.DEFAULT_RULES, Rule('git', enabled_by_default=False), False),
+        ([], Rule('git', enabled_by_default=False), False),
+        ([], Rule('git', enabled_by_default=True), False),
+        (const.DEFAULT_RULES + ['git'], Rule('git', enabled_by_default=False), True),
+        (['git'], Rule('git', enabled_by_default=False), True)])
+    def test_is_enabled(self, settings, rules, rule, is_enabled):
+        settings.update(rules=rules)
         assert rule.is_enabled == is_enabled
 
     def test_isnt_match(self):
@@ -131,10 +139,13 @@ class TestCommand(object):
                                       env=os_environ)
 
     @pytest.mark.parametrize('script, result', [
+        ([], None),
         ([''], None),
         (['', ''], None),
         (['ls', '-la'], 'ls -la'),
-        (['ls'], 'ls')])
+        (['ls'], 'ls'),
+        (['echo \\ '], 'echo \\ '),
+        (['echo \\\n'], 'echo \\\n')])
     def test_from_script(self, script, result):
         if result:
             assert Command.from_raw_script(script).script == result
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3e73c3d..eae743f 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -94,6 +94,20 @@ def test_get_all_executables_pathsep(path, pathsep):
         Path_mock.assert_has_calls([call(p) for p in path.split(pathsep)], True)
 
 
+@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep')
+@pytest.mark.parametrize('path, pathsep, excluded', [
+    ('/foo:/bar:/baz:/foo/bar:/mnt/foo', ':', '/mnt/foo'),
+    (r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar;Z:\\foo', ';', r'Z:\\foo')])
+def test_get_all_executables_exclude_paths(path, pathsep, excluded, settings):
+    settings.init()
+    settings.excluded_search_path_prefixes = [excluded]
+    with patch('thefuck.utils.Path') as Path_mock:
+        get_all_executables()
+        path_list = path.split(pathsep)
+        assert call(path_list[-1]) not in Path_mock.mock_calls
+        assert all(call(p) in Path_mock.mock_calls for p in path_list[:-1])
+
+
 @pytest.mark.parametrize('args, result', [
     (('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'),
     (('git brnch', 'brnch', 'branch'), 'git branch')])
@@ -132,6 +146,8 @@ def test_get_all_matched_commands(stderr, result):
 
 @pytest.mark.usefixtures('no_memoize')
 @pytest.mark.parametrize('script, names, result', [
+    ('/usr/bin/git diff', ['git', 'hub'], True),
+    ('/bin/hdfs dfs -rm foo', ['hdfs'], True),
     ('git diff', ['git', 'hub'], True),
     ('hub diff', ['git', 'hub'], True),
     ('hg diff', ['git', 'hub'], False)])
@@ -141,6 +157,8 @@ def test_is_app(script, names, result):
 
 @pytest.mark.usefixtures('no_memoize')
 @pytest.mark.parametrize('script, names, result', [
+    ('/usr/bin/git diff', ['git', 'hub'], True),
+    ('/bin/hdfs dfs -rm foo', ['hdfs'], True),
     ('git diff', ['git', 'hub'], True),
     ('hub diff', ['git', 'hub'], True),
     ('hg diff', ['git', 'hub'], False)])
@@ -217,7 +235,7 @@ class TestCache(object):
 
 
 class TestGetValidHistoryWithoutCurrent(object):
-    @pytest.yield_fixture(autouse=True)
+    @pytest.fixture(autouse=True)
     def fail_on_warning(self):
         warnings.simplefilter('error')
         yield
@@ -225,9 +243,12 @@ class TestGetValidHistoryWithoutCurrent(object):
 
     @pytest.fixture(autouse=True)
     def history(self, mocker):
-        return mocker.patch('thefuck.shells.shell.get_history',
-                            return_value=['le cat', 'fuck', 'ls cat',
-                                          'diff x', 'nocommand x', u'café ô'])
+        mock = mocker.patch('thefuck.shells.shell.get_history')
+        #  Passing as an argument causes `UnicodeDecodeError`
+        #  with newer py.test and python 2.7
+        mock.return_value = ['le cat', 'fuck', 'ls cat',
+                             'diff x', 'nocommand x', u'café ô']
+        return mock
 
     @pytest.fixture(autouse=True)
     def alias(self, mocker):
diff --git a/thefuck/argument_parser.py b/thefuck/argument_parser.py
index b7b9783..69c247f 100644
--- a/thefuck/argument_parser.py
+++ b/thefuck/argument_parser.py
@@ -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', '--yeah',
+            '-y', '--yes', '--yeah', '--hard',
             action='store_true',
             help='execute fixed command without confirmation')
         group.add_argument(
diff --git a/thefuck/conf.py b/thefuck/conf.py
index b551963..27876ef 100644
--- a/thefuck/conf.py
+++ b/thefuck/conf.py
@@ -101,7 +101,7 @@ class Settings(dict):
         elif attr in ('require_confirmation', 'no_colors', 'debug',
                       'alter_history', 'instant_mode'):
             return val.lower() == 'true'
-        elif attr == 'slow_commands':
+        elif attr in ('slow_commands', 'excluded_search_path_prefixes'):
             return val.split(':')
         else:
             return val
diff --git a/thefuck/const.py b/thefuck/const.py
index d272f1b..8d33926 100644
--- a/thefuck/const.py
+++ b/thefuck/const.py
@@ -43,7 +43,8 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES,
                     'repeat': False,
                     'instant_mode': False,
                     'num_close_matches': 3,
-                    'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}}
+                    'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'},
+                    'excluded_search_path_prefixes': []}
 
 ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
                'THEFUCK_EXCLUDE_RULES': 'exclude_rules',
@@ -58,7 +59,8 @@ ENV_TO_ATTR = {'THEFUCK_RULES': 'rules',
                'THEFUCK_SLOW_COMMANDS': 'slow_commands',
                'THEFUCK_REPEAT': 'repeat',
                'THEFUCK_INSTANT_MODE': 'instant_mode',
-               'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches'}
+               'THEFUCK_NUM_CLOSE_MATCHES': 'num_close_matches',
+               'THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES': 'excluded_search_path_prefixes'}
 
 SETTINGS_HEADER = u"""# The Fuck settings file
 #
diff --git a/thefuck/corrector.py b/thefuck/corrector.py
index 222a135..fdd4698 100644
--- a/thefuck/corrector.py
+++ b/thefuck/corrector.py
@@ -15,7 +15,7 @@ def get_loaded_rules(rules_paths):
     for path in rules_paths:
         if path.name != '__init__.py':
             rule = Rule.from_path(path)
-            if rule.is_enabled:
+            if rule and rule.is_enabled:
                 yield rule
 
 
@@ -71,7 +71,7 @@ def organize_commands(corrected_commands):
         without_duplicates,
         key=lambda corrected_command: corrected_command.priority)
 
-    logs.debug('Corrected commands: '.format(
+    logs.debug(u'Corrected commands: {}'.format(
         ', '.join(u'{}'.format(cmd) for cmd in [first_command] + sorted_commands)))
 
     for command in sorted_commands:
diff --git a/thefuck/entrypoints/alias.py b/thefuck/entrypoints/alias.py
index 4bcda8b..c7094d4 100644
--- a/thefuck/entrypoints/alias.py
+++ b/thefuck/entrypoints/alias.py
@@ -1,4 +1,5 @@
 import six
+from ..conf import settings
 from ..logs import warn
 from ..shells import shell
 from ..utils import which
@@ -23,4 +24,5 @@ def _get_alias(known_args):
 
 
 def print_alias(known_args):
+    settings.init(known_args)
     print(_get_alias(known_args))
diff --git a/thefuck/entrypoints/fix_command.py b/thefuck/entrypoints/fix_command.py
index e3e3b01..018ba58 100644
--- a/thefuck/entrypoints/fix_command.py
+++ b/thefuck/entrypoints/fix_command.py
@@ -12,7 +12,7 @@ from ..utils import get_alias, get_all_executables
 
 def _get_raw_command(known_args):
     if known_args.force_command:
-        return known_args.force_command
+        return [known_args.force_command]
     elif not os.environ.get('TF_HISTORY'):
         return known_args.command
     else:
@@ -23,6 +23,7 @@ def _get_raw_command(known_args):
             diff = SequenceMatcher(a=alias, b=command).ratio()
             if diff < const.DIFF_WITH_ALIAS or command in executables:
                 return [command]
+    return []
 
 
 def fix_command(known_args):
diff --git a/thefuck/entrypoints/main.py b/thefuck/entrypoints/main.py
index 0134687..856a3d2 100644
--- a/thefuck/entrypoints/main.py
+++ b/thefuck/entrypoints/main.py
@@ -7,7 +7,7 @@ import os  # noqa: E402
 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 ..utils import get_installation_version  # noqa: E402
 from ..shells import shell  # noqa: E402
 from .alias import print_alias  # noqa: E402
 from .fix_command import fix_command  # noqa: E402
@@ -20,12 +20,15 @@ def main():
     if known_args.help:
         parser.print_help()
     elif known_args.version:
-        logs.version(get_installation_info().version,
+        logs.version(get_installation_version(),
                      sys.version.split()[0], shell.info())
-    elif known_args.command or 'TF_HISTORY' in os.environ:
-        fix_command(known_args)
+    # It's important to check if an alias is being requested before checking if
+    # `TF_HISTORY` is in `os.environ`, otherwise it might mess with subshells.
+    # Check https://github.com/nvbn/thefuck/issues/921 for reference
     elif known_args.alias:
         print_alias(known_args)
+    elif known_args.command or 'TF_HISTORY' in os.environ:
+        fix_command(known_args)
     elif known_args.shell_logger:
         try:
             from .shell_logger import shell_logger  # noqa: E402
diff --git a/thefuck/logs.py b/thefuck/logs.py
index 0c50765..e064de6 100644
--- a/thefuck/logs.py
+++ b/thefuck/logs.py
@@ -106,7 +106,7 @@ def how_to_configure_alias(configuration_details):
 
         if configuration_details.can_configure_automatically:
             print(
-                u"Or run {bold}fuck{reset} second time for configuring"
+                u"Or run {bold}fuck{reset} a second time to configure"
                 u" it automatically.".format(
                     bold=color(colorama.Style.BRIGHT),
                     reset=color(colorama.Style.RESET_ALL)))
diff --git a/thefuck/output_readers/read_log.py b/thefuck/output_readers/read_log.py
index 4da63e0..0224a0d 100644
--- a/thefuck/output_readers/read_log.py
+++ b/thefuck/output_readers/read_log.py
@@ -40,6 +40,9 @@ def _group_by_calls(log):
 
 
 def _get_script_group_lines(grouped, script):
+    if six.PY2:
+        script = script.encode('utf-8')
+
     parts = shlex.split(script)
 
     for script_line, lines in reversed(grouped):
diff --git a/thefuck/output_readers/rerun.py b/thefuck/output_readers/rerun.py
index 74cbfbf..b7ffe24 100644
--- a/thefuck/output_readers/rerun.py
+++ b/thefuck/output_readers/rerun.py
@@ -1,5 +1,6 @@
 import os
 import shlex
+import six
 from subprocess import Popen, PIPE, STDOUT
 from psutil import AccessDenied, Process, TimeoutExpired
 from .. import logs
@@ -53,13 +54,17 @@ def get_output(script, expanded):
     env = dict(os.environ)
     env.update(settings.env)
 
-    is_slow = shlex.split(expanded) in settings.slow_commands
-    with logs.debug_time(u'Call: {}; with env: {}; is slow: '.format(
+    if six.PY2:
+        expanded = expanded.encode('utf-8')
+
+    split_expand = shlex.split(expanded)
+    is_slow = split_expand[0] in settings.slow_commands if split_expand else False
+    with logs.debug_time(u'Call: {}; with env: {}; is slow: {}'.format(
             script, env, is_slow)):
         result = Popen(expanded, shell=True, stdin=PIPE,
                        stdout=PIPE, stderr=STDOUT, env=env)
         if _wait_output(result, is_slow):
-            output = result.stdout.read().decode('utf-8')
+            output = result.stdout.read().decode('utf-8', errors='replace')
             logs.debug(u'Received output: {}'.format(output))
             return output
         else:
diff --git a/thefuck/rules/apt_invalid_operation.py b/thefuck/rules/apt_invalid_operation.py
index 076109b..8641939 100644
--- a/thefuck/rules/apt_invalid_operation.py
+++ b/thefuck/rules/apt_invalid_operation.py
@@ -34,7 +34,8 @@ def _parse_apt_get_and_cache_operations(help_text_lines):
                 return
 
             yield line.split()[0]
-        elif line.startswith('Commands:'):
+        elif line.startswith('Commands:') \
+                or line.startswith('Most used commands:'):
             is_commands_list = True
 
 
@@ -53,5 +54,10 @@ def _get_operations(app):
 @sudo_support
 def get_new_command(command):
     invalid_operation = command.output.split()[-1]
-    operations = _get_operations(command.script_parts[0])
-    return replace_command(command, invalid_operation, operations)
+
+    if invalid_operation == 'uninstall':
+        return [command.script.replace('uninstall', 'remove')]
+
+    else:
+        operations = _get_operations(command.script_parts[0])
+        return replace_command(command, invalid_operation, operations)
diff --git a/thefuck/rules/apt_list_upgradable.py b/thefuck/rules/apt_list_upgradable.py
index 071a748..128c823 100644
--- a/thefuck/rules/apt_list_upgradable.py
+++ b/thefuck/rules/apt_list_upgradable.py
@@ -8,7 +8,7 @@ enabled_by_default = apt_available
 @sudo_support
 @for_app('apt')
 def match(command):
-    return "Run 'apt list --upgradable' to see them." in command.output
+    return 'apt list --upgradable' in command.output
 
 
 @sudo_support
diff --git a/thefuck/rules/brew_unknown_command.py b/thefuck/rules/brew_unknown_command.py
index d9cb58c..244f48a 100644
--- a/thefuck/rules/brew_unknown_command.py
+++ b/thefuck/rules/brew_unknown_command.py
@@ -3,8 +3,8 @@ import re
 from thefuck.utils import get_closest, replace_command
 from thefuck.specific.brew import get_brew_path_prefix, brew_available
 
-BREW_CMD_PATH = '/Library/Homebrew/cmd'
-TAP_PATH = '/Library/Taps'
+BREW_CMD_PATH = '/Homebrew/Library/Homebrew/cmd'
+TAP_PATH = '/Homebrew/Library/Taps'
 TAP_CMD_PATH = '/%s/%s/cmd'
 
 enabled_by_default = brew_available
@@ -62,7 +62,7 @@ def _brew_commands():
     # Failback commands for testing (Based on Homebrew 0.9.5)
     return ['info', 'home', 'options', 'install', 'uninstall',
             'search', 'list', 'update', 'upgrade', 'pin', 'unpin',
-            'doctor', 'create', 'edit']
+            'doctor', 'create', 'edit', 'cask']
 
 
 def match(command):
diff --git a/thefuck/rules/cd_cs.py b/thefuck/rules/cd_cs.py
new file mode 100644
index 0000000..f95415d
--- /dev/null
+++ b/thefuck/rules/cd_cs.py
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+
+# Redirects cs to cd when there is a typo
+# Due to the proximity of the keys - d and s - this seems like a common typo
+# ~ > cs /etc/
+# cs: command not found
+# ~ > fuck
+# cd /etc/ [enter/↑/↓/ctrl+c]
+# /etc >
+
+
+def match(command):
+    if command.script_parts[0] == 'cs':
+        return True
+
+
+def get_new_command(command):
+    return 'cd' + ''.join(command.script[2:])
+
+
+priority = 900
diff --git a/thefuck/rules/choco_install.py b/thefuck/rules/choco_install.py
new file mode 100644
index 0000000..8c07302
--- /dev/null
+++ b/thefuck/rules/choco_install.py
@@ -0,0 +1,25 @@
+from thefuck.utils import for_app, which
+
+
+@for_app("choco", "cinst")
+def match(command):
+    return ((command.script.startswith('choco install') or 'cinst' in command.script_parts)
+            and 'Installing the following packages' in command.output)
+
+
+def get_new_command(command):
+    # Find the argument that is the package name
+    for script_part in command.script_parts:
+        if (
+            script_part not in ["choco", "cinst", "install"]
+            # Need exact match (bc chocolatey is a package)
+            and not script_part.startswith('-')
+            # Leading hyphens are parameters; some packages contain them though
+            and '=' not in script_part and '/' not in script_part
+            # These are certainly parameters
+        ):
+            return command.script.replace(script_part, script_part + ".install")
+    return []
+
+
+enabled_by_default = bool(which("choco")) or bool(which("cinst"))
diff --git a/thefuck/rules/composer_not_command.py b/thefuck/rules/composer_not_command.py
index bfe4c5a..b678800 100644
--- a/thefuck/rules/composer_not_command.py
+++ b/thefuck/rules/composer_not_command.py
@@ -5,12 +5,18 @@ from thefuck.utils import replace_argument, for_app
 @for_app('composer')
 def match(command):
     return (('did you mean this?' in command.output.lower()
-             or 'did you mean one of these?' in command.output.lower()))
+             or 'did you mean one of these?' in command.output.lower())) or (
+        "install" in command.script_parts and "composer require" in command.output.lower()
+    )
 
 
 def get_new_command(command):
-    broken_cmd = re.findall(r"Command \"([^']*)\" is not defined", command.output)[0]
-    new_cmd = re.findall(r'Did you mean this\?[^\n]*\n\s*([^\n]*)', command.output)
-    if not new_cmd:
-        new_cmd = re.findall(r'Did you mean one of these\?[^\n]*\n\s*([^\n]*)', command.output)
-    return replace_argument(command.script, broken_cmd, new_cmd[0].strip())
+    if "install" in command.script_parts and "composer require" in command.output.lower():
+        broken_cmd, new_cmd = "install", "require"
+    else:
+        broken_cmd = re.findall(r"Command \"([^']*)\" is not defined", command.output)[0]
+        new_cmd = re.findall(r'Did you mean this\?[^\n]*\n\s*([^\n]*)', command.output)
+        if not new_cmd:
+            new_cmd = re.findall(r'Did you mean one of these\?[^\n]*\n\s*([^\n]*)', command.output)
+        new_cmd = new_cmd[0].strip()
+    return replace_argument(command.script, broken_cmd, new_cmd)
diff --git a/thefuck/rules/conda_mistype.py b/thefuck/rules/conda_mistype.py
new file mode 100644
index 0000000..fbba609
--- /dev/null
+++ b/thefuck/rules/conda_mistype.py
@@ -0,0 +1,17 @@
+import re
+from thefuck.utils import replace_command, for_app
+
+
+@for_app("conda")
+def match(command):
+    """
+    Match a mistyped command
+    """
+    return "Did you mean 'conda" in command.output
+
+
+def get_new_command(command):
+    match = re.findall(r"'conda ([^']*)'", command.output)
+    broken_cmd = match[0]
+    correct_cmd = match[1]
+    return replace_command(command, broken_cmd, [correct_cmd])
diff --git a/thefuck/rules/cp_create_destination.py b/thefuck/rules/cp_create_destination.py
new file mode 100644
index 0000000..6a1fbc5
--- /dev/null
+++ b/thefuck/rules/cp_create_destination.py
@@ -0,0 +1,15 @@
+from thefuck.shells import shell
+from thefuck.utils import for_app
+
+
+@for_app("cp", "mv")
+def match(command):
+    return (
+        "No such file or directory" in command.output
+        or command.output.startswith("cp: directory")
+        and command.output.rstrip().endswith("does not exist")
+    )
+
+
+def get_new_command(command):
+    return shell.and_(u"mkdir -p {}".format(command.script_parts[-1]), command.script)
diff --git a/thefuck/rules/dirty_untar.py b/thefuck/rules/dirty_untar.py
index d94958b..638aa87 100644
--- a/thefuck/rules/dirty_untar.py
+++ b/thefuck/rules/dirty_untar.py
@@ -41,6 +41,10 @@ def get_new_command(command):
 def side_effect(old_cmd, command):
     with tarfile.TarFile(_tar_file(old_cmd.script_parts)[0]) as archive:
         for file in archive.getnames():
+            if not os.path.abspath(file).startswith(os.getcwd()):
+                # it's unsafe to overwrite files outside of the current directory
+                continue
+
             try:
                 os.remove(file)
             except OSError:
diff --git a/thefuck/rules/dirty_unzip.py b/thefuck/rules/dirty_unzip.py
index 5369dea..6b50798 100644
--- a/thefuck/rules/dirty_unzip.py
+++ b/thefuck/rules/dirty_unzip.py
@@ -45,6 +45,10 @@ def get_new_command(command):
 def side_effect(old_cmd, command):
     with zipfile.ZipFile(_zip_file(old_cmd), 'r') as archive:
         for file in archive.namelist():
+            if not os.path.abspath(file).startswith(os.getcwd()):
+                # it's unsafe to overwrite files outside of the current directory
+                continue
+
             try:
                 os.remove(file)
             except OSError:
diff --git a/thefuck/rules/docker_image_being_used_by_container.py b/thefuck/rules/docker_image_being_used_by_container.py
new file mode 100644
index 0000000..d68f919
--- /dev/null
+++ b/thefuck/rules/docker_image_being_used_by_container.py
@@ -0,0 +1,20 @@
+from thefuck.utils import for_app
+from thefuck.shells import shell
+
+
+@for_app('docker')
+def match(command):
+    '''
+    Matches a command's output with docker's output
+    warning you that you need to remove a container before removing an image.
+    '''
+    return 'image is being used by running container' in command.output
+
+
+def get_new_command(command):
+    '''
+    Prepends docker container rm -f {container ID} to
+    the previous docker image rm {image ID} command
+    '''
+    container_id = command.output.strip().split(' ')
+    return shell.and_('docker container rm -f {}', '{}').format(container_id[-1], command.script)
diff --git a/thefuck/rules/docker_login.py b/thefuck/rules/docker_login.py
index 5a96b6c..81acb45 100644
--- a/thefuck/rules/docker_login.py
+++ b/thefuck/rules/docker_login.py
@@ -1,4 +1,5 @@
 from thefuck.utils import for_app
+from thefuck.shells import shell
 
 
 @for_app('docker')
@@ -9,4 +10,4 @@ def match(command):
 
 
 def get_new_command(command):
-    return 'docker login && {}'.format(command.script)
+    return shell.and_('docker login', command.script)
diff --git a/thefuck/rules/docker_not_command.py b/thefuck/rules/docker_not_command.py
index 9aa6a22..d9eb1c2 100644
--- a/thefuck/rules/docker_not_command.py
+++ b/thefuck/rules/docker_not_command.py
@@ -8,24 +8,42 @@ from thefuck.specific.sudo import sudo_support
 @sudo_support
 @for_app('docker')
 def match(command):
-    return 'is not a docker command' in command.output
+    return 'is not a docker command' in command.output or 'Usage:	docker' in command.output
 
 
-def get_docker_commands():
-    proc = subprocess.Popen('docker', stdout=subprocess.PIPE)
-    lines = [line.decode('utf-8') for line in proc.stdout.readlines()]
-    lines = dropwhile(lambda line: not line.startswith('Commands:'), lines)
+def _parse_commands(lines, starts_with):
+    lines = dropwhile(lambda line: not line.startswith(starts_with), lines)
     lines = islice(lines, 1, None)
-    lines = list(takewhile(lambda line: line != '\n', lines))
+    lines = list(takewhile(lambda line: line.strip(), lines))
     return [line.strip().split(' ')[0] for line in lines]
 
 
+def get_docker_commands():
+    proc = subprocess.Popen('docker', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+    # Old version docker returns its output to stdout, while newer version returns to stderr.
+    lines = proc.stdout.readlines() or proc.stderr.readlines()
+    lines = [line.decode('utf-8') for line in lines]
+
+    # Only newer versions of docker have management commands in the help text.
+    if 'Management Commands:\n' in lines:
+        management_commands = _parse_commands(lines, 'Management Commands:')
+    else:
+        management_commands = []
+    regular_commands = _parse_commands(lines, 'Commands:')
+    return management_commands + regular_commands
+
+
 if which('docker'):
     get_docker_commands = cache(which('docker'))(get_docker_commands)
 
 
 @sudo_support
 def get_new_command(command):
+    if 'Usage:' in command.output and len(command.script_parts) > 1:
+        management_subcommands = _parse_commands(command.output.split('\n'), 'Commands:')
+        return replace_command(command, command.script_parts[2], management_subcommands)
+
     wrong_command = re.findall(
         r"docker: '(\w+)' is not a docker command.", command.output)[0]
     return replace_command(command, wrong_command, get_docker_commands())
diff --git a/thefuck/rules/git_branch_0flag.py b/thefuck/rules/git_branch_0flag.py
new file mode 100644
index 0000000..90dbdcb
--- /dev/null
+++ b/thefuck/rules/git_branch_0flag.py
@@ -0,0 +1,24 @@
+from thefuck.shells import shell
+from thefuck.specific.git import git_support
+from thefuck.utils import memoize
+
+
+@memoize
+def first_0flag(script_parts):
+    return next((p for p in script_parts if len(p) == 2 and p.startswith("0")), None)
+
+
+@git_support
+def match(command):
+    return command.script_parts[1] == "branch" and first_0flag(command.script_parts)
+
+
+@git_support
+def get_new_command(command):
+    branch_name = first_0flag(command.script_parts)
+    fixed_flag = branch_name.replace("0", "-")
+    fixed_script = command.script.replace(branch_name, fixed_flag)
+    if "A branch named '" in command.output and "' already exists." in command.output:
+        delete_branch = u"git branch -D {}".format(branch_name)
+        return shell.and_(delete_branch, fixed_script)
+    return fixed_script
diff --git a/thefuck/rules/git_branch_delete_checked_out.py b/thefuck/rules/git_branch_delete_checked_out.py
new file mode 100644
index 0000000..eadc2d4
--- /dev/null
+++ b/thefuck/rules/git_branch_delete_checked_out.py
@@ -0,0 +1,19 @@
+from thefuck.shells import shell
+from thefuck.specific.git import git_support
+from thefuck.utils import replace_argument
+
+
+@git_support
+def match(command):
+    return (
+        ("branch -d" in command.script or "branch -D" in command.script)
+        and "error: Cannot delete branch '" in command.output
+        and "' checked out at '" in command.output
+    )
+
+
+@git_support
+def get_new_command(command):
+    return shell.and_("git checkout master", "{}").format(
+        replace_argument(command.script, "-d", "-D")
+    )
diff --git a/thefuck/rules/git_checkout.py b/thefuck/rules/git_checkout.py
index 65705f4..e487973 100644
--- a/thefuck/rules/git_checkout.py
+++ b/thefuck/rules/git_checkout.py
@@ -8,7 +8,7 @@ from thefuck.shells import shell
 
 @git_support
 def match(command):
-    return ('did not match any file(s) known to git.' in command.output
+    return ('did not match any file(s) known to git' in command.output
             and "Did you forget to 'git add'?" not in command.output)
 
 
@@ -18,10 +18,12 @@ def get_branches():
         stdout=subprocess.PIPE)
     for line in proc.stdout.readlines():
         line = line.decode('utf-8')
+        if '->' in line:    # Remote HEAD like b'  remotes/origin/HEAD -> origin/master'
+            continue
         if line.startswith('*'):
             line = line.split(' ')[1]
-        if '/' in line:
-            line = line.split('/')[-1]
+        if line.strip().startswith('remotes/'):
+            line = '/'.join(line.split('/')[2:])
         yield line.strip()
 
 
@@ -29,13 +31,19 @@ def get_branches():
 def get_new_command(command):
     missing_file = re.findall(
         r"error: pathspec '([^']*)' "
-        r"did not match any file\(s\) known to git.", command.output)[0]
+        r"did not match any file\(s\) known to git", command.output)[0]
     closest_branch = utils.get_closest(missing_file, get_branches(),
                                        fallback_to_first=False)
+
+    new_commands = []
+
     if closest_branch:
-        return replace_argument(command.script, missing_file, closest_branch)
-    elif command.script_parts[1] == 'checkout':
-        return replace_argument(command.script, 'checkout', 'checkout -b')
-    else:
-        return shell.and_('git branch {}', '{}').format(
-            missing_file, command.script)
+        new_commands.append(replace_argument(command.script, missing_file, closest_branch))
+    if command.script_parts[1] == 'checkout':
+        new_commands.append(replace_argument(command.script, 'checkout', 'checkout -b'))
+
+    if not new_commands:
+        new_commands.append(shell.and_('git branch {}', '{}').format(
+            missing_file, command.script))
+
+    return new_commands
diff --git a/thefuck/rules/git_clone_git_clone.py b/thefuck/rules/git_clone_git_clone.py
new file mode 100644
index 0000000..38bab25
--- /dev/null
+++ b/thefuck/rules/git_clone_git_clone.py
@@ -0,0 +1,12 @@
+from thefuck.specific.git import git_support
+
+
+@git_support
+def match(command):
+    return (' git clone ' in command.script
+            and 'fatal: Too many arguments.' in command.output)
+
+
+@git_support
+def get_new_command(command):
+    return command.script.replace(' git clone ', ' ', 1)
diff --git a/thefuck/rules/git_commit_add.py b/thefuck/rules/git_commit_add.py
new file mode 100644
index 0000000..4db1634
--- /dev/null
+++ b/thefuck/rules/git_commit_add.py
@@ -0,0 +1,17 @@
+from thefuck.utils import eager, replace_argument
+from thefuck.specific.git import git_support
+
+
+@git_support
+def match(command):
+    return (
+        "commit" in command.script_parts
+        and "no changes added to commit" in command.output
+    )
+
+
+@eager
+@git_support
+def get_new_command(command):
+    for opt in ("-a", "-p"):
+        yield replace_argument(command.script, "commit", "commit {}".format(opt))
diff --git a/thefuck/rules/git_hook_bypass.py b/thefuck/rules/git_hook_bypass.py
new file mode 100644
index 0000000..f4afcf7
--- /dev/null
+++ b/thefuck/rules/git_hook_bypass.py
@@ -0,0 +1,27 @@
+from thefuck.utils import replace_argument
+from thefuck.specific.git import git_support
+
+hooked_commands = ("am", "commit", "push")
+
+
+@git_support
+def match(command):
+    return any(
+        hooked_command in command.script_parts for hooked_command in hooked_commands
+    )
+
+
+@git_support
+def get_new_command(command):
+    hooked_command = next(
+        hooked_command
+        for hooked_command in hooked_commands
+        if hooked_command in command.script_parts
+    )
+    return replace_argument(
+        command.script, hooked_command, hooked_command + " --no-verify"
+    )
+
+
+priority = 1100
+requires_output = False
diff --git a/thefuck/rules/git_lfs_mistype.py b/thefuck/rules/git_lfs_mistype.py
new file mode 100644
index 0000000..afa3d5b
--- /dev/null
+++ b/thefuck/rules/git_lfs_mistype.py
@@ -0,0 +1,18 @@
+import re
+from thefuck.utils import get_all_matched_commands, replace_command
+from thefuck.specific.git import git_support
+
+
+@git_support
+def match(command):
+    '''
+    Match a mistyped command
+    '''
+    return 'lfs' in command.script and 'Did you mean this?' in command.output
+
+
+@git_support
+def get_new_command(command):
+    broken_cmd = re.findall(r'Error: unknown command "([^"]*)" for "git-lfs"', command.output)[0]
+    matched = get_all_matched_commands(command.output, ['Did you mean', ' for usage.'])
+    return replace_command(command, broken_cmd, matched)
diff --git a/thefuck/rules/git_main_master.py b/thefuck/rules/git_main_master.py
new file mode 100644
index 0000000..f595344
--- /dev/null
+++ b/thefuck/rules/git_main_master.py
@@ -0,0 +1,16 @@
+from thefuck.specific.git import git_support
+
+
+@git_support
+def match(command):
+    return "'master'" in command.output or "'main'" in command.output
+
+
+@git_support
+def get_new_command(command):
+    if "'master'" in command.output:
+        return command.script.replace("master", "main")
+    return command.script.replace("main", "master")
+
+
+priority = 1200
diff --git a/thefuck/rules/git_push_without_commits.py b/thefuck/rules/git_push_without_commits.py
index d2c59c2..408d2d7 100644
--- a/thefuck/rules/git_push_without_commits.py
+++ b/thefuck/rules/git_push_without_commits.py
@@ -1,14 +1,12 @@
 import re
+from thefuck.shells import shell
 from thefuck.specific.git import git_support
 
-fix = u'git commit -m "Initial commit." && {command}'
-refspec_does_not_match = re.compile(r'src refspec \w+ does not match any\.')
-
 
 @git_support
 def match(command):
-    return bool(refspec_does_not_match.search(command.output))
+    return bool(re.search(r"src refspec \w+ does not match any", command.output))
 
 
 def get_new_command(command):
-    return fix.format(command=command.script)
+    return shell.and_('git commit -m "Initial commit"', command.script)
diff --git a/thefuck/rules/go_unknown_command.py b/thefuck/rules/go_unknown_command.py
new file mode 100644
index 0000000..20d5242
--- /dev/null
+++ b/thefuck/rules/go_unknown_command.py
@@ -0,0 +1,28 @@
+from itertools import dropwhile, islice, takewhile
+import subprocess
+
+from thefuck.utils import get_closest, replace_argument, for_app, which, cache
+
+
+def get_golang_commands():
+    proc = subprocess.Popen('go', stderr=subprocess.PIPE)
+    lines = [line.decode('utf-8').strip() for line in proc.stderr.readlines()]
+    lines = dropwhile(lambda line: line != 'The commands are:', lines)
+    lines = islice(lines, 2, None)
+    lines = takewhile(lambda line: line, lines)
+    return [line.split(' ')[0] for line in lines]
+
+
+if which('go'):
+    get_golang_commands = cache(which('go'))(get_golang_commands)
+
+
+@for_app('go')
+def match(command):
+    return 'unknown command' in command.output
+
+
+def get_new_command(command):
+    closest_subcommand = get_closest(command.script_parts[1], get_golang_commands())
+    return replace_argument(command.script, command.script_parts[1],
+                            closest_subcommand)
diff --git a/thefuck/rules/gradle_no_task.py b/thefuck/rules/gradle_no_task.py
index 7820d1d..4a07479 100644
--- a/thefuck/rules/gradle_no_task.py
+++ b/thefuck/rules/gradle_no_task.py
@@ -5,7 +5,7 @@ from thefuck.utils import for_app, eager, replace_command
 regex = re.compile(r"Task '(.*)' (is ambiguous|not found)")
 
 
-@for_app('gradle', './gradlew')
+@for_app('gradle', 'gradlew')
 def match(command):
     return regex.findall(command.output)
 
diff --git a/thefuck/rules/missing_space_before_subcommand.py b/thefuck/rules/missing_space_before_subcommand.py
index 410cb4b..fd4fbd7 100644
--- a/thefuck/rules/missing_space_before_subcommand.py
+++ b/thefuck/rules/missing_space_before_subcommand.py
@@ -4,7 +4,7 @@ from thefuck.utils import get_all_executables, memoize
 @memoize
 def _get_executable(script_part):
     for executable in get_all_executables():
-        if script_part.startswith(executable):
+        if len(executable) > 1 and script_part.startswith(executable):
             return executable
 
 
diff --git a/thefuck/rules/nixos_cmd_not_found.py b/thefuck/rules/nixos_cmd_not_found.py
new file mode 100644
index 0000000..40d9d55
--- /dev/null
+++ b/thefuck/rules/nixos_cmd_not_found.py
@@ -0,0 +1,15 @@
+import re
+from thefuck.specific.nix import nix_available
+from thefuck.shells import shell
+
+regex = re.compile(r'nix-env -iA ([^\s]*)')
+enabled_by_default = nix_available
+
+
+def match(command):
+    return regex.findall(command.output)
+
+
+def get_new_command(command):
+    name = regex.findall(command.output)[0]
+    return shell.and_('nix-env -iA {}'.format(name), command.script)
diff --git a/thefuck/rules/pyenv_no_such_command.py b/thefuck/rules/omnienv_no_such_command.py
similarity index 50%
rename from thefuck/rules/pyenv_no_such_command.py
rename to thefuck/rules/omnienv_no_such_command.py
index cc9b609..ca8cc36 100644
--- a/thefuck/rules/pyenv_no_such_command.py
+++ b/thefuck/rules/omnienv_no_such_command.py
@@ -1,8 +1,12 @@
 import re
-from subprocess import PIPE, Popen
-
 from thefuck.utils import (cache, for_app, replace_argument, replace_command,
                            which)
+from subprocess import PIPE, Popen
+
+
+supported_apps = 'goenv', 'nodenv', 'pyenv', 'rbenv'
+enabled_by_default = any(which(a) for a in supported_apps)
+
 
 COMMON_TYPOS = {
     'list': ['versions', 'install --list'],
@@ -10,24 +14,22 @@ COMMON_TYPOS = {
 }
 
 
-@for_app('pyenv')
+@for_app(*supported_apps, at_least=1)
 def match(command):
-    return 'pyenv: no such command' in command.output
+    return 'env: no such command ' in command.output
 
 
-def get_pyenv_commands():
-    proc = Popen(['pyenv', 'commands'], stdout=PIPE)
+def get_app_commands(app):
+    proc = Popen([app, '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]
+    broken = re.findall(r"env: 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()))
+
+    app = command.script_parts[0]
+    app_commands = cache(which(app))(get_app_commands)(app)
+    matched.extend(replace_command(command, broken, app_commands))
     return matched
diff --git a/thefuck/rules/pacman_invalid_option.py b/thefuck/rules/pacman_invalid_option.py
new file mode 100644
index 0000000..cc50fd3
--- /dev/null
+++ b/thefuck/rules/pacman_invalid_option.py
@@ -0,0 +1,20 @@
+from thefuck.specific.archlinux import archlinux_env
+from thefuck.specific.sudo import sudo_support
+from thefuck.utils import for_app
+import re
+
+
+@sudo_support
+@for_app("pacman")
+def match(command):
+    return command.output.startswith("error: invalid option '-") and any(
+        " -{}".format(option) in command.script for option in "surqfdvt"
+    )
+
+
+def get_new_command(command):
+    option = re.findall(r" -[dfqrstuv]", command.script)[0]
+    return re.sub(option, option.upper(), command.script)
+
+
+enabled_by_default = archlinux_env()
diff --git a/thefuck/rules/pip_unknown_command.py b/thefuck/rules/pip_unknown_command.py
index 75fcc7c..2720cda 100644
--- a/thefuck/rules/pip_unknown_command.py
+++ b/thefuck/rules/pip_unknown_command.py
@@ -12,8 +12,8 @@ def match(command):
 
 
 def get_new_command(command):
-    broken_cmd = re.findall(r'ERROR: unknown command \"([a-z]+)\"',
+    broken_cmd = re.findall(r'ERROR: unknown command "([^"]+)"',
                             command.output)[0]
-    new_cmd = re.findall(r'maybe you meant \"([a-z]+)\"', command.output)[0]
+    new_cmd = re.findall(r'maybe you meant "([^"]+)"', command.output)[0]
 
     return replace_argument(command.script, broken_cmd, new_cmd)
diff --git a/thefuck/rules/python_module_error.py b/thefuck/rules/python_module_error.py
new file mode 100644
index 0000000..4696d63
--- /dev/null
+++ b/thefuck/rules/python_module_error.py
@@ -0,0 +1,13 @@
+import re
+from thefuck.shells import shell
+
+MISSING_MODULE = r"ModuleNotFoundError: No module named '([^']+)'"
+
+
+def match(command):
+    return "ModuleNotFoundError: No module named '" in command.output
+
+
+def get_new_command(command):
+    missing_module = re.findall(MISSING_MODULE, command.output)[0]
+    return shell.and_("pip install {}".format(missing_module), command.script)
diff --git a/thefuck/rules/rails_migrations_pending.py b/thefuck/rules/rails_migrations_pending.py
new file mode 100644
index 0000000..866c505
--- /dev/null
+++ b/thefuck/rules/rails_migrations_pending.py
@@ -0,0 +1,14 @@
+import re
+from thefuck.shells import shell
+
+
+SUGGESTION_REGEX = r"To resolve this issue, run:\s+(.*?)\n"
+
+
+def match(command):
+    return "Migrations are pending. To resolve this issue, run:" in command.output
+
+
+def get_new_command(command):
+    migration_script = re.search(SUGGESTION_REGEX, command.output).group(1)
+    return shell.and_(migration_script, command.script)
diff --git a/thefuck/rules/remove_shell_prompt_literal.py b/thefuck/rules/remove_shell_prompt_literal.py
new file mode 100644
index 0000000..d0d064e
--- /dev/null
+++ b/thefuck/rules/remove_shell_prompt_literal.py
@@ -0,0 +1,23 @@
+"""Fixes error for commands containing one or more occurrences of the shell
+prompt symbol '$'.
+
+This usually happens when commands are copied from documentations
+including them in their code blocks.
+
+Example:
+> $ git clone https://github.com/nvbn/thefuck.git
+bash: $: command not found...
+"""
+
+import re
+
+
+def match(command):
+    return (
+        "$: command not found" in command.output
+        and re.search(r"^[\s]*\$ [\S]+", command.script) is not None
+    )
+
+
+def get_new_command(command):
+    return command.script.lstrip("$ ")
diff --git a/thefuck/rules/sudo.py b/thefuck/rules/sudo.py
index 4723de0..a298021 100644
--- a/thefuck/rules/sudo.py
+++ b/thefuck/rules/sudo.py
@@ -21,7 +21,7 @@ patterns = ['permission denied',
             'edspermissionerror',
             'you don\'t have write permissions',
             'use `sudo`',
-            'SudoRequiredError',
+            'sudorequirederror',
             'error: insufficient privileges']
 
 
diff --git a/thefuck/rules/switch_lang.py b/thefuck/rules/switch_lang.py
index d535ad8..6f8d763 100644
--- a/thefuck/rules/switch_lang.py
+++ b/thefuck/rules/switch_lang.py
@@ -5,13 +5,14 @@ target_layout = '''qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:"ZXCVB
 # any new keyboard layout must be appended
 
 greek = u''';ςερτυθιοπ[]ασδφγηξκλ΄ζχψωβνμ,./:΅ΕΡΤΥΘΙΟΠ{}ΑΣΔΦΓΗΞΚΛ¨"ΖΧΨΩΒΝΜ<>?'''
+korean = u'''ㅂㅈㄷㄱㅅㅛㅕㅑㅐㅔ[]ㅁㄴㅇㄹㅎㅗㅓㅏㅣ;'ㅋㅌㅊㅍㅠㅜㅡ,./ㅃㅉㄸㄲㅆㅛㅕㅑㅒㅖ{}ㅁㄴㅇㄹㅎㅗㅓㅏㅣ:"ㅋㅌㅊㅍㅠㅜㅡ<>?'''
 
 source_layouts = [u'''йцукенгшщзхъфывапролджэячсмитьбю.ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,''',
                   u'''йцукенгшщзхїфівапролджєячсмитьбю.ЙЦУКЕНГШЩЗХЇФІВАПРОЛДЖЄЯЧСМИТЬБЮ,''',
                   u'''ضصثقفغعهخحجچشسیبلاتنمکگظطزرذدپو./ًٌٍَُِّْ][}{ؤئيإأآة»«:؛كٓژٰ‌ٔء><؟''',
                   u'''/'קראטוןםפ][שדגכעיחלךף,זסבהנמצתץ.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>?''',
-                  greek]
-
+                  greek,
+                  korean]
 
 source_to_target = {
     greek: {u';': "q", u'ς': "w", u'ε': "e", u'ρ': "r", u'τ': "t", u'υ': "y",
@@ -30,6 +31,19 @@ source_to_target = {
             u'Ώ': "V"},
 }
 
+'''Lists used for decomposing korean letters.'''
+HEAD_LIST = [u'ㄱ', u'ㄲ', u'ㄴ', u'ㄷ', u'ㄸ', u'ㄹ', u'ㅁ', u'ㅂ', u'ㅃ', u'ㅅ', u'ㅆ',
+             u'ㅇ', u'ㅈ', u'ㅉ', u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
+BODY_LIST = [u'ㅏ', u'ㅐ', u'ㅑ', u'ㅒ', u'ㅓ', u'ㅔ', u'ㅕ', u'ㅖ', u'ㅗ', u'ㅘ', u'ㅙ',
+             u'ㅚ', u'ㅛ', u'ㅜ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅠ', u'ㅡ', u'ㅢ', u'ㅣ']
+TAIL_LIST = [u' ', u'ㄱ', u'ㄲ', u'ㄳ', u'ㄴ', u'ㄵ', u'ㄶ', u'ㄷ', u'ㄹ', u'ㄺ', u'ㄻ',
+             u'ㄼ', u'ㄽ', u'ㄾ', u'ㄿ', u'ㅀ', u'ㅁ', u'ㅂ', u'ㅄ', u'ㅅ', u'ㅆ', u'ㅇ', u'ㅈ',
+             u'ㅊ', u'ㅋ', u'ㅌ', u'ㅍ', u'ㅎ']
+DOUBLE_LIST = [u'ㅘ', u'ㅙ', u'ㅚ', u'ㅝ', u'ㅞ', u'ㅟ', u'ㅢ', u'ㄳ', u'ㄵ', u'ㄶ', u'ㄺ',
+               u'ㄻ', u'ㄼ', u'ㄽ', u'ㄾ', u'ㅀ', u'ㅄ']
+DOUBLE_MOD_LIST = [u'ㅗㅏ', u'ㅗㅐ', u'ㅗㅣ', u'ㅜㅓ', u'ㅜㅔ', u'ㅜㅣ', u'ㅡㅣ', u'ㄱㅅ',
+                   u'ㄴㅈ', u'ㄴㅎ', u'ㄹㄱ', u'ㄹㅁ', u'ㄹㅂ', u'ㄹㅅ', u'ㄹㅌ', u'ㄹㅎ', u'ㅂㅅ']
+
 
 @memoize
 def _get_matched_layout(command):
@@ -50,8 +64,7 @@ def _get_matched_layout(command):
 def _switch(ch, layout):
     if ch in layout:
         return target_layout[layout.index(ch)]
-    else:
-        return ch
+    return ch
 
 
 def _switch_command(command, layout):
@@ -63,9 +76,33 @@ def _switch_command(command, layout):
     return ''.join(_switch(ch, layout) for ch in command.script)
 
 
+def _decompose_korean(command):
+    def _change_double(ch):
+        if ch in DOUBLE_LIST:
+            return DOUBLE_MOD_LIST[DOUBLE_LIST.index(ch)]
+        return ch
+
+    hg_str = u''
+    for ch in command.script:
+        if u'가' <= ch <= u'힣':
+            ord_ch = ord(ch) - ord(u'가')
+            hd = ord_ch // 588
+            bd = (ord_ch - 588 * hd) // 28
+            tl = ord_ch - 588 * hd - 28 * bd
+            for ch in [HEAD_LIST[hd], BODY_LIST[bd], TAIL_LIST[tl]]:
+                if ch != ' ':
+                    hg_str += _change_double(ch)
+        else:
+            hg_str += _change_double(ch)
+    return hg_str
+
+
 def match(command):
     if 'not found' not in command.output:
         return False
+    if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
+            for ch in command.script):
+        return True
 
     matched_layout = _get_matched_layout(command)
     return (matched_layout and
@@ -73,5 +110,8 @@ def match(command):
 
 
 def get_new_command(command):
+    if any(u'ㄱ' <= ch <= u'ㅎ' or u'ㅏ' <= ch <= u'ㅣ' or u'가' <= ch <= u'힣'
+            for ch in command.script):
+        command.script = _decompose_korean(command)
     matched_layout = _get_matched_layout(command)
     return _switch_command(command, matched_layout)
diff --git a/thefuck/rules/terraform_init.py b/thefuck/rules/terraform_init.py
new file mode 100644
index 0000000..29dd44f
--- /dev/null
+++ b/thefuck/rules/terraform_init.py
@@ -0,0 +1,13 @@
+from thefuck.shells import shell
+from thefuck.utils import for_app
+
+
+@for_app('terraform')
+def match(command):
+    return ('this module is not yet installed' in command.output.lower() or
+            'initialization required' in command.output.lower()
+            )
+
+
+def get_new_command(command):
+    return shell.and_('terraform init', command.script)
diff --git a/thefuck/rules/touch.py b/thefuck/rules/touch.py
index 31e9fce..56d86c0 100644
--- a/thefuck/rules/touch.py
+++ b/thefuck/rules/touch.py
@@ -9,6 +9,6 @@ def match(command):
 
 
 def get_new_command(command):
-    path = path = re.findall(
+    path = re.findall(
         r"touch: (?:cannot touch ')?(.+)/.+'?:", command.output)[0]
     return shell.and_(u'mkdir -p {}'.format(path), command.script)
diff --git a/thefuck/rules/wrong_hyphen_before_subcommand.py b/thefuck/rules/wrong_hyphen_before_subcommand.py
new file mode 100644
index 0000000..4fbaf64
--- /dev/null
+++ b/thefuck/rules/wrong_hyphen_before_subcommand.py
@@ -0,0 +1,20 @@
+from thefuck.utils import get_all_executables
+from thefuck.specific.sudo import sudo_support
+
+
+@sudo_support
+def match(command):
+    first_part = command.script_parts[0]
+    if "-" not in first_part or first_part in get_all_executables():
+        return False
+    cmd, _ = first_part.split("-", 1)
+    return cmd in get_all_executables()
+
+
+@sudo_support
+def get_new_command(command):
+    return command.script.replace("-", " ", 1)
+
+
+priority = 4500
+requires_output = False
diff --git a/thefuck/rules/yum_invalid_operation.py b/thefuck/rules/yum_invalid_operation.py
new file mode 100644
index 0000000..13a9001
--- /dev/null
+++ b/thefuck/rules/yum_invalid_operation.py
@@ -0,0 +1,39 @@
+import subprocess
+from itertools import dropwhile, islice, takewhile
+
+from thefuck.specific.sudo import sudo_support
+from thefuck.specific.yum import yum_available
+from thefuck.utils import for_app, replace_command, which, cache
+
+enabled_by_default = yum_available
+
+
+@sudo_support
+@for_app('yum')
+def match(command):
+    return 'No such command: ' in command.output
+
+
+def _get_operations():
+    proc = subprocess.Popen('yum', stdout=subprocess.PIPE)
+
+    lines = proc.stdout.readlines()
+    lines = [line.decode('utf-8') for line in lines]
+    lines = dropwhile(lambda line: not line.startswith("List of Commands:"), lines)
+    lines = islice(lines, 2, None)
+    lines = list(takewhile(lambda line: line.strip(), lines))
+    return [line.strip().split(' ')[0] for line in lines]
+
+
+if which('yum'):
+    _get_operations = cache(which('yum'))(_get_operations)
+
+
+@sudo_support
+def get_new_command(command):
+    invalid_operation = command.script_parts[1]
+
+    if invalid_operation == 'uninstall':
+        return [command.script.replace('uninstall', 'remove')]
+
+    return replace_command(command, invalid_operation, _get_operations())
diff --git a/thefuck/shells/fish.py b/thefuck/shells/fish.py
index 5147819..eb7e915 100644
--- a/thefuck/shells/fish.py
+++ b/thefuck/shells/fish.py
@@ -52,7 +52,7 @@ class Fish(Generic):
         if settings.alter_history:
             alter_history = ('    builtin history delete --exact'
                              ' --case-sensitive -- $fucked_up_command\n'
-                             '    builtin history merge ^ /dev/null\n')
+                             '    builtin history merge\n')
         else:
             alter_history = ''
         # It is VERY important to have the variables declared WITHIN the alias
diff --git a/thefuck/shells/powershell.py b/thefuck/shells/powershell.py
index 59b30ba..95226d0 100644
--- a/thefuck/shells/powershell.py
+++ b/thefuck/shells/powershell.py
@@ -24,9 +24,9 @@ class Powershell(Generic):
 
     def how_to_configure(self):
         return ShellConfiguration(
-            content=u'iex "thefuck --alias"',
+            content=u'iex "$(thefuck --alias)"',
             path='$profile',
-            reload='& $profile',
+            reload='. $profile',
             can_configure_automatically=False)
 
     def _get_version(self):
diff --git a/thefuck/specific/git.py b/thefuck/specific/git.py
index b2c107c..9793146 100644
--- a/thefuck/specific/git.py
+++ b/thefuck/specific/git.py
@@ -14,7 +14,7 @@ def git_support(fn, command):
         return False
 
     # perform git aliases expansion
-    if 'trace: alias expansion:' in command.output:
+    if command.output and 'trace: alias expansion:' in command.output:
         search = re.search("trace: alias expansion: ([^ ]*) => ([^\n]*)",
                            command.output)
         alias = search.group(1)
@@ -25,7 +25,7 @@ def git_support(fn, command):
         # eg. 'git commit'
         expansion = ' '.join(shell.quote(part)
                              for part in shell.split_command(search.group(2)))
-        new_script = command.script.replace(alias, expansion)
+        new_script = re.sub(r"\b{}\b".format(alias), expansion, command.script)
 
         command = command.update(script=new_script)
 
diff --git a/thefuck/specific/nix.py b/thefuck/specific/nix.py
new file mode 100644
index 0000000..87b1f1d
--- /dev/null
+++ b/thefuck/specific/nix.py
@@ -0,0 +1,3 @@
+from thefuck.utils import which
+
+nix_available = bool(which('nix'))
diff --git a/thefuck/specific/yum.py b/thefuck/specific/yum.py
new file mode 100644
index 0000000..aeff11c
--- /dev/null
+++ b/thefuck/specific/yum.py
@@ -0,0 +1,3 @@
+from thefuck.utils import which
+
+yum_available = bool(which('yum'))
diff --git a/thefuck/types.py b/thefuck/types.py
index 8c5770f..96e6ace 100644
--- a/thefuck/types.py
+++ b/thefuck/types.py
@@ -122,7 +122,7 @@ class Rule(object):
     def __repr__(self):
         return 'Rule(name={}, match={}, get_new_command={}, ' \
                'enabled_by_default={}, side_effect={}, ' \
-               'priority={}, requires_output)'.format(
+               'priority={}, requires_output={})'.format(
                    self.name, self.match, self.get_new_command,
                    self.enabled_by_default, self.side_effect,
                    self.priority, self.requires_output)
@@ -136,9 +136,16 @@ class Rule(object):
 
         """
         name = path.name[:-3]
+        if name in settings.exclude_rules:
+            logs.debug(u'Ignoring excluded rule: {}'.format(name))
+            return
         with logs.debug_time(u'Importing rule: {};'.format(name)):
-            rule_module = load_source(name, str(path))
-            priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
+            try:
+                rule_module = load_source(name, str(path))
+            except Exception:
+                logs.exception(u"Rule {} failed to load".format(name), sys.exc_info())
+                return
+        priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
         return cls(name, rule_module.match,
                    rule_module.get_new_command,
                    getattr(rule_module, 'enabled_by_default', True),
@@ -153,14 +160,11 @@ class Rule(object):
         :rtype: bool
 
         """
-        if self.name in settings.exclude_rules:
-            return False
-        elif self.name in settings.rules:
-            return True
-        elif self.enabled_by_default and ALL_ENABLED in settings.rules:
-            return True
-        else:
-            return False
+        return (
+            self.name in settings.rules
+            or self.enabled_by_default
+            and ALL_ENABLED in settings.rules
+        )
 
     def is_match(self, command):
         """Returns `True` if rule matches the command.
@@ -255,4 +259,4 @@ class CorrectedCommand(object):
         logs.debug(u'PYTHONIOENCODING: {}'.format(
             os.environ.get('PYTHONIOENCODING', '!!not-set!!')))
 
-        print(self._get_script())
+        sys.stdout.write(self._get_script())
diff --git a/thefuck/utils.py b/thefuck/utils.py
index 6112c01..466e4ba 100644
--- a/thefuck/utils.py
+++ b/thefuck/utils.py
@@ -98,12 +98,16 @@ def get_closest(word, possibilities, cutoff=0.6, fallback_to_first=True):
 
 
 def get_close_matches(word, possibilities, n=None, cutoff=0.6):
-    """Overrides `difflib.get_close_match` to controle argument `n`."""
+    """Overrides `difflib.get_close_match` to control argument `n`."""
     if n is None:
         n = settings.num_close_matches
     return difflib_get_close_matches(word, possibilities, n, cutoff)
 
 
+def include_path_in_search(path):
+    return not any(path.startswith(x) for x in settings.excluded_search_path_prefixes)
+
+
 @memoize
 def get_all_executables():
     from thefuck.shells import shell
@@ -119,6 +123,7 @@ def get_all_executables():
 
     bins = [exe.name.decode('utf8') if six.PY2 else exe.name
             for path in os.environ.get('PATH', '').split(os.pathsep)
+            if include_path_in_search(path)
             for exe in _safe(lambda: list(Path(path).iterdir()), [])
             if not _safe(exe.is_dir, True)
             and exe.name not in tf_entry_points]
@@ -175,13 +180,13 @@ def is_app(command, *app_names, **kwargs):
         raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))
 
     if len(command.script_parts) > at_least:
-        return command.script_parts[0] in app_names
+        return os.path.basename(command.script_parts[0]) in app_names
 
     return False
 
 
 def for_app(*app_names, **kwargs):
-    """Specifies that matching script is for on of app names."""
+    """Specifies that matching script is for one of app names."""
     def _for_app(fn, command):
         if is_app(command, *app_names, **kwargs):
             return fn(command)
@@ -289,10 +294,15 @@ def cache(*depends_on):
 cache.disabled = False
 
 
-def get_installation_info():
-    import pkg_resources
+def get_installation_version():
+    try:
+        from importlib.metadata import version
+
+        return version('thefuck')
+    except ImportError:
+        import pkg_resources
 
-    return pkg_resources.require('thefuck')[0]
+        return pkg_resources.require('thefuck')[0].version
 
 
 def get_alias():
@@ -334,4 +344,4 @@ def format_raw_script(raw_script):
     else:
         script = ' '.join(raw_script)
 
-    return script.strip()
+    return script.lstrip()
diff --git a/tox.ini b/tox.ini
index 63391c6..6e0646d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,py34,py35,py36,py37
+envlist = py27,py35,py36,py37,py38
 
 [testenv]
 deps = -rrequirements.txt