diff --git a/README.md b/README.md index f0618c34..2ba23d26 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ using the matched rule and runs it. Rules enabled by default are as follows: * `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; +* `grunt_task_not_found` – fixes misspelled `grunt` commands; * `gulp_not_task` – fixes misspelled `gulp` tasks; * `has_exists_script` – prepends `./` when script/binary exists; * `heroku_not_command` – fixes wrong `heroku` commands like `heroku log`; diff --git a/tests/rules/test_grunt_task_not_found.py b/tests/rules/test_grunt_task_not_found.py new file mode 100644 index 00000000..b149fb8a --- /dev/null +++ b/tests/rules/test_grunt_task_not_found.py @@ -0,0 +1,127 @@ +from io import BytesIO +import pytest +from tests.utils import Command +from thefuck.rules.grunt_task_not_found import match, get_new_command + +stdout = ''' +Warning: Task "{}" not found. Use --force to continue. + +Aborted due to warnings. + + +Execution Time (2016-08-13 21:01:40 UTC+3) +loading tasks 11ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 92% +Total 12ms + +'''.format + +grunt_help_stdout = b''' +Grunt: The JavaScript Task Runner (v0.4.5) + +Usage + grunt [options] [task [task ...]] + +Options + --help, -h Display this help text. + --base Specify an alternate base path. By default, all file paths are + relative to the Gruntfile. (grunt.file.setBase) * + --no-color Disable colored output. + --gruntfile Specify an alternate Gruntfile. By default, grunt looks in the + current or parent directories for the nearest Gruntfile.js or + Gruntfile.coffee file. + --debug, -d Enable debugging mode for tasks that support it. + --stack Print a stack trace when exiting with a warning or fatal error. + --force, -f A way to force your way past warnings. Want a suggestion? Don't + use this option, fix your code. + --tasks Additional directory paths to scan for task and "extra" files. + (grunt.loadTasks) * + --npm Npm-installed grunt plugins to scan for task and "extra" files. + (grunt.loadNpmTasks) * + --no-write Disable writing files (dry run). + --verbose, -v Verbose mode. A lot more information output. + --version, -V Print the grunt version. Combine with --verbose for more info. + --completion Output shell auto-completion rules. See the grunt-cli + documentation for more information. + +Options marked with * have methods exposed via the grunt API and should instead +be specified inside the Gruntfile wherever possible. + +Available tasks + autoprefixer Prefix CSS files. * + concurrent Run grunt tasks concurrently * + clean Clean files and folders. * + compass Compile Sass to CSS using Compass * + concat Concatenate files. * + connect Start a connect web server. * + copy Copy files. * + cssmin Minify CSS * + htmlmin Minify HTML * + imagemin Minify PNG, JPEG, GIF and SVG images * + jshint Validate files with JSHint. * + uglify Minify files with UglifyJS. * + watch Run predefined tasks whenever watched files change. + filerev File revisioning based on content hashing * + cdnify Replace scripts with refs to the Google CDN * + karma run karma. * + newer Run a task with only those source files that have been modified + since the last successful run. + any-newer DEPRECATED TASK. Use the "newer" task instead + newer-postrun Internal task. + newer-clean Remove cached timestamps. + ngAnnotate Add, remove and rebuild AngularJS dependency injection + annotations * + ngconstant Dynamic angular constant generator task. * + svgmin Minify SVG * + usemin Replaces references to non-minified scripts / stylesheets * + useminPrepare Using HTML markup as the primary source of information * + wiredep Inject Bower components into your source code. * + serve Compile then start a connect web server + server DEPRECATED TASK. Use the "serve" task instead + test Alias for "clean:server", "ngconstant:test", "wiredep", + "concurrent:test", "autoprefixer", "connect:test", "karma" + tasks. + build Alias for "ngconstant:production", "clean:dist", "wiredep", + "useminPrepare", "concurrent:dist", "autoprefixer", "concat", + "ngAnnotate", "copy:dist", "cdnify", "cssmin", "uglify", + "filerev", "usemin", "htmlmin" tasks. + default Alias for "newer:jshint", "test", "build" tasks. + +Tasks run in the order specified. Arguments may be passed to tasks that accept +them by using colons, like "lint:files". Tasks marked with * are "multi tasks" +and will iterate over all sub-targets if no argument is specified. + +The list of available tasks may change based on tasks directories or grunt +plugins specified in the Gruntfile or via command-line options. + +For more information, see http://gruntjs.com/ +''' + + +@pytest.fixture(autouse=True) +def grunt_help(mocker): + patch = mocker.patch('thefuck.rules.grunt_task_not_found.Popen') + patch.return_value.stdout = BytesIO(grunt_help_stdout) + return patch + + +@pytest.mark.parametrize('command', [ + Command('grunt defualt', stdout('defualt')), + Command('grunt buld:css', stdout('buld:css'))]) +def test_match(command): + assert match(command) + + +@pytest.mark.parametrize('command', [ + Command('npm nuild', stdout('nuild')), + Command('grunt rm')]) +def test_not_match(command): + assert not match(command) + + +@pytest.mark.parametrize('command, result', [ + (Command('grunt defualt', stdout('defualt')), 'grunt default'), + (Command('grunt cmpass:all', stdout('cmpass:all')), 'grunt compass:all'), + (Command('grunt cmpass:all --color', stdout('cmpass:all')), + 'grunt compass:all --color')]) +def test_get_new_command(command, result): + assert get_new_command(command) == result diff --git a/thefuck/rules/gradle_no_task.py b/thefuck/rules/gradle_no_task.py index f7492083..eaf30136 100644 --- a/thefuck/rules/gradle_no_task.py +++ b/thefuck/rules/gradle_no_task.py @@ -2,10 +2,12 @@ import re from subprocess import Popen, PIPE from thefuck.utils import for_app, eager, replace_command +regex = re.compile(r"Task '(.*)' (is ambiguous|not found)") + @for_app('gradle', './gradlew') def match(command): - return re.findall(r"Task '(.*)' (is ambiguous|not found)", command.stderr) + return regex.findall(command.stderr) @eager @@ -27,7 +29,6 @@ def _get_all_tasks(gradle): def get_new_command(command): - wrong_task = re.findall(r"Task '(.*)' (is ambiguous|not found)", - command.stderr)[0][0] + wrong_task = regex.findall(command.stderr)[0][0] all_tasks = _get_all_tasks(command.script_parts[0]) return replace_command(command, wrong_task, all_tasks) diff --git a/thefuck/rules/grunt_task_not_found.py b/thefuck/rules/grunt_task_not_found.py new file mode 100644 index 00000000..d36f7af3 --- /dev/null +++ b/thefuck/rules/grunt_task_not_found.py @@ -0,0 +1,36 @@ +import re +from subprocess import Popen, PIPE +from thefuck.utils import for_app, eager, get_closest + +regex = re.compile(r'Warning: Task "(.*)" not found.') + + +@for_app('grunt') +def match(command): + return regex.findall(command.stdout) + + +@eager +def _get_all_tasks(): + proc = Popen(['grunt', '--help'], stdout=PIPE) + should_yield = False + for line in proc.stdout.readlines(): + line = line.decode().strip() + + if 'Available tasks' in line: + should_yield = True + continue + + if should_yield and not line: + return + + if ' ' in line: + yield line.split(' ')[0] + + +def get_new_command(command): + misspelled_task = regex.findall(command.stdout)[0].split(':')[0] + tasks = _get_all_tasks() + fixed = get_closest(misspelled_task, tasks) + return command.script.replace(' {}'.format(misspelled_task), + ' {}'.format(fixed))