diff --git a/tests/distro/run_extra_commands/application/test_run_command.py b/tests/distro/run_extra_commands/application/test_run_command.py index b198ff0..dfd0b63 100644 --- a/tests/distro/run_extra_commands/application/test_run_command.py +++ b/tests/distro/run_extra_commands/application/test_run_command.py @@ -16,15 +16,15 @@ def test_valid_tutor_command(): This test verifies that are executed all the extra commands successfully. """ # Given - tutor_commands_manager = TestTutorCommandManager() - run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager) - valid_tutor_commands = [ "command with word tutor 1", "command with word tutor 2", "command with word tutor 3", ] + tutor_commands_manager = TestTutorCommandManager() + run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=valid_tutor_commands) + # When for command in valid_tutor_commands: run_tutor_command(command=command) @@ -32,7 +32,7 @@ def test_valid_tutor_command(): assert tutor_commands_manager.commands_ran == len(valid_tutor_commands) -def test_invalid_tutor_command(): +def test_invalid_or_misspelled_tutor_command(): """ Test running invalid commands. @@ -40,17 +40,28 @@ def test_invalid_tutor_command(): intended to execute invalid extra commands. """ # Given - tutor_commands_manager = TestTutorCommandManager() - run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager) - - invalid_tutor_command = "pip command 1" + invalid_tutor_command = [ + "pip command 1", + "tutor command && pip command 2", + "tutor command & pip command 3", + "tutor command || pip command 4", + "tutor command | pip command 5", + "tutor command ; pip command 6", + ] - # When with pytest.raises(CommandError) as command_error: - run_tutor_command(command=invalid_tutor_command) + tutor_commands_manager = TestTutorCommandManager() + CommandsRunner(commands_manager=tutor_commands_manager, commands=invalid_tutor_command) assert command_error.type is CommandError - assert "is not a valid Tutor command" in command_error.value.args[0] + + splitted_commands = [tutor_commands_manager.split_command(command) for command in invalid_tutor_command] + commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ") + + pip_commands_sent = commands_word_by_word.count("pip") + pip_commands_found = command_error.value.args[0].split(" ").count("pip") + + assert pip_commands_sent == pip_commands_found def test_misspelled_tutor_command(): @@ -61,14 +72,24 @@ def test_misspelled_tutor_command(): a misspelled Tutor command. """ # Given - tutor_commands_manager = TestTutorCommandManager() - run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager) - - invalid_tutor_command = "totur command bad written" + misspelled_commands = [ + "totur command 1", + "totur command 2", + "totur command 3", + "totur command 4", + "totur command 5", + ] - # When with pytest.raises(CommandError) as command_error: - run_tutor_command(command=invalid_tutor_command) + tutor_commands_manager = TestTutorCommandManager() + CommandsRunner(commands_manager=tutor_commands_manager, commands=misspelled_commands) assert command_error.type is CommandError - assert "you have a typo" in command_error.value.args[0] + + splitted_commands = [tutor_commands_manager.split_command(command) for command in misspelled_commands] + commands_word_by_word = " ".join(sum(splitted_commands, [])).split(" ") + + misspelled_commands_sent = commands_word_by_word.count("totur") + misspelled_commands_found = command_error.value.args[0].split(" ").count("totur") + + assert misspelled_commands_sent == misspelled_commands_found diff --git a/tests/distro/run_extra_commands/infrastructure/test_tutor_commands.py b/tests/distro/run_extra_commands/infrastructure/test_tutor_commands.py index 9d3b970..e69c982 100644 --- a/tests/distro/run_extra_commands/infrastructure/test_tutor_commands.py +++ b/tests/distro/run_extra_commands/infrastructure/test_tutor_commands.py @@ -2,12 +2,10 @@ Test tutor commands functions. """ -from tutordistro.distro.extra_commands.domain.command_manager import CommandManager -from tutordistro.distro.share.domain.command_error import CommandError -from tutordistro.utils.constants import find_tutor_misspelled +from tutordistro.distro.extra_commands.infrastructure.tutor_commands import TutorCommandManager -class TestTutorCommandManager(CommandManager): +class TestTutorCommandManager(TutorCommandManager): """ Executes a Tutor command for testing. @@ -25,18 +23,5 @@ def run_command(self, command: str): Args: command (str): Testing command. """ - is_tutor_misspelled = find_tutor_misspelled(command) - - if "tutor" not in command.lower(): - if is_tutor_misspelled: - raise CommandError( - f'Maybe you have a typo using the command "{command}"' - ) - raise CommandError( - f''' - Command "{command}" is not a valid Tutor command. - Take the official Tutor commands into account - https://docs.tutor.edly.io/reference/cli/index.html - ''' - ) + self.commands_ran += 1 diff --git a/tutordistro/commands/run_extra_commands.py b/tutordistro/commands/run_extra_commands.py index fdce3b4..13a0f09 100644 --- a/tutordistro/commands/run_extra_commands.py +++ b/tutordistro/commands/run_extra_commands.py @@ -22,10 +22,11 @@ def run_extra_commands(): .strip() ) config = tutor_config.load(directory) + distro_extra_commands = config.get("DISTRO_EXTRA_COMMANDS", None) tutor_commands_manager = TutorCommandManager() - run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager) + run_tutor_command = CommandsRunner(commands_manager=tutor_commands_manager, commands=distro_extra_commands) - if config.get("DISTRO_EXTRA_COMMANDS"): - for command in config["DISTRO_EXTRA_COMMANDS"]: + if distro_extra_commands: + for command in distro_extra_commands: run_tutor_command(command=command) diff --git a/tutordistro/distro/extra_commands/application/commands_runner.py b/tutordistro/distro/extra_commands/application/commands_runner.py index f480ea8..b2cf730 100644 --- a/tutordistro/distro/extra_commands/application/commands_runner.py +++ b/tutordistro/distro/extra_commands/application/commands_runner.py @@ -15,8 +15,9 @@ class CommandsRunner: commands_manager (ThemeRepository): The command manager to use for executing the extra command. """ - def __init__(self, commands_manager: CommandManager): + def __init__(self, commands_manager: CommandManager, commands: list[str]): self.commands_manager = commands_manager + commands_manager.validate_commands(commands) def __call__(self, command: str): """ diff --git a/tutordistro/distro/extra_commands/infrastructure/tutor_commands.py b/tutordistro/distro/extra_commands/infrastructure/tutor_commands.py index 79c6304..ed196e6 100644 --- a/tutordistro/distro/extra_commands/infrastructure/tutor_commands.py +++ b/tutordistro/distro/extra_commands/infrastructure/tutor_commands.py @@ -2,11 +2,12 @@ Distro tutor command functions. """ +import re import subprocess from tutordistro.distro.extra_commands.domain.command_manager import CommandManager from tutordistro.distro.share.domain.command_error import CommandError -from tutordistro.utils.constants import find_tutor_misspelled +from tutordistro.utils.constants import COMMAND_CHAINING_OPERATORS, create_regex_from_array, find_tutor_misspelled class TutorCommandManager(CommandManager): @@ -18,6 +19,58 @@ class TutorCommandManager(CommandManager): Args: CommandManager (class): Base command manager class. """ + def split_command(self, command: str): + """ + Takes a command that is wanted to be split according to some + bash command chaining operators + + Args: + command (str): Command with command chaining operator + + Return: + The command split into an array + """ + return re.split(create_regex_from_array(COMMAND_CHAINING_OPERATORS), command) + + def validate_commands(self, commands: list[str] | None): + """ + Takes all the extra commands sent through config.yml and verifies that + all the commands are correct before executing them + + Args: + commands (list[str] | None): The commands sent through DISTRO_EXTRA_COMMANDS in config.yml + """ + if not commands: + raise CommandError( + "No commands found in the DISTRO_EXTRA_COMMANDS attribute of the config.yml file." + ) + + splitted_commands = [self.split_command(command) for command in commands] + flat_commands_array = sum(splitted_commands, []) + + invalid_commands = [] + misspelled_commands = [] + for command in flat_commands_array: + if "tutor" not in command.lower(): + if find_tutor_misspelled(command): + misspelled_commands.append(command) + else: + invalid_commands.append(command) + + if invalid_commands or misspelled_commands: + raise CommandError( + f""" + Error: Were found some issues with the commands: + + {'=> Invalid commands: ' if invalid_commands else ""} + {', '.join(invalid_commands)} + + {'=> Misspelled commands: ' if misspelled_commands else ""} + {', '.join(misspelled_commands)} + + Take a look of the official Tutor commands: https://docs.tutor.edly.io/reference/cli/index.html + """ + ) def run_command(self, command: str): """ @@ -29,20 +82,6 @@ def run_command(self, command: str): command (str): Tutor command. """ try: - is_tutor_misspelled = find_tutor_misspelled(command) - - if "tutor" not in command.lower(): - if is_tutor_misspelled: - raise CommandError( - f'Maybe you have a typo using the command "{command}"' - ) - raise CommandError( - f""" - Command "{command}" is not a valid Tutor command. - Take the official Tutor commands into account - https://docs.tutor.edly.io/reference/cli/index.html - """ - ) with subprocess.Popen( command, shell=True, @@ -56,6 +95,7 @@ def run_command(self, command: str): raise CommandError( f'Error running command "{command}".\n{error.decode()}' ) + # This print is left on purpose to show the command output print(output.decode()) except subprocess.SubprocessError as error: diff --git a/tutordistro/utils/constants.py b/tutordistro/utils/constants.py index 110528d..f366767 100644 --- a/tutordistro/utils/constants.py +++ b/tutordistro/utils/constants.py @@ -3,6 +3,8 @@ """ import re +COMMAND_CHAINING_OPERATORS = ["&&", "&", "||", "|", ";"] + def find_tutor_misspelled(command: str): """ @@ -15,3 +17,19 @@ def find_tutor_misspelled(command: str): If its found the word 'tutor' misspelled is returned True """ return re.match(r'[tT](?:[oru]{3}|[oru]{2}[rR]|[oru]u?)', command) + + +def create_regex_from_array(arr: list[str]): + """ + This functions compiles a new regex turning taking care of + escaping special characters + + Args: + arr (list[str]): String that would be used to create a new regex + + Return: + A new compiled regex pattern that can be used for comparisons + """ + escaped_arr = [re.escape(item) for item in arr] + regex_pattern = "|".join(escaped_arr) + return re.compile(regex_pattern)