1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-03-14 06:38:32 +00:00

Merge branch 'master' of https://github.com/nvbn/thefuck into conda

This commit is contained in:
Connor Martin 2021-02-01 20:14:22 -06:00
commit 9a91f6cc70
14 changed files with 191 additions and 112 deletions

49
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Tests
on: [push, pull_request]
env:
PYTHON_LATEST: 3.9
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10-dev]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Cache dependencies
id: cache-deps
uses: actions/cache@v2
with:
path: |
${{ env.pythonLocation }}/bin/*
${{ env.pythonLocation }}/lib/*
${{ env.pythonLocation }}/scripts/*
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py', 'requirements.txt') }}
- name: Install The Fuck with all dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: |
pip install -Ur requirements.txt coveralls
python setup.py develop
- name: Lint
if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
run: flake8
- name: Run tests
if: matrix.os != 'ubuntu-latest' || matrix.python-version != env.PYTHON_LATEST
run: coverage run --source=thefuck,tests -m pytest -v --capture=sys tests
- name: Run tests (including functional)
if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
run: coverage run --source=thefuck,tests -m pytest -v --capture=sys tests --enable-functional
- name: Post coverage results
if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.PYTHON_LATEST
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: coveralls --service=github

View File

@ -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

View File

@ -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),
@ -282,6 +282,7 @@ following rules are enabled by default:
* `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;
* `react_native_command_unrecognized` – fixes unrecognized `react-native` commands;
@ -503,10 +504,8 @@ 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/divykj/thefuck/workflows/Tests/badge.svg
[workflow-link]: https://github.com/divykj/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

View File

@ -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"

View File

@ -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")

View File

@ -1,6 +1,7 @@
import pytest
from thefuck.rules.go_unknown_command import match, get_new_command
from thefuck.types import Command
from thefuck.utils import which
@pytest.fixture
@ -17,5 +18,6 @@ def test_not_match():
assert not match(Command('go run', 'go run: no go files listed'))
@pytest.mark.skipif(not which('go'), reason='Skip if go executable not found')
def test_get_new_command(build_misspelled_output):
assert get_new_command(Command('go bulid', build_misspelled_output)) == 'go build'

View 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

View File

@ -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',

View File

@ -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),

View File

@ -45,6 +45,12 @@ class TestCorrectedCommand(object):
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,6 +139,7 @@ class TestCommand(object):
env=os_environ)
@pytest.mark.parametrize('script, result', [
([], None),
([''], None),
(['', ''], None),
(['ls', '-la'], 'ls -la'),

View 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

View File

@ -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):

View 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)

View File

@ -136,9 +136,16 @@ class Rule(object):
"""
name = path.name[:-3]
if name in settings.exclude_rules:
logs.debug(u'Ignoring excluded rule: {}'.format(name))
return
with logs.debug_time(u'Importing rule: {};'.format(name)):
rule_module = load_source(name, str(path))
priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
try:
rule_module = load_source(name, str(path))
except Exception:
logs.exception(u"Rule {} failed to load".format(name), sys.exc_info())
return
priority = getattr(rule_module, 'priority', DEFAULT_PRIORITY)
return cls(name, rule_module.match,
rule_module.get_new_command,
getattr(rule_module, 'enabled_by_default', True),
@ -153,14 +160,11 @@ class Rule(object):
:rtype: bool
"""
if self.name in settings.exclude_rules:
return False
elif self.name in settings.rules:
return True
elif self.enabled_by_default and ALL_ENABLED in settings.rules:
return True
else:
return False
return (
self.name in settings.rules
or self.enabled_by_default
and ALL_ENABLED in settings.rules
)
def is_match(self, command):
"""Returns `True` if rule matches the command.