diff --git a/README.md b/README.md index 05478be1..3b3f098f 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `git_stash` – stashes you local modifications before rebasing or switching branch; * `git_two_dashes` – adds a missing dash to commands like `git commit -amend` or `git rebase -continue`; * `go_run` – appends `.go` extension when compiling/running Go programs; +* `gradle_no_task` – fixes not found or ambiguous `gradle` task; * `gradle_wrapper` – replaces `gradle` with `./gradlew`; * `grep_arguments_order` – fixes grep arguments order for situations like `grep -lir . test`; * `grep_recursive` – adds `-r` when you trying to `grep` directory; diff --git a/tests/rules/test_gradle_not_task.py b/tests/rules/test_gradle_not_task.py new file mode 100644 index 00000000..1cf968bc --- /dev/null +++ b/tests/rules/test_gradle_not_task.py @@ -0,0 +1,158 @@ +import pytest +from io import BytesIO +from thefuck.rules.gradle_no_task import match, get_new_command +from tests.utils import Command + +gradle_tasks = b''' +:tasks + +------------------------------------------------------------ +All tasks runnable from root project +------------------------------------------------------------ + +Android tasks +------------- +androidDependencies - Displays the Android dependencies of the project. +signingReport - Displays the signing info for each variant. +sourceSets - Prints out all the source sets defined in this project. + +Build tasks +----------- +assemble - Assembles all variants of all applications and secondary packages. +assembleAndroidTest - Assembles all the Test applications. +assembleDebug - Assembles all Debug builds. +assembleRelease - Assembles all Release builds. +build - Assembles and tests this project. +buildDependents - Assembles and tests this project and all projects that depend on it. +buildNeeded - Assembles and tests this project and all projects it depends on. +compileDebugAndroidTestSources +compileDebugSources +compileDebugUnitTestSources +compileReleaseSources +compileReleaseUnitTestSources +extractDebugAnnotations - Extracts Android annotations for the debug variant into the archive file +extractReleaseAnnotations - Extracts Android annotations for the release variant into the archive file +mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests. + +Build Setup tasks +----------------- +init - Initializes a new Gradle build. [incubating] +wrapper - Generates Gradle wrapper files. [incubating] + +Help tasks +---------- +components - Displays the components produced by root project 'org.rerenderer_example.snake'. [incubating] +dependencies - Displays all dependencies declared in root project 'org.rerenderer_example.snake'. +dependencyInsight - Displays the insight into a specific dependency in root project 'org.rerenderer_example.snake'. +help - Displays a help message. +model - Displays the configuration model of root project 'org.rerenderer_example.snake'. [incubating] +projects - Displays the sub-projects of root project 'org.rerenderer_example.snake'. +properties - Displays the properties of root project 'org.rerenderer_example.snake'. +tasks - Displays the tasks runnable from root project 'org.rerenderer_example.snake' (some of the displayed tasks may belong to subprojects). + +Install tasks +------------- +installDebug - Installs the Debug build. +installDebugAndroidTest - Installs the android (on device) tests for the Debug build. +installRelease - Installs the Release build. +uninstallAll - Uninstall all applications. +uninstallDebug - Uninstalls the Debug build. +uninstallDebugAndroidTest - Uninstalls the android (on device) tests for the Debug build. +uninstallRelease - Uninstalls the Release build. + +React tasks +----------- +bundleDebugJsAndAssets - bundle JS and assets for Debug. +bundleReleaseJsAndAssets - bundle JS and assets for Release. + +Verification tasks +------------------ +check - Runs all checks. +clean - Deletes the build directory. +connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices. +connectedCheck - Runs all device checks on currently connected devices. +connectedDebugAndroidTest - Installs and runs the tests for debug on connected devices. +deviceAndroidTest - Installs and runs instrumentation tests using all Device Providers. +deviceCheck - Runs all device checks using Device Providers and Test Servers. +lint - Runs lint on all variants. +lintDebug - Runs lint on the Debug build. +lintRelease - Runs lint on the Release build. +test - Run unit tests for all variants. +testDebugUnitTest - Run unit tests for the debug build. +testReleaseUnitTest - Run unit tests for the release build. + +Other tasks +----------- +assembleDefault +copyDownloadableDepsToLibs +jarDebugClasses +jarReleaseClasses + +To see all tasks and more detail, run gradlew tasks --all + +To see more detail about a task, run gradlew help --task <task> + +BUILD SUCCESSFUL + +Total time: 1.936 secs +''' + +stderr_not_found = ''' + +FAILURE: Build failed with an exception. + +* What went wrong: +Task '{}' not found in root project 'org.rerenderer_example.snake'. + +* Try: +Run gradlew tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. +'''.format + +stderr_ambiguous = ''' + +FAILURE: Build failed with an exception. + +* What went wrong: +Task '{}' is ambiguous in root project 'org.rerenderer_example.snake'. Candidates are: 'assembleRelease', 'assembleReleaseUnitTest'. + +* Try: +Run gradlew tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. +'''.format + + +@pytest.fixture(autouse=True) +def tasks(mocker): + patch = mocker.patch('thefuck.rules.gradle_no_task.Popen') + patch.return_value.stdout = BytesIO(gradle_tasks) + return patch + + +@pytest.mark.parametrize('command', [ + Command('./gradlew assembler', stderr=stderr_ambiguous('assembler')), + Command('./gradlew instar', stderr=stderr_not_found('instar')), + Command('gradle assembler', stderr=stderr_ambiguous('assembler')), + Command('gradle instar', stderr=stderr_not_found('instar'))]) +def test_match(command): + assert match(command) + + +@pytest.mark.parametrize('command', [ + Command('./gradlew assemble'), + Command('gradle assemble'), + Command('npm assembler', stderr=stderr_ambiguous('assembler')), + Command('npm instar', stderr=stderr_not_found('instar'))]) +def test_not_match(command): + assert not match(command) + + +@pytest.mark.parametrize('command, result', [ + (Command('./gradlew assembler', stderr=stderr_ambiguous('assembler')), + './gradlew assemble'), + (Command('./gradlew instardebug', stderr=stderr_not_found('instardebug')), + './gradlew installDebug'), + (Command('gradle assembler', stderr=stderr_ambiguous('assembler')), + 'gradle assemble'), + (Command('gradle instardebug', stderr=stderr_not_found('instardebug')), + 'gradle installDebug')]) +def test_get_new_command(command, result): + assert get_new_command(command)[0] == result diff --git a/thefuck/const.py b/thefuck/const.py index fdef9b3a..fbbcdddf 100644 --- a/thefuck/const.py +++ b/thefuck/const.py @@ -32,7 +32,8 @@ DEFAULT_SETTINGS = {'rules': DEFAULT_RULES, 'history_limit': None, 'alter_history': True, 'wait_slow_command': 15, - 'slow_commands': ['lein', 'react-native'], + 'slow_commands': ['lein', 'react-native', 'gradle', + './gradlew'], 'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'}} ENV_TO_ATTR = {'THEFUCK_RULES': 'rules', diff --git a/thefuck/rules/gradle_no_task.py b/thefuck/rules/gradle_no_task.py new file mode 100644 index 00000000..f7492083 --- /dev/null +++ b/thefuck/rules/gradle_no_task.py @@ -0,0 +1,33 @@ +import re +from subprocess import Popen, PIPE +from thefuck.utils import for_app, eager, replace_command + + +@for_app('gradle', './gradlew') +def match(command): + return re.findall(r"Task '(.*)' (is ambiguous|not found)", command.stderr) + + +@eager +def _get_all_tasks(gradle): + proc = Popen([gradle, 'tasks'], stdout=PIPE) + should_yield = False + for line in proc.stdout.readlines(): + line = line.decode().strip() + if line.startswith('----'): + should_yield = True + continue + + if not line.strip(): + should_yield = False + continue + + if should_yield and not line.startswith('All tasks runnable from root project'): + yield line.split(' ')[0] + + +def get_new_command(command): + wrong_task = re.findall(r"Task '(.*)' (is ambiguous|not found)", + command.stderr)[0][0] + all_tasks = _get_all_tasks(command.script_parts[0]) + return replace_command(command, wrong_task, all_tasks)