From dcef21e77848eb00b562ed18738d12331dacd3a1 Mon Sep 17 00:00:00 2001 From: hustona Date: Tue, 19 Apr 2022 19:00:40 -0400 Subject: [PATCH] completed changes for makefile rule --- README.md | 1 + tests/rules/test_makefile.py | 129 +++++++++++++++++++++++++++++++++++ thefuck/rules/makefile.py | 61 +++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 tests/rules/test_makefile.py create mode 100644 thefuck/rules/makefile.py diff --git a/README.md b/README.md index 182acce8..845f1abd 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ following rules are enabled by default: * `ln_s_order` – fixes `ln -s` arguments order; * `ls_all` – adds `-A` to `ls` when output is empty; * `ls_lah` – adds `-lah` to `ls`; +* `makefile` – fixes wrong `make` commands; * `man` – changes manual section; * `man_no_space` – fixes man commands without spaces, for example `mandiff`; * `mercurial` – fixes wrong `hg` commands; diff --git a/tests/rules/test_makefile.py b/tests/rules/test_makefile.py new file mode 100644 index 00000000..3bc1a1c7 --- /dev/null +++ b/tests/rules/test_makefile.py @@ -0,0 +1,129 @@ +import pytest +import os +from thefuck.rules.makefile import match, get_new_command +from thefuck.types import Command + +@pytest.mark.parametrize('command,expected', [ + (Command('make fo', "make: *** No rule to make target 'fo'. Stop."), True), + (Command('make ba', "make: *** No rule to make target 'ba'. Stop."), True), + (Command('mak foo', "mak: command not found"), True), + (Command('mak oo', "mak: command not found"), True), + (Command('meak foo', "meak: command not found"), True), + (Command('maek bar', "maek: command not found"), True), + (Command('maek br', "maek: command not found"), True), + (Command('amke foo', "amke: command not found"), True), + (Command('make foo', "make: command not found"), False), + (Command('cd foo', 'cd: foo: No such file or directory'), False), + (Command('java foo.java', 'error message'), False), + (Command('make partone parttwo', "make: *** No rule to make target 'partone parttwo'. Stop."), True) + ]) +def test_match(command,expected): + """ + Tests the match function with inputs such as misspelled makes, + misspelled targets, irrelevant commands, and correctly formatted commands. + Tests with makefiles named Makefile, makefile, and GNUmakefile + """ + with open("Makefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("bar:\n") + result = match(command) + os.remove("Makefile") # Removes Temporary Makefile + assert result == expected + + with open("makefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("bar:\n") + result = match(command) + os.remove("makefile") # Removes Temporary Makefile + assert result == expected + + with open("GNUmakefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("bar:\n") + result = match(command) + os.remove("GNUmakefile") # Removes Temporary Makefile + assert result == expected + + + +@pytest.mark.parametrize('command,expected', [ + (Command('make foo', "no error"), False), + (Command('make fo', "make: *** No rule to make target 'fo'. Stop."), False), + (Command('maek bar', "maek: command not found"), False), + (Command('cd foo', 'cd: foo: No such file or directory'), False) + ]) +def test_match_no_makefile(command,expected): + """ + Tests the match function with no available makefile + """ + result = match(command) + assert result == expected + + +@pytest.mark.parametrize('command,expected', [ + (Command('make fo', "make: *** No rule to make target 'fo'. Stop."), "make foo"), + (Command('make ba', "make: *** No rule to make target 'ba'. Stop."), "make bar"), + (Command('make for', "make: *** No rule to make target 'for'. Stop."), "make foo"), + (Command('mak foo', "mak: command not found"), "make foo"), + (Command('mak oo', "mak: command not found"), "make foo"), + (Command('meak foo', "meak: command not found"), "make foo"), + (Command('maek bar', "maek: command not found"), "make bar"), + (Command('maek br', "maek: command not found"), "make bar"), + (Command('amke foo', "amke: command not found"), "make foo"), + (Command('amke qwe', "amke: command not found"), "make foo"), + ]) +def test_get_new_command(command,expected): + """ + Test the correct functionality of generating the new command. + Tests with makefiles named Makefile, makefile, and GNUmakefile + """ + + with open("Makefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("\tg++ testfoo.cpp\n") + openfile.write("bar:\n") + openfile.write("\tg++ testbar.cpp\n") + result = get_new_command(command) + os.remove("Makefile") # Removes Temporary Makefile + assert result == expected + + with open("makefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("\tg++ testfoo.cpp\n") + openfile.write("bar:\n") + openfile.write("\tg++ testbar.cpp\n") + result = get_new_command(command) + os.remove("makefile") # Removes Temporary Makefile + assert result == expected + + with open("GNUmakefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("\tg++ testfoo.cpp\n") + openfile.write("bar:\n") + openfile.write("\tg++ testbar.cpp\n") + result = get_new_command(command) + os.remove("GNUmakefile") # Removes Temporary Makefile + assert result == expected + +@pytest.mark.parametrize('command,expected', [ + (Command('make fo', "make: *** No rule to make target 'fo'. Stop."), "make foo"), + (Command('make foodo', "make: *** No rule to make target 'foodo'. Stop."), "make food"), + (Command('make dfoo', "make: *** No rule to make target 'dfoo'. Stop."), "make foo"), + ]) +def test_get_new_command_2(command,expected): + with open("Makefile", "w") as openfile: + # Creates Temporary makefile for testing + openfile.write("foo:\n") + openfile.write("\tg++ testfoo.cpp\n") + openfile.write("food:\n") + openfile.write("\tg++ testfood.cpp\n") + result = get_new_command(command) + os.remove("Makefile") # Removes Temporary Makefile + assert result == expected + \ No newline at end of file diff --git a/thefuck/rules/makefile.py b/thefuck/rules/makefile.py new file mode 100644 index 00000000..32e0ed5d --- /dev/null +++ b/thefuck/rules/makefile.py @@ -0,0 +1,61 @@ +import os +from os.path import isfile, join +from thefuck.utils import get_closest + +TOLERANCE = 0.6 +COMMON_MISSPELLINGS = ['mke', 'maek', 'amke', 'meak', 'mkae', 'ake', 'mae', 'mak', 'makee', 'mmake', 'nake', 'makr', 'mske'] + +def makefile_in_directory(): + """ + Returns the name of the Makefile in the current directory. + Will return a blank string if no makefile found. + """ + name_of_makefile = "" + file_list = [f for f in os.listdir(os.getcwd()) if isfile(join(os.getcwd(), f))] + if 'GNUmakefile' in file_list: + name_of_makefile = 'GNUmakefile' + elif 'makefile' in file_list: + name_of_makefile = 'makefile' + elif 'Makefile' in file_list: + name_of_makefile = 'Makefile' + return name_of_makefile + +def match(command): + """Match function that determines if this rule could fix this command""" + if len(makefile_in_directory()) == 0: + # This would mean there is no makefile in the directory, we do not have a match + return False + first_word = command.script_parts[0].lower() + if first_word in COMMON_MISSPELLINGS: + # There is a Makefile and the first word of the command was a misspelling of 'make' + return True + if 'no rule to make target' in command.output.lower(): + # The target passed into the make command was misspelled or did not exist + return True + return False + + +def get_new_command(command): + """ + Attempt to rebuild the make command by spellchecking the targets. + If it fails (i.e. no targets are a close enough match), then it + defaults to first make target in the file. + Change sensitivity by changing TOLERANCE. Default value is 0.6 + """ + # Get possible targets from makefile and find the most similar. + name_of_makefile = makefile_in_directory() + possible_targets = [] + with open(name_of_makefile, 'r') as openfile: + lines = openfile.readlines() + for line in lines: # Loop through the makefile lines and find all targets + line = line.strip() + parts = line.split(':') + if len(parts) > 1: # If there is a colon + if parts[0][0] != '.': # If it is a valid target + possible_targets.append(parts[0]) + new_target = get_closest(' '.join(command.script_parts[1:]), possible_targets, cutoff=TOLERANCE) + return "make " + new_target + + + + \ No newline at end of file