mirror of
https://github.com/nvbn/thefuck.git
synced 2025-10-31 23:22:10 +00:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7e7e1d884 | ||
|
|
62e0767c50 | ||
|
|
3cd187a3bb | ||
|
|
0420442e77 | ||
|
|
617aaa1fd0 | ||
|
|
cf0921be4a | ||
|
|
ef1ea4b4dd | ||
|
|
2cadcca904 | ||
|
|
d81929f294 | ||
|
|
b03e0913d3 | ||
|
|
b79e104df8 | ||
|
|
3f71959b1b | ||
|
|
51b82c5377 | ||
|
|
5f562a185c | ||
|
|
1a242c7daa | ||
|
|
ceeaeab94b | ||
|
|
77627a3140 | ||
|
|
03a032295d | ||
|
|
56c16b737f | ||
|
|
f9768cf929 | ||
|
|
ed40463105 | ||
|
|
f1b7d879bd | ||
|
|
5198b34f24 | ||
|
|
1a1b5200a9 | ||
|
|
06cb50b1e3 | ||
|
|
16eb823066 | ||
|
|
d8ddf5a2be | ||
|
|
cf1beb6b89 | ||
|
|
925e562d96 | ||
|
|
841e3f9e13 | ||
|
|
0f4a523dc4 | ||
|
|
c719712b62 | ||
|
|
51e4e87280 | ||
|
|
7b7c150bb7 | ||
|
|
2a166a7dec | ||
|
|
8fa10b1049 | ||
|
|
7f3442747e | ||
|
|
8bebce331e | ||
|
|
dbc435c040 | ||
|
|
30c90bccaa | ||
|
|
8e8c80c227 | ||
|
|
c2df71caed | ||
|
|
eb05b28c5b | ||
|
|
a2a6cbdc70 | ||
|
|
58ddd4338a | ||
|
|
58f61d8090 | ||
|
|
0668822abb | ||
|
|
711feb4df5 | ||
|
|
70a42b54ab | ||
|
|
11b70526f7 | ||
|
|
55922e4dbe | ||
|
|
799f4127ca | ||
|
|
fe1942866b | ||
|
|
13fda64d6e | ||
|
|
6111523034 | ||
|
|
24576b30b2 | ||
|
|
373f445e9b | ||
|
|
54253027e3 | ||
|
|
9201ce79cf | ||
|
|
7f97818663 | ||
|
|
2f4adcf3cb | ||
|
|
06e14afd17 | ||
|
|
3651b0fa0c | ||
|
|
d723bb71b7 | ||
|
|
b2e1886de8 | ||
|
|
0949d2e770 | ||
|
|
e343c577cd | ||
|
|
6da0bc557f | ||
|
|
1a595f1ba2 | ||
|
|
875d3f11cb | ||
|
|
4c7479b3ad | ||
|
|
5b612add74 | ||
|
|
b9dd54c768 | ||
|
|
7af9f41d93 | ||
|
|
c2cc95db88 | ||
|
|
0e34c2343e | ||
|
|
fd90e69ceb | ||
|
|
0c58317932 | ||
|
|
62dddd5821 | ||
|
|
40dd65963d | ||
|
|
836f6eeac5 | ||
|
|
b4c75eebe6 | ||
|
|
22efa8f70e | ||
|
|
9d3bcad229 | ||
|
|
c196e2901c | ||
|
|
ca46956e20 | ||
|
|
639e9bda7a | ||
|
|
39753a004e | ||
|
|
365db1ee41 | ||
|
|
81b39defe4 | ||
|
|
f82176802e | ||
|
|
6975d30818 | ||
|
|
3c542a5b8c |
10
.devcontainer/Dockerfile
Normal file
10
.devcontainer/Dockerfile
Normal file
@@ -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
|
||||
39
.devcontainer/devcontainer.json
Normal file
39
.devcontainer/devcontainer.json
Normal file
@@ -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"
|
||||
}
|
||||
68
.github/workflows/test.yml
vendored
Normal file
68
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
PYTHON_LATEST: "3.11"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-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: |
|
||||
docker build -t thefuck/python3 -f tests/Dockerfile --build-arg PYTHON_VERSION=3 .
|
||||
docker build -t thefuck/python2 -f tests/Dockerfile --build-arg PYTHON_VERSION=2 .
|
||||
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
|
||||
test-deprecated:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["2.7", "3.6"]
|
||||
runs-on: ubuntu-latest
|
||||
container: python:${{ matrix.python-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install The Fuck with all dependencies
|
||||
run: |
|
||||
pip install -Ur requirements.txt coveralls
|
||||
python setup.py develop
|
||||
- name: Lint
|
||||
run: flake8
|
||||
- name: Run tests
|
||||
run: coverage run --source=thefuck,tests -m pytest -v --capture=sys tests
|
||||
51
.travis.yml
51
.travis.yml
@@ -1,51 +0,0 @@
|
||||
language: python
|
||||
sudo: false
|
||||
os: linux
|
||||
dist: xenial
|
||||
matrix:
|
||||
include:
|
||||
- python: "nightly"
|
||||
- python: "3.8-dev"
|
||||
- python: "3.8"
|
||||
- python: "3.7-dev"
|
||||
- python: "3.7"
|
||||
- python: "3.6-dev"
|
||||
- python: "3.6"
|
||||
- python: "3.5"
|
||||
- python: "2.7"
|
||||
- os: osx
|
||||
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 unlink python@2; 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.8 && $TRAVIS_OS_NAME != "osx" ]]; then $RUN_TESTS --enable-functional; fi
|
||||
- if [[ $TRAVIS_PYTHON_VERSION != 3.8 || $TRAVIS_OS_NAME == "osx" ]]; then $RUN_TESTS; fi
|
||||
after_success:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 3.8 && $TRAVIS_OS_NAME != "osx" ]]; then coveralls; fi
|
||||
@@ -26,6 +26,13 @@ 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:
|
||||
@@ -44,13 +51,13 @@ flake8
|
||||
Run unit tests:
|
||||
|
||||
```bash
|
||||
py.test
|
||||
pytest
|
||||
```
|
||||
|
||||
Run unit and functional tests (requires docker):
|
||||
|
||||
```bash
|
||||
py.test --enable-functional
|
||||
pytest --enable-functional
|
||||
```
|
||||
|
||||
For sending package to pypi:
|
||||
@@ -59,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.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright (c) 2015-2018 Vladimir Iakovlev
|
||||
Copyright (c) 2015-2022 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
|
||||
|
||||
131
README.md
131
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+)
|
||||
- python (3.5+)
|
||||
- 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:
|
||||
@@ -122,6 +137,11 @@ On ChromeOS, install *The Fuck* using [chromebrew](https://github.com/skycocker/
|
||||
crew install thefuck
|
||||
```
|
||||
|
||||
On Arch based systems, install *The Fuck* with the following command:
|
||||
```
|
||||
sudo pacman -S thefuck
|
||||
```
|
||||
|
||||
On other systems, install *The Fuck* by using `pip`:
|
||||
|
||||
```bash
|
||||
@@ -157,6 +177,8 @@ To fix commands recursively until succeeding, use the `-r` option:
|
||||
fuck -r
|
||||
```
|
||||
|
||||
##### [Back to Contents](#contents)
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
@@ -165,6 +187,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
|
||||
@@ -176,15 +204,17 @@ following rules are enabled by default:
|
||||
* `aws_cli` – fixes misspelled commands like `aws dynamdb scan`;
|
||||
* `az_cli` – fixes misspelled commands like `az providers`;
|
||||
* `cargo` – runs `cargo build` instead of `cargo`;
|
||||
* `cargo_no_command` – fixes wrongs commands like `cargo buid`;
|
||||
* `cargo_no_command` – fixes wrong 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_correction` – spellchecks and corrects 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;
|
||||
* `chmod_x` – adds execution bit;
|
||||
* `choco_install` – appends common suffixes for chocolatey packages;
|
||||
* `composer_not_command` – fixes composer command name;
|
||||
* `cp_create_destination` – creates a new directory when you attempt to `cp` or `mv` to a non existent one
|
||||
* `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;
|
||||
@@ -195,7 +225,7 @@ following rules are enabled by default:
|
||||
* `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;
|
||||
* `fab_command_not_found` – fixes misspelled fabric commands;
|
||||
* `fix_alt_space` – replaces Alt+Space with Space character;
|
||||
* `fix_file` – opens a file with an error in your `$EDITOR`;
|
||||
* `gem_unknown_command` – fixes wrong `gem` commands;
|
||||
@@ -206,7 +236,11 @@ following rules are enabled by default:
|
||||
* `git_branch_delete_checked_out` – changes `git branch -d` to `git checkout master && git branch -D` when trying to delete a checked out branch;
|
||||
* `git_branch_exists` – offers `git branch -d foo`, `git branch -D foo` or `git checkout foo` when creating a branch that already exists;
|
||||
* `git_branch_list` – catches `git branch list` in place of `git branch` and removes created branch;
|
||||
* `git_branch_0flag` – fixes commands such as `git branch 0v` and `git branch 0r` removing the created branch;
|
||||
* `git_checkout` – fixes branch name or creates new branch;
|
||||
* `git_clone_git_clone` – replaces `git clone git clone ...` with `git clone ...`
|
||||
* `git_clone_missing` – adds `git clone` to URLs that appear to link to a git repository.
|
||||
* `git_commit_add` – offers `git commit -a ...` or `git commit -p ...` after previous commit if it failed because nothing was staged;
|
||||
* `git_commit_amend` – offers `git commit --amend` after previous commit;
|
||||
* `git_commit_reset` – offers `git reset HEAD~` after previous commit;
|
||||
* `git_diff_no_index` – adds `--no-index` to previous `git diff` on untracked files;
|
||||
@@ -214,6 +248,9 @@ following rules are enabled by default:
|
||||
* `git_fix_stash` – fixes `git stash` commands (misspelled subcommand and missing `save`);
|
||||
* `git_flag_after_filename` – fixes `fatal: bad flag '...' after filename`
|
||||
* `git_help_aliased` – fixes `git help <alias>` commands replacing <alias> with the aliased command;
|
||||
* `git_hook_bypass` – adds `--no-verify` flag previous to `git am`, `git commit`, or `git push` command;
|
||||
* `git_lfs_mistype` – fixes mistyped `git lfs <command>` commands;
|
||||
* `git_main_master` – fixes incorrect branch name between `main` and `master`
|
||||
* `git_merge` – adds remote to branch names;
|
||||
* `git_merge_unrelated` – adds `--allow-unrelated-histories` when required
|
||||
* `git_not_command` – fixes wrong git commands like `git brnch`;
|
||||
@@ -221,12 +258,12 @@ following rules are enabled by default:
|
||||
* `git_pull_clone` – clones instead of pulling when the repo does not exist;
|
||||
* `git_pull_uncommitted_changes` – stashes changes before pulling and pops them afterwards;
|
||||
* `git_push` – adds `--set-upstream origin $branch` to previous failed `git push`;
|
||||
* `git_push_different_branch_names` – fixes pushes when local brach name does not match remote branch name;
|
||||
* `git_push_different_branch_names` – fixes pushes when local branch name does not match remote branch name;
|
||||
* `git_push_pull` – runs `git pull` when `push` was rejected;
|
||||
* `git_push_without_commits` – Creates an initial commit if you forget and only `git add .`, when setting up a new project;
|
||||
* `git_push_without_commits` – creates an initial commit if you forget and only `git add .`, when setting up a new project;
|
||||
* `git_rebase_no_changes` – runs `git rebase --skip` instead of `git rebase --continue` when there are no changes;
|
||||
* `git_remote_delete` – replaces `git remote delete remote_name` with `git remote remove remote_name`;
|
||||
* `git_rm_local_modifications` – adds `-f` or `--cached` when you try to `rm` a locally modified file;
|
||||
* `git_rm_local_modifications` – adds `-f` or `--cached` when you try to `rm` a locally modified file;
|
||||
* `git_rm_recursive` – adds `-r` when you try to `rm` a directory;
|
||||
* `git_rm_staged` – adds `-f` or `--cached` when you try to `rm` a file with staged changes
|
||||
* `git_rebase_merge_dir` – offers `git rebase (--continue | --abort | --skip)` or removing the `.git/rebase-merge` dir when a rebase is in progress;
|
||||
@@ -244,9 +281,9 @@ following rules are enabled by default:
|
||||
* `grunt_task_not_found` – fixes misspelled `grunt` commands;
|
||||
* `gulp_not_task` – fixes misspelled `gulp` tasks;
|
||||
* `has_exists_script` – prepends `./` when script/binary exists;
|
||||
* `heroku_multiple_apps` – add `--app <app>` to `heroku` commands like `heroku pg`;
|
||||
* `heroku_multiple_apps` – adds `--app <app>` to `heroku` commands like `heroku pg`;
|
||||
* `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`;
|
||||
* `history` – tries to replace command with most similar command from history;
|
||||
* `history` – tries to replace command with the most similar command from history;
|
||||
* `hostscli` – tries to fix `hostscli` usage;
|
||||
* `ifconfig_device_not_found` – fixes wrong device names like `wlan0` to `wlp2s0`;
|
||||
* `java` – removes `.java` extension when running Java programs;
|
||||
@@ -261,7 +298,7 @@ following rules are enabled by default:
|
||||
* `man_no_space` – fixes man commands without spaces, for example `mandiff`;
|
||||
* `mercurial` – fixes wrong `hg` commands;
|
||||
* `missing_space_before_subcommand` – fixes command with missing space like `npminstall`;
|
||||
* `mkdir_p` – adds `-p` when you try to create a directory without parent;
|
||||
* `mkdir_p` – adds `-p` when you try to create a directory without a parent;
|
||||
* `mvn_no_command` – adds `clean package` to `mvn`;
|
||||
* `mvn_unknown_lifecycle_phase` – fixes misspelled life cycle phases with `mvn`;
|
||||
* `npm_missing_script` – fixes `npm` custom script name in `npm run-script <script>`;
|
||||
@@ -269,45 +306,51 @@ following rules are enabled by default:
|
||||
* `npm_wrong_command` – fixes wrong npm commands like `npm urgrade`;
|
||||
* `no_command` – fixes wrong console commands, for example `vom/vim`;
|
||||
* `no_such_file` – creates missing directories with `mv` and `cp` commands;
|
||||
* `open` – either prepends `http://` to address passed to `open` or create a new file or directory and passes it to `open`;
|
||||
* `omnienv_no_such_command` – fixes wrong commands for `goenv`, `nodenv`, `pyenv` and `rbenv` (eg.: `pyenv isntall` or `goenv list`);
|
||||
* `open` – either prepends `http://` to address passed to `open` or creates a new file or directory and passes it to `open`;
|
||||
* `pip_install` – fixes permission issues with `pip install` commands by adding `--user` or prepending `sudo` if necessary;
|
||||
* `pip_unknown_command` – fixes wrong `pip` commands, for example `pip instatl/pip install`;
|
||||
* `php_s` – replaces `-s` by `-S` when trying to run a local php server;
|
||||
* `port_already_in_use` – kills process that bound port;
|
||||
* `prove_recursively` – adds `-r` when called with directory;
|
||||
* `pyenv_no_such_command` – fixes wrong pyenv commands like `pyenv isntall` or `pyenv list`;
|
||||
* `python_command` – prepends `python` when you try to run non-executable/without `./` python script;
|
||||
* `python_execute` – appends missing `.py` when executing Python files;
|
||||
* `python_module_error` – fixes ModuleNotFoundError by trying to `pip install` that module;
|
||||
* `quotation_marks` – fixes uneven usage of `'` and `"` when containing args';
|
||||
* `path_from_history` – replaces not found path with similar absolute path from history;
|
||||
* `path_from_history` – replaces not found path with a similar absolute path from history;
|
||||
* `rails_migrations_pending` – runs pending migrations;
|
||||
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
|
||||
* `remove_shell_prompt_literal` – remove leading shell prompt symbol `$`, common when copying commands from documentations;
|
||||
* `remove_trailing_cedilla` – remove trailing cedillas `ç`, a common typo for european keyboard layouts;
|
||||
* `remove_shell_prompt_literal` – removes leading shell prompt symbol `$`, common when copying commands from documentations;
|
||||
* `remove_trailing_cedilla` – removes trailing cedillas `ç`, a common typo for European keyboard layouts;
|
||||
* `rm_dir` – adds `-rf` when you try to remove a directory;
|
||||
* `scm_correction` – corrects wrong scm like `hg log` to `git log`;
|
||||
* `sed_unterminated_s` – adds missing '/' to `sed`'s `s` commands;
|
||||
* `sl_ls` – changes `sl` to `ls`;
|
||||
* `ssh_known_hosts` – removes host from `known_hosts` on warning;
|
||||
* `sudo` – prepends `sudo` to previous command if it failed because of permissions;
|
||||
* `sudo` – prepends `sudo` to the previous command if it failed because of permissions;
|
||||
* `sudo_command_from_user_path` – runs commands from users `$PATH` with `sudo`;
|
||||
* `switch_lang` – switches command from your local layout to en;
|
||||
* `systemctl` – correctly orders parameters of confusing `systemctl`;
|
||||
* `terraform_init.py` – run `terraform init` before plan or apply;
|
||||
* `test.py` – runs `py.test` instead of `test.py`;
|
||||
* `terraform_init.py` – runs `terraform init` before plan or apply;
|
||||
* `terraform_no_command.py` – fixes unrecognized `terraform` commands;
|
||||
* `test.py` – runs `pytest` instead of `test.py`;
|
||||
* `touch` – creates missing directories before "touching";
|
||||
* `tsuru_login` – runs `tsuru login` if not authenticated or session expired;
|
||||
* `tsuru_not_command` – fixes wrong `tsuru` commands like `tsuru shell`;
|
||||
* `tmux` – fixes `tmux` commands;
|
||||
* `unknown_command` – fixes hadoop hdfs-style "unknown command", for example adds missing '-' to the command on `hdfs dfs ls`;
|
||||
* `unsudo` – removes `sudo` from previous command if a process refuses to run on super user privilege.
|
||||
* `unsudo` – removes `sudo` from previous command if a process refuses to run on superuser privilege.
|
||||
* `vagrant_up` – starts up the vagrant instance;
|
||||
* `whois` – fixes `whois` command;
|
||||
* `workon_doesnt_exists` – fixes `virtualenvwrapper` env name os suggests to create new.
|
||||
* `wrong_hyphen_before_subcommand` – removes an improperly placed hyphen (`apt-install` -> `apt install`, `git-log` -> `git log`, etc.)
|
||||
* `yarn_alias` – fixes aliased `yarn` commands like `yarn ls`;
|
||||
* `yarn_command_not_found` – fixes misspelled `yarn` commands;
|
||||
* `yarn_command_replaced` – fixes replaced `yarn` commands;
|
||||
* `yarn_help` – makes it easier to open `yarn` documentation;
|
||||
|
||||
##### [Back to Contents](#contents)
|
||||
|
||||
The following rules are enabled by default on specific platforms only:
|
||||
|
||||
* `apt_get` – installs app from apt if it not installed (requires `python-commandnotfound` / `python3-commandnotfound`);
|
||||
@@ -324,8 +367,9 @@ The following rules are enabled by default on specific platforms only:
|
||||
* `brew_update_formula` – turns `brew update <formula>` into `brew upgrade <formula>`;
|
||||
* `dnf_no_such_command` – fixes mistyped DNF commands;
|
||||
* `nixos_cmd_not_found` – installs apps on NixOS;
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yay` or `yaourt` if available);
|
||||
* `pacman_not_found` – fixes package name with `pacman`, `yay` or `yaourt`.
|
||||
* `pacman` – installs app with `pacman` if it is not installed (uses `yay`, `pikaur` or `yaourt` if available);
|
||||
* `pacman_invalid_option` – replaces lowercase `pacman` options with uppercase.
|
||||
* `pacman_not_found` – fixes package name with `pacman`, `yay`, `pikaur` or `yaourt`.
|
||||
* `yum_invalid_operation` – fixes invalid `yum` calls, like `yum isntall vim`;
|
||||
|
||||
The following commands are bundled with *The Fuck*, but are not enabled by
|
||||
@@ -334,6 +378,8 @@ default:
|
||||
* `git_push_force` – adds `--force-with-lease` to a `git push` (may conflict with `git_push_pull`);
|
||||
* `rm_root` – adds `--no-preserve-root` to `rm -rf /` command.
|
||||
|
||||
##### [Back to Contents](#contents)
|
||||
|
||||
## Creating your own rules
|
||||
|
||||
To add your own rule, create a file named `your-rule-name.py`
|
||||
@@ -387,6 +433,8 @@ 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`
|
||||
@@ -395,15 +443,16 @@ Several *The Fuck* parameters can be changed in the file `$XDG_CONFIG_HOME/thefu
|
||||
* `rules` – list of enabled rules, by default `thefuck.const.DEFAULT_RULES`;
|
||||
* `exclude_rules` – list of disabled rules, by default `[]`;
|
||||
* `require_confirmation` – requires confirmation before running new command, by default `True`;
|
||||
* `wait_command` – max amount of time in seconds for getting previous command output;
|
||||
* `wait_command` – the max amount of time in seconds for getting previous command output;
|
||||
* `no_colors` – disable colored output;
|
||||
* `priority` – dict with rules priorities, rule with lower `priority` will be matched first;
|
||||
* `debug` – enables debug output, by default `False`;
|
||||
* `history_limit` – numeric value of how many history commands will be scanned, like `2000`;
|
||||
* `history_limit` – the numeric value of how many history commands will be scanned, like `2000`;
|
||||
* `alter_history` – push fixed command to history, by default `True`;
|
||||
* `wait_slow_command` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
||||
* `slow_commands` – list of slow commands;
|
||||
* `num_close_matches` – maximum number of close matches to suggest, by default `3`.
|
||||
* `num_close_matches` – the maximum number of close matches to suggest, by default `3`.
|
||||
* `excluded_search_path_prefixes` – path prefixes to ignore when searching for commands, by default `[]`.
|
||||
|
||||
An example of `settings.py`:
|
||||
|
||||
@@ -426,16 +475,17 @@ Or via environment variables:
|
||||
* `THEFUCK_RULES` – list of enabled rules, like `DEFAULT_RULES:rm_root` or `sudo:no_command`;
|
||||
* `THEFUCK_EXCLUDE_RULES` – list of disabled rules, like `git_pull:git_push`;
|
||||
* `THEFUCK_REQUIRE_CONFIRMATION` – require confirmation before running new command, `true/false`;
|
||||
* `THEFUCK_WAIT_COMMAND` – max amount of time in seconds for getting previous command output;
|
||||
* `THEFUCK_WAIT_COMMAND` – the max amount of time in seconds for getting previous command output;
|
||||
* `THEFUCK_NO_COLORS` – disable colored output, `true/false`;
|
||||
* `THEFUCK_PRIORITY` – priority of the rules, like `no_command=9999:apt_get=100`,
|
||||
rule with lower `priority` will be matched first;
|
||||
* `THEFUCK_DEBUG` – enables debug output, `true/false`;
|
||||
* `THEFUCK_HISTORY_LIMIT` – how many history commands will be scanned, like `2000`;
|
||||
* `THEFUCK_ALTER_HISTORY` – push fixed command to history `true/false`;
|
||||
* `THEFUCK_WAIT_SLOW_COMMAND` – max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
||||
* `THEFUCK_WAIT_SLOW_COMMAND` – the max amount of time in seconds for getting previous command output if it in `slow_commands` list;
|
||||
* `THEFUCK_SLOW_COMMANDS` – list of slow commands, like `lein:gradle`;
|
||||
* `THEFUCK_NUM_CLOSE_MATCHES` – maximum number of close matches to suggest, like `5`.
|
||||
* `THEFUCK_NUM_CLOSE_MATCHES` – the maximum number of close matches to suggest, like `5`.
|
||||
* `THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES` – path prefixes to ignore when searching for commands, by default `[]`.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -450,6 +500,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
|
||||
@@ -469,6 +521,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.
|
||||
@@ -488,6 +542,8 @@ For example:
|
||||
eval $(thefuck --alias --enable-experimental-instant-mode)
|
||||
```
|
||||
|
||||
##### [Back to Contents](#contents)
|
||||
|
||||
## Developing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
@@ -498,14 +554,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)
|
||||
|
||||
23
appveyor.yml
23
appveyor.yml
@@ -1,23 +0,0 @@
|
||||
build: false
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- PYTHON: "C:/Python27"
|
||||
- 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"
|
||||
BIN
example.gif
BIN
example.gif
Binary file not shown.
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 631 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 535 KiB After Width: | Height: | Size: 457 KiB |
2
scripts/fuck.bat
Normal file
2
scripts/fuck.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@set PYTHONIOENCODING=utf-8
|
||||
@powershell -noprofile -c "cmd /c \"$(thefuck %* $(doskey /history)[-2])\"; [Console]::ResetColor();"
|
||||
22
scripts/fuck.ps1
Normal file
22
scripts/fuck.ps1
Normal file
@@ -0,0 +1,22 @@
|
||||
if ((Get-Command "fuck").CommandType -eq "Function") {
|
||||
fuck @args;
|
||||
[Console]::ResetColor()
|
||||
exit
|
||||
}
|
||||
|
||||
"First time use of thefuck detected. "
|
||||
|
||||
if ((Get-Content $PROFILE -Raw -ErrorAction Ignore) -like "*thefuck*") {
|
||||
} else {
|
||||
" - Adding thefuck intialization to user `$PROFILE"
|
||||
$script = "`n`$env:PYTHONIOENCODING='utf-8' `niex `"`$(thefuck --alias)`"";
|
||||
Write-Output $script | Add-Content $PROFILE
|
||||
}
|
||||
|
||||
" - Adding fuck() function to current session..."
|
||||
$env:PYTHONIOENCODING='utf-8'
|
||||
iex "$($(thefuck --alias).Replace("function fuck", "function global:fuck"))"
|
||||
|
||||
" - Invoking fuck()`n"
|
||||
fuck @args;
|
||||
[Console]::ResetColor()
|
||||
23
setup.py
23
setup.py
@@ -31,13 +31,26 @@ elif (3, 0) < version < (3, 5):
|
||||
' ({}.{} detected).'.format(*version))
|
||||
sys.exit(-1)
|
||||
|
||||
VERSION = '3.30'
|
||||
VERSION = '3.32'
|
||||
|
||||
install_requires = ['psutil', 'colorama', 'six', 'decorator', 'pyte']
|
||||
install_requires = ['psutil', 'colorama', 'six']
|
||||
extras_require = {':python_version<"3.4"': ['pathlib2'],
|
||||
':python_version<"3.3"': ['backports.shutil_get_terminal_size'],
|
||||
':python_version<="2.7"': ['decorator<5', 'pyte<0.8.1'],
|
||||
':python_version>"2.7"': ['decorator', 'pyte'],
|
||||
":sys_platform=='win32'": ['win_unicode_console']}
|
||||
|
||||
if sys.platform == "win32":
|
||||
scripts = ['scripts\\fuck.bat', 'scripts\\fuck.ps1']
|
||||
entry_points = {'console_scripts': [
|
||||
'thefuck = thefuck.entrypoints.main:main',
|
||||
'thefuck_firstuse = thefuck.entrypoints.not_configured:main']}
|
||||
else:
|
||||
scripts = []
|
||||
entry_points = {'console_scripts': [
|
||||
'thefuck = thefuck.entrypoints.main:main',
|
||||
'fuck = thefuck.entrypoints.not_configured:main']}
|
||||
|
||||
setup(name='thefuck',
|
||||
version=VERSION,
|
||||
description="Magnificent app which corrects your previous console command",
|
||||
@@ -50,8 +63,8 @@ setup(name='thefuck',
|
||||
'tests', 'tests.*', 'release']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
entry_points={'console_scripts': [
|
||||
'thefuck = thefuck.entrypoints.main:main',
|
||||
'fuck = thefuck.entrypoints.not_configured:main']})
|
||||
scripts=scripts,
|
||||
entry_points=entry_points)
|
||||
|
||||
7
tests/Dockerfile
Normal file
7
tests/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
ARG PYTHON_VERSION
|
||||
FROM python:${PYTHON_VERSION}
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -yy --no-install-recommends --no-install-suggests fish tcsh zsh
|
||||
RUN pip install --upgrade pip
|
||||
COPY . /src
|
||||
RUN pip install /src
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
20
tests/functional/conftest.py
Normal file
20
tests/functional/conftest.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from pytest_docker_pexpect.docker import run as pexpect_docker_run, \
|
||||
stats as pexpect_docker_stats
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def build_container_mock(mocker):
|
||||
return mocker.patch('pytest_docker_pexpect.docker.build_container')
|
||||
|
||||
|
||||
def run_side_effect(*args, **kwargs):
|
||||
container_id = pexpect_docker_run(*args, **kwargs)
|
||||
pexpect_docker_stats(container_id)
|
||||
return container_id
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def run_mock(mocker):
|
||||
return mocker.patch('pytest_docker_pexpect.docker.run', side_effect=run_side_effect)
|
||||
@@ -20,10 +20,12 @@ def with_confirmation(proc, TIMEOUT):
|
||||
assert proc.expect([TIMEOUT, u'test'])
|
||||
|
||||
|
||||
def history_changed(proc, TIMEOUT, to):
|
||||
def history_changed(proc, TIMEOUT, *to):
|
||||
"""Ensures that history changed."""
|
||||
proc.send('\033[A')
|
||||
assert proc.expect([TIMEOUT, to])
|
||||
pattern = [TIMEOUT]
|
||||
pattern.extend(to)
|
||||
assert proc.expect(pattern)
|
||||
|
||||
|
||||
def history_not_changed(proc, TIMEOUT):
|
||||
@@ -44,14 +46,14 @@ def select_command_with_arrows(proc, TIMEOUT):
|
||||
proc.send('\033[B')
|
||||
assert proc.expect([TIMEOUT, u'git push'])
|
||||
proc.send('\033[B')
|
||||
assert proc.expect([TIMEOUT, u'git help'])
|
||||
assert proc.expect([TIMEOUT, u'git help', u'git hook'])
|
||||
proc.send('\033[A')
|
||||
assert proc.expect([TIMEOUT, u'git push'])
|
||||
proc.send('\033[B')
|
||||
assert proc.expect([TIMEOUT, u'git help'])
|
||||
assert proc.expect([TIMEOUT, u'git help', u'git hook'])
|
||||
proc.send('\n')
|
||||
|
||||
assert proc.expect([TIMEOUT, u'usage'])
|
||||
assert proc.expect([TIMEOUT, u'usage', u'fatal: not a git repository'])
|
||||
|
||||
|
||||
def refuse_with_confirmation(proc, TIMEOUT):
|
||||
|
||||
@@ -4,12 +4,12 @@ from tests.functional.plots import with_confirmation, without_confirmation, \
|
||||
select_command_with_arrows, how_to_configure
|
||||
|
||||
|
||||
python_3 = (u'thefuck/python3-bash',
|
||||
u'FROM python:3',
|
||||
python_3 = (u'thefuck/python3',
|
||||
u'',
|
||||
u'sh')
|
||||
|
||||
python_2 = (u'thefuck/python2-bash',
|
||||
u'FROM python:2',
|
||||
python_2 = (u'thefuck/python2',
|
||||
u'',
|
||||
u'sh')
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@ echo "instant mode ready: $THEFUCK_INSTANT_MODE"
|
||||
def proc(request, spawnu, TIMEOUT):
|
||||
container, instant_mode = request.param
|
||||
proc = spawnu(*container)
|
||||
proc.sendline(u"pip install /src")
|
||||
assert proc.expect([TIMEOUT, u'Successfully installed'])
|
||||
proc.sendline(init_bashrc.format(
|
||||
u'--enable-experimental-instant-mode' if instant_mode else ''))
|
||||
proc.sendline(u"bash")
|
||||
@@ -47,7 +45,7 @@ def test_with_confirmation(proc, TIMEOUT):
|
||||
@pytest.mark.functional
|
||||
def test_select_command_with_arrows(proc, TIMEOUT):
|
||||
select_command_with_arrows(proc, TIMEOUT)
|
||||
history_changed(proc, TIMEOUT, u'git help')
|
||||
history_changed(proc, TIMEOUT, u'git help', u'git hook')
|
||||
|
||||
|
||||
@pytest.mark.functional
|
||||
|
||||
@@ -2,29 +2,13 @@ import pytest
|
||||
from tests.functional.plots import with_confirmation, without_confirmation, \
|
||||
refuse_with_confirmation, select_command_with_arrows
|
||||
|
||||
containers = (('thefuck/python3-fish',
|
||||
u'''FROM python:3
|
||||
# Use jessie-backports since it has the fish package. See here for details:
|
||||
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
|
||||
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy fish''',
|
||||
u'fish'),
|
||||
('thefuck/python2-fish',
|
||||
u'''FROM python:2
|
||||
# Use jessie-backports since it has the fish package. See here for details:
|
||||
# https://github.com/tianon/docker-brew-debian/blob/88ae21052affd8a14553bb969f9d41c464032122/jessie/backports/Dockerfile
|
||||
RUN awk '$1 ~ "^deb" { $3 = $3 "-backports"; print; exit }' /etc/apt/sources.list > /etc/apt/sources.list.d/backports.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy fish''',
|
||||
u'fish'))
|
||||
containers = ((u'thefuck/python3', u'', u'fish'),
|
||||
(u'thefuck/python2', u'', u'fish'))
|
||||
|
||||
|
||||
@pytest.fixture(params=containers)
|
||||
def proc(request, spawnu, TIMEOUT):
|
||||
proc = spawnu(*request.param)
|
||||
proc.sendline(u"pip install /src")
|
||||
assert proc.expect([TIMEOUT, u'Successfully installed'])
|
||||
proc.sendline(u'thefuck --alias > ~/.config/fish/config.fish')
|
||||
proc.sendline(u'fish')
|
||||
return proc
|
||||
|
||||
@@ -2,23 +2,13 @@ import pytest
|
||||
from tests.functional.plots import with_confirmation, without_confirmation, \
|
||||
refuse_with_confirmation, select_command_with_arrows
|
||||
|
||||
containers = (('thefuck/python3-tcsh',
|
||||
u'''FROM python:3
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy tcsh''',
|
||||
u'tcsh'),
|
||||
('thefuck/python2-tcsh',
|
||||
u'''FROM python:2
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy tcsh''',
|
||||
u'tcsh'))
|
||||
containers = ((u'thefuck/python3', u'', u'tcsh'),
|
||||
(u'thefuck/python2', u'', u'tcsh'))
|
||||
|
||||
|
||||
@pytest.fixture(params=containers)
|
||||
def proc(request, spawnu, TIMEOUT):
|
||||
proc = spawnu(*request.param)
|
||||
proc.sendline(u'pip install /src')
|
||||
assert proc.expect([TIMEOUT, u'Successfully installed'])
|
||||
proc.sendline(u'tcsh')
|
||||
proc.sendline(u'setenv PYTHONIOENCODING utf8')
|
||||
proc.sendline(u'eval `thefuck --alias`')
|
||||
|
||||
@@ -4,17 +4,8 @@ from tests.functional.plots import with_confirmation, without_confirmation, \
|
||||
select_command_with_arrows, how_to_configure
|
||||
|
||||
|
||||
python_3 = ('thefuck/python3-zsh',
|
||||
u'''FROM python:3
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy zsh''',
|
||||
u'sh')
|
||||
|
||||
python_2 = ('thefuck/python2-zsh',
|
||||
u'''FROM python:2
|
||||
RUN apt-get update
|
||||
RUN apt-get install -yy zsh''',
|
||||
u'sh')
|
||||
python_3 = (u'thefuck/python3', u'', u'sh')
|
||||
python_2 = (u'thefuck/python2', u'', u'sh')
|
||||
|
||||
|
||||
init_zshrc = u'''echo '
|
||||
@@ -35,8 +26,6 @@ echo "instant mode ready: $THEFUCK_INSTANT_MODE"
|
||||
def proc(request, spawnu, TIMEOUT):
|
||||
container, instant_mode = request.param
|
||||
proc = spawnu(*container)
|
||||
proc.sendline(u'pip install /src')
|
||||
assert proc.expect([TIMEOUT, u'Successfully installed'])
|
||||
proc.sendline(init_zshrc.format(
|
||||
u'--enable-experimental-instant-mode' if instant_mode else ''))
|
||||
proc.sendline(u"zsh")
|
||||
@@ -54,7 +43,7 @@ def test_with_confirmation(proc, TIMEOUT):
|
||||
@pytest.mark.functional
|
||||
def test_select_command_with_arrows(proc, TIMEOUT):
|
||||
select_command_with_arrows(proc, TIMEOUT)
|
||||
history_changed(proc, TIMEOUT, u'git help')
|
||||
history_changed(proc, TIMEOUT, u'git help', u'git hook')
|
||||
|
||||
|
||||
@pytest.mark.functional
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
from mock import Mock, patch
|
||||
from psutil import AccessDenied, TimeoutExpired
|
||||
|
||||
@@ -22,6 +24,20 @@ 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
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32', reason="skip when running on Windows")
|
||||
@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)
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import pytest
|
||||
from thefuck.rules.brew_install import match, get_new_command
|
||||
from thefuck.rules.brew_install import _get_formulas
|
||||
from thefuck.rules.brew_install import match, get_new_command, _get_suggestions
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def brew_no_available_formula():
|
||||
return '''Error: No available formula for elsticsearch '''
|
||||
def brew_no_available_formula_one():
|
||||
return '''Warning: No available formula with the name "giss". Did you mean gist?'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def brew_no_available_formula_two():
|
||||
return '''Warning: No available formula with the name "elasticserar". Did you mean elasticsearch or elasticsearch@6?'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def brew_no_available_formula_three():
|
||||
return '''Warning: No available formula with the name "gitt". Did you mean git, gitg or gist?'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def brew_install_no_argument():
|
||||
return '''This command requires a formula argument'''
|
||||
return '''Install a formula or cask. Additional options specific to a formula may be'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -19,28 +28,38 @@ def brew_already_installed():
|
||||
return '''Warning: git-2.3.5 already installed'''
|
||||
|
||||
|
||||
def _is_not_okay_to_test():
|
||||
return 'elasticsearch' not in _get_formulas()
|
||||
def test_suggestions():
|
||||
assert _get_suggestions("one") == ['one']
|
||||
assert _get_suggestions("one or two") == ['one', 'two']
|
||||
assert _get_suggestions("one, two or three") == ['one', 'two', 'three']
|
||||
|
||||
|
||||
@pytest.mark.skipif(_is_not_okay_to_test(),
|
||||
reason='No need to run if there\'s no formula')
|
||||
def test_match(brew_no_available_formula, brew_already_installed,
|
||||
def test_match(brew_no_available_formula_one, brew_no_available_formula_two,
|
||||
brew_no_available_formula_three, brew_already_installed,
|
||||
brew_install_no_argument):
|
||||
assert match(Command('brew install elsticsearch',
|
||||
brew_no_available_formula))
|
||||
assert match(Command('brew install giss',
|
||||
brew_no_available_formula_one))
|
||||
assert match(Command('brew install elasticserar',
|
||||
brew_no_available_formula_two))
|
||||
assert match(Command('brew install gitt',
|
||||
brew_no_available_formula_three))
|
||||
assert not match(Command('brew install git',
|
||||
brew_already_installed))
|
||||
assert not match(Command('brew install', brew_install_no_argument))
|
||||
|
||||
|
||||
@pytest.mark.skipif(_is_not_okay_to_test(),
|
||||
reason='No need to run if there\'s no formula')
|
||||
def test_get_new_command(brew_no_available_formula):
|
||||
assert get_new_command(Command('brew install elsticsearch',
|
||||
brew_no_available_formula))\
|
||||
== 'brew install elasticsearch'
|
||||
def test_get_new_command(brew_no_available_formula_one, brew_no_available_formula_two,
|
||||
brew_no_available_formula_three):
|
||||
assert get_new_command(Command('brew install giss',
|
||||
brew_no_available_formula_one))\
|
||||
== ['brew install gist']
|
||||
assert get_new_command(Command('brew install elasticsear',
|
||||
brew_no_available_formula_two))\
|
||||
== ['brew install elasticsearch', 'brew install elasticsearch@6']
|
||||
assert get_new_command(Command('brew install gitt',
|
||||
brew_no_available_formula_three))\
|
||||
== ['brew install git', 'brew install gitg', 'brew install gist']
|
||||
|
||||
assert get_new_command(Command('brew install aa',
|
||||
brew_no_available_formula))\
|
||||
brew_no_available_formula_one))\
|
||||
!= 'brew install aha'
|
||||
|
||||
@@ -4,7 +4,7 @@ from thefuck.rules.brew_update_formula import get_new_command, match
|
||||
|
||||
|
||||
output = ("Error: This command updates brew itself, and does not take formula"
|
||||
" names.\nUse 'brew upgrade thefuck'.")
|
||||
" names.\nUse `brew upgrade thefuck`.")
|
||||
|
||||
|
||||
def test_match():
|
||||
|
||||
11
tests/rules/test_cd_cs.py
Normal file
11
tests/rules/test_cd_cs.py
Normal file
@@ -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/'
|
||||
@@ -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')
|
||||
|
||||
24
tests/rules/test_conda_mistype.py
Normal file
24
tests/rules/test_conda_mistype.py
Normal file
@@ -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'])
|
||||
@@ -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('pytest', '/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))
|
||||
|
||||
70
tests/rules/test_git_branch_0flag.py
Normal file
70
tests/rules/test_git_branch_0flag.py
Normal file
@@ -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
|
||||
24
tests/rules/test_git_clone_git_clone.py
Normal file
24
tests/rules/test_git_clone_git_clone.py
Normal file
@@ -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'
|
||||
50
tests/rules/test_git_clone_missing.py
Normal file
50
tests/rules/test_git_clone_missing.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import pytest
|
||||
from thefuck.rules.git_clone_missing import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
valid_urls = [
|
||||
'https://github.com/nvbn/thefuck.git',
|
||||
'https://github.com/nvbn/thefuck',
|
||||
'http://github.com/nvbn/thefuck.git',
|
||||
'git@github.com:nvbn/thefuck.git',
|
||||
'git@github.com:nvbn/thefuck',
|
||||
'ssh://git@github.com:nvbn/thefuck.git',
|
||||
]
|
||||
invalid_urls = [
|
||||
'', # No command
|
||||
'notacommand', # Command not found
|
||||
'ssh git@github.com:nvbn/thefrick.git', # ssh command, not a git clone
|
||||
'git clone foo', # Valid clone
|
||||
'git clone https://github.com/nvbn/thefuck.git', # Full command
|
||||
'github.com/nvbn/thefuck.git', # Missing protocol
|
||||
'github.com:nvbn/thefuck.git', # SSH missing username
|
||||
'git clone git clone ssh://git@github.com:nvbn/thefrick.git', # 2x clone
|
||||
'https:/github.com/nvbn/thefuck.git' # Bad protocol
|
||||
]
|
||||
outputs = [
|
||||
'No such file or directory',
|
||||
'not found',
|
||||
'is not recognised as',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd', valid_urls)
|
||||
@pytest.mark.parametrize('output', outputs)
|
||||
def test_match(cmd, output):
|
||||
c = Command(cmd, output)
|
||||
assert match(c)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('cmd', invalid_urls)
|
||||
@pytest.mark.parametrize('output', outputs + ["some other output"])
|
||||
def test_not_match(cmd, output):
|
||||
c = Command(cmd, output)
|
||||
assert not match(c)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script', valid_urls)
|
||||
@pytest.mark.parametrize('output', outputs)
|
||||
def test_get_new_command(script, output):
|
||||
command = Command(script, output)
|
||||
new_command = 'git clone ' + script
|
||||
assert get_new_command(command) == new_command
|
||||
38
tests/rules/test_git_commit_add.py
Normal file
38
tests/rules/test_git_commit_add.py
Normal file
@@ -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
|
||||
43
tests/rules/test_git_hook_bypass.py
Normal file
43
tests/rules/test_git_hook_bypass.py
Normal file
@@ -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
|
||||
29
tests/rules/test_git_lfs_mistype.py
Normal file
29
tests/rules/test_git_lfs_mistype.py
Normal file
@@ -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'])
|
||||
47
tests/rules/test_git_main_master.py
Normal file
47
tests/rules/test_git_main_master.py
Normal file
@@ -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
|
||||
@@ -1,27 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from thefuck.types import Command
|
||||
from thefuck.rules.git_push_without_commits import (
|
||||
fix,
|
||||
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'
|
||||
'''
|
||||
from thefuck.rules.git_push_without_commits import get_new_command, match
|
||||
|
||||
|
||||
@pytest.mark.parametrize('command', [Command(command, expected_error)])
|
||||
def test_match(command):
|
||||
assert match(command)
|
||||
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, result', [(
|
||||
Command(command, expected_error),
|
||||
fix.format(command=command),
|
||||
)])
|
||||
def test_get_new_command(command, result):
|
||||
assert get_new_command(command) == result
|
||||
def test_not_match():
|
||||
script = "git push -u origin master"
|
||||
assert not match(Command(script, "Everything up-to-date"))
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
from io import BytesIO
|
||||
from thefuck.rules.go_unknown_command import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
@@ -9,6 +10,65 @@ def build_misspelled_output():
|
||||
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))
|
||||
|
||||
@@ -17,5 +77,6 @@ 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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,7 +21,8 @@ def history_without_current(mocker):
|
||||
('vom file.py', 'vom: not found'),
|
||||
('fucck', 'fucck: not found'),
|
||||
('puthon', "'puthon' is not recognized as an internal or external command"),
|
||||
('got commit', 'got: command not found')])
|
||||
('got commit', 'got: command not found'),
|
||||
('gti commit -m "new commit"', 'gti: command not found')])
|
||||
def test_match(mocker, script, output):
|
||||
mocker.patch('thefuck.rules.no_command.which', return_value=None)
|
||||
|
||||
@@ -43,6 +44,7 @@ def test_not_match(mocker, script, output, which):
|
||||
@pytest.mark.parametrize('script, result', [
|
||||
('vom file.py', ['vim file.py']),
|
||||
('fucck', ['fsck']),
|
||||
('got commit', ['git commit', 'go commit'])])
|
||||
('got commit', ['git commit', 'go commit']),
|
||||
('gti commit -m "new commit"', ['git commit -m "new commit"'])])
|
||||
def test_get_new_command(script, result):
|
||||
assert get_new_command(Command(script, '')) == result
|
||||
|
||||
@@ -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'),
|
||||
30
tests/rules/test_pacman_invalid_option.py
Normal file
30
tests/rules/test_pacman_invalid_option.py
Normal file
@@ -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())
|
||||
@@ -12,6 +12,7 @@ extra/llvm35 3.5.2-13/usr/bin/llc'''
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yay -S llc', 'error: target not found: llc'),
|
||||
Command('pikaur -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@@ -21,6 +22,7 @@ def test_match(command):
|
||||
|
||||
@pytest.mark.parametrize('command', [
|
||||
Command('yay -S llc', 'error: target not found: llc'),
|
||||
Command('pikaur -S llc', 'error: target not found: llc'),
|
||||
Command('yaourt -S llc', 'error: target not found: llc'),
|
||||
Command('pacman llc', 'error: target not found: llc'),
|
||||
Command('sudo pacman llc', 'error: target not found: llc')])
|
||||
@@ -34,6 +36,7 @@ def test_match_mocked(subp_mock, command):
|
||||
reason='Skip if pacman is not available')
|
||||
@pytest.mark.parametrize('command, fixed', [
|
||||
(Command('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('pikaur -S llc', 'error: target not found: llc'), ['pikaur -S extra/llvm', 'pikaur -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
@@ -43,6 +46,7 @@ def test_get_new_command(command, fixed):
|
||||
|
||||
@pytest.mark.parametrize('command, fixed', [
|
||||
(Command('yay -S llc', 'error: target not found: llc'), ['yay -S extra/llvm', 'yay -S extra/llvm35']),
|
||||
(Command('pikaur -S llc', 'error: target not found: llc'), ['pikaur -S extra/llvm', 'pikaur -S extra/llvm35']),
|
||||
(Command('yaourt -S llc', 'error: target not found: llc'), ['yaourt -S extra/llvm', 'yaourt -S extra/llvm35']),
|
||||
(Command('pacman -S llc', 'error: target not found: llc'), ['pacman -S extra/llvm', 'pacman -S extra/llvm35']),
|
||||
(Command('sudo pacman -S llc', 'error: target not found: llc'), ['sudo pacman -S extra/llvm', 'sudo pacman -S extra/llvm35'])])
|
||||
|
||||
63
tests/rules/test_python_module_error.py
Normal file
63
tests/rules/test_python_module_error.py
Normal file
@@ -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
|
||||
46
tests/rules/test_rails_migrations_pending.py
Normal file
46
tests/rules/test_rails_migrations_pending.py
Normal file
@@ -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
|
||||
@@ -8,7 +8,15 @@ def output():
|
||||
return "$: command not found"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("script", ["$ cd newdir", " $ cd newdir"])
|
||||
@pytest.mark.parametrize(
|
||||
"script",
|
||||
[
|
||||
"$ cd newdir",
|
||||
" $ cd newdir",
|
||||
"$ $ cd newdir",
|
||||
" $ $ cd newdir",
|
||||
],
|
||||
)
|
||||
def test_match(script, output):
|
||||
assert match(Command(script, output))
|
||||
|
||||
@@ -31,7 +39,9 @@ def test_not_match(command):
|
||||
"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):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
import pytest
|
||||
from thefuck.rules.ssh_known_hosts import match, get_new_command,\
|
||||
from thefuck.rules.ssh_known_hosts import match, get_new_command, \
|
||||
side_effect
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ from thefuck.types import Command
|
||||
'requested operation requires superuser privilege',
|
||||
'need to be root',
|
||||
'need root',
|
||||
'shutdown: NOT super-user',
|
||||
'Error: This command has to be run with superuser privileges (under the root user on most systems).',
|
||||
'updatedb: can not open a temporary file for `/var/lib/mlocate/mlocate.db',
|
||||
'must be root',
|
||||
'You don\'t have access to the history DB.',
|
||||
"error: [Errno 13] Permission denied: '/usr/local/lib/python2.7/dist-packages/ipaddr.py'"])
|
||||
|
||||
27
tests/rules/test_terraform_no_command.py
Normal file
27
tests/rules/test_terraform_no_command.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
from thefuck.rules.terraform_no_command import match, get_new_command
|
||||
from thefuck.types import Command
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output', [
|
||||
('terraform appyl', 'Terraform has no command named "appyl". Did you mean "apply"?'),
|
||||
('terraform destory', 'Terraform has no command named "destory". Did you mean "destroy"?')])
|
||||
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))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('script, output, new_command', [
|
||||
('terraform appyl', 'Terraform has no command named "appyl". Did you mean "apply"?', 'terraform apply',),
|
||||
('terraform destory --some-other-option', 'Terraform has no command named "destory". Did you mean "destroy"?', 'terraform destroy --some-other-option',),
|
||||
])
|
||||
def test_get_new_command(script, output, new_command):
|
||||
assert get_new_command(Command(script, output)) == new_command
|
||||
30
tests/rules/test_wrong_hyphen_before_subcommand.py
Normal file
30
tests/rules/test_wrong_hyphen_before_subcommand.py
Normal file
@@ -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
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -8,5 +8,5 @@ def test_readme(source_root):
|
||||
|
||||
for rule in bundled:
|
||||
if rule.stem != '__init__':
|
||||
assert rule.stem in readme,\
|
||||
assert rule.stem in readme, \
|
||||
'Missing rule "{}" in README.md'.format(rule.stem)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -227,7 +245,7 @@ class TestGetValidHistoryWithoutCurrent(object):
|
||||
def history(self, mocker):
|
||||
mock = mocker.patch('thefuck.shells.shell.get_history')
|
||||
# Passing as an argument causes `UnicodeDecodeError`
|
||||
# with newer py.test and python 2.7
|
||||
# with newer pytest and python 2.7
|
||||
mock.return_value = ['le cat', 'fuck', 'ls cat',
|
||||
'diff x', 'nocommand x', u'café ô']
|
||||
return mock
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from imp import load_source
|
||||
import os
|
||||
import sys
|
||||
from warnings import warn
|
||||
@@ -6,6 +5,17 @@ from six import text_type
|
||||
from . import const
|
||||
from .system import Path
|
||||
|
||||
try:
|
||||
import importlib.util
|
||||
|
||||
def load_source(name, pathname, _file=None):
|
||||
module_spec = importlib.util.spec_from_file_location(name, pathname)
|
||||
module = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(module)
|
||||
return module
|
||||
except ImportError:
|
||||
from imp import load_source
|
||||
|
||||
|
||||
class Settings(dict):
|
||||
def __getattr__(self, item):
|
||||
@@ -101,7 +111,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
|
||||
|
||||
@@ -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
|
||||
#
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,7 +20,7 @@ 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())
|
||||
# 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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,6 +54,9 @@ def get_output(script, expanded):
|
||||
env = dict(os.environ)
|
||||
env.update(settings.env)
|
||||
|
||||
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(
|
||||
@@ -60,7 +64,7 @@ def get_output(script, expanded):
|
||||
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:
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
import os
|
||||
import re
|
||||
from thefuck.utils import get_closest, replace_argument
|
||||
from thefuck.specific.brew import get_brew_path_prefix, brew_available
|
||||
from thefuck.utils import for_app
|
||||
from thefuck.specific.brew import brew_available
|
||||
|
||||
enabled_by_default = brew_available
|
||||
|
||||
|
||||
def _get_formulas():
|
||||
# Formulas are based on each local system's status
|
||||
try:
|
||||
brew_path_prefix = get_brew_path_prefix()
|
||||
brew_formula_path = brew_path_prefix + '/Library/Formula'
|
||||
|
||||
for file_name in os.listdir(brew_formula_path):
|
||||
if file_name.endswith('.rb'):
|
||||
yield file_name[:-3]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _get_similar_formula(formula_name):
|
||||
return get_closest(formula_name, _get_formulas(), cutoff=0.85)
|
||||
def _get_suggestions(str):
|
||||
suggestions = str.replace(" or ", ", ").split(", ")
|
||||
return suggestions
|
||||
|
||||
|
||||
@for_app('brew', at_least=2)
|
||||
def match(command):
|
||||
is_proper_command = ('brew install' in command.script and
|
||||
'No available formula' in command.output)
|
||||
|
||||
if is_proper_command:
|
||||
formula = re.findall(r'Error: No available formula for ([a-z]+)',
|
||||
command.output)[0]
|
||||
return bool(_get_similar_formula(formula))
|
||||
return False
|
||||
is_proper_command = ('install' in command.script and
|
||||
'No available formula' in command.output and
|
||||
'Did you mean' in command.output)
|
||||
return is_proper_command
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
not_exist_formula = re.findall(r'Error: No available formula for ([a-z]+)',
|
||||
command.output)[0]
|
||||
exist_formula = _get_similar_formula(not_exist_formula)
|
||||
|
||||
return replace_argument(command.script, not_exist_formula, exist_formula)
|
||||
matcher = re.search('Warning: No available formula with the name "(?:[^"]+)". Did you mean (.+)\\?', command.output)
|
||||
suggestions = _get_suggestions(matcher.group(1))
|
||||
return ["brew install " + formula for formula in suggestions]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -5,7 +5,7 @@ from thefuck.utils import for_app
|
||||
def match(command):
|
||||
return ('update' in command.script
|
||||
and "Error: This command updates brew itself" in command.output
|
||||
and "Use 'brew upgrade" in command.output)
|
||||
and "Use `brew upgrade" in command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
|
||||
21
thefuck/rules/cd_cs.py
Normal file
21
thefuck/rules/cd_cs.py
Normal file
@@ -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
|
||||
@@ -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)
|
||||
|
||||
17
thefuck/rules/conda_mistype.py
Normal file
17
thefuck/rules/conda_mistype.py
Normal file
@@ -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])
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
24
thefuck/rules/git_branch_0flag.py
Normal file
24
thefuck/rules/git_branch_0flag.py
Normal file
@@ -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
|
||||
12
thefuck/rules/git_clone_git_clone.py
Normal file
12
thefuck/rules/git_clone_git_clone.py
Normal file
@@ -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)
|
||||
42
thefuck/rules/git_clone_missing.py
Normal file
42
thefuck/rules/git_clone_missing.py
Normal file
@@ -0,0 +1,42 @@
|
||||
'''
|
||||
Rule: git_clone_missing
|
||||
|
||||
Correct missing `git clone` command when pasting a git URL
|
||||
|
||||
```sh
|
||||
>>> https://github.com/nvbn/thefuck.git
|
||||
git clone https://github.com/nvbn/thefuck.git
|
||||
```
|
||||
|
||||
Author: Miguel Guthridge
|
||||
'''
|
||||
from six.moves.urllib import parse
|
||||
from thefuck.utils import which
|
||||
|
||||
|
||||
def match(command):
|
||||
# We want it to be a URL by itself
|
||||
if len(command.script_parts) != 1:
|
||||
return False
|
||||
# Ensure we got the error we expected
|
||||
if which(command.script_parts[0]) or not (
|
||||
'No such file or directory' in command.output
|
||||
or 'not found' in command.output
|
||||
or 'is not recognised as' in command.output
|
||||
):
|
||||
return False
|
||||
url = parse.urlparse(command.script, scheme='ssh')
|
||||
# HTTP URLs need a network address
|
||||
if not url.netloc and url.scheme != 'ssh':
|
||||
return False
|
||||
# SSH needs a username and a splitter between the path
|
||||
if url.scheme == 'ssh' and not (
|
||||
'@' in command.script
|
||||
and ':' in command.script
|
||||
):
|
||||
return False
|
||||
return url.scheme in ['http', 'https', 'ssh']
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return 'git clone ' + command.script
|
||||
17
thefuck/rules/git_commit_add.py
Normal file
17
thefuck/rules/git_commit_add.py
Normal file
@@ -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))
|
||||
27
thefuck/rules/git_hook_bypass.py
Normal file
27
thefuck/rules/git_hook_bypass.py
Normal file
@@ -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
|
||||
18
thefuck/rules/git_lfs_mistype.py
Normal file
18
thefuck/rules/git_lfs_mistype.py
Normal file
@@ -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)
|
||||
16
thefuck/rules/git_main_master.py
Normal file
16
thefuck/rules/git_main_master.py
Normal file
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -14,7 +14,7 @@ def get_golang_commands():
|
||||
|
||||
|
||||
if which('go'):
|
||||
get_docker_commands = cache(which('go'))(get_golang_commands)
|
||||
get_golang_commands = cache(which('go'))(get_golang_commands)
|
||||
|
||||
|
||||
@for_app('go')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ def get_new_command(command):
|
||||
get_all_executables())
|
||||
if cmd not in new_cmds]
|
||||
|
||||
return [' '.join([new_command] + command.script_parts[1:])
|
||||
for new_command in new_cmds]
|
||||
return [command.script.replace(old_command, cmd, 1) for cmd in new_cmds]
|
||||
|
||||
|
||||
priority = 3000
|
||||
|
||||
@@ -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
|
||||
20
thefuck/rules/pacman_invalid_option.py
Normal file
20
thefuck/rules/pacman_invalid_option.py
Normal file
@@ -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()
|
||||
@@ -12,7 +12,7 @@ from thefuck.specific.archlinux import get_pkgfile, archlinux_env
|
||||
|
||||
def match(command):
|
||||
return (command.script_parts
|
||||
and (command.script_parts[0] in ('pacman', 'yay', 'yaourt')
|
||||
and (command.script_parts[0] in ('pacman', 'yay', 'pikaur', 'yaourt')
|
||||
or command.script_parts[0:2] == ['sudo', 'pacman'])
|
||||
and 'error: target not found:' in command.output)
|
||||
|
||||
|
||||
13
thefuck/rules/python_module_error.py
Normal file
13
thefuck/rules/python_module_error.py
Normal file
@@ -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)
|
||||
14
thefuck/rules/rails_migrations_pending.py
Normal file
14
thefuck/rules/rails_migrations_pending.py
Normal file
@@ -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)
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Fixes error for commands containing the shell prompt symbol '$'.
|
||||
"""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.
|
||||
@@ -19,4 +20,4 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return command.script.replace("$", "", 1).strip()
|
||||
return command.script.lstrip("$ ")
|
||||
|
||||
@@ -4,6 +4,8 @@ patterns = ['permission denied',
|
||||
'you cannot perform this operation unless you are root',
|
||||
'non-root users cannot',
|
||||
'operation not permitted',
|
||||
'not super-user',
|
||||
'superuser privilege',
|
||||
'root privilege',
|
||||
'this command has to be run under the root user.',
|
||||
'this operation requires root.',
|
||||
@@ -22,7 +24,8 @@ patterns = ['permission denied',
|
||||
'you don\'t have write permissions',
|
||||
'use `sudo`',
|
||||
'sudorequirederror',
|
||||
'error: insufficient privileges']
|
||||
'error: insufficient privileges',
|
||||
'updatedb: can not open a temporary file']
|
||||
|
||||
|
||||
def match(command):
|
||||
|
||||
16
thefuck/rules/terraform_no_command.py
Normal file
16
thefuck/rules/terraform_no_command.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import re
|
||||
from thefuck.utils import for_app
|
||||
|
||||
MISTAKE = r'(?<=Terraform has no command named ")([^"]+)(?="\.)'
|
||||
FIX = r'(?<=Did you mean ")([^"]+)(?="\?)'
|
||||
|
||||
|
||||
@for_app('terraform')
|
||||
def match(command):
|
||||
return re.search(MISTAKE, command.output) and re.search(FIX, command.output)
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
mistake = re.search(MISTAKE, command.output).group(0)
|
||||
fix = re.search(FIX, command.output).group(0)
|
||||
return command.script.replace(mistake, fix)
|
||||
@@ -3,7 +3,7 @@ def match(command):
|
||||
|
||||
|
||||
def get_new_command(command):
|
||||
return 'py.test'
|
||||
return 'pytest'
|
||||
|
||||
|
||||
# make it come before the python_command rule
|
||||
|
||||
20
thefuck/rules/wrong_hyphen_before_subcommand.py
Normal file
20
thefuck/rules/wrong_hyphen_before_subcommand.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -34,6 +34,8 @@ def get_pkgfile(command):
|
||||
def archlinux_env():
|
||||
if utils.which('yay'):
|
||||
pacman = 'yay'
|
||||
elif utils.which('pikaur'):
|
||||
pacman = 'pikaur'
|
||||
elif utils.which('yaourt'):
|
||||
pacman = 'yaourt'
|
||||
elif utils.which('pacman'):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user