def executeCMD(model: UMLModel, command_history: CommandHistory,
               commandName: str, arguments: list) -> None:
    """Grabs the command and pushes it into the history if it is undoable
    Executes the command in the REPL, and prints out a success/error message
    depending on whether it was an appropriate command
    
    Params:
    - model - the current model to execute commands on 
    - commandName - the name of the command
    - arguments - the possible argument(s) that came with the command
    """

    # Ensure valid command
    if commandName not in CommandData.COMMANDS:
        print(
            f"{ERROR_COLOR}CommandError:{NORMAL_COLOR}",
            f"'{commandName}' is not a valid command\ntype 'help' for a list of valid commands"
        )
        return

    # Find command usage matching args
    # and validate number of arguments
    command = None
    for usage in CommandData.COMMANDS[commandName]:
        # found usage matching num args
        if usage["num_arguments"] == len(arguments):
            # if the command class was provided
            if "command" in usage:
                command = usage["command"](model, usage["function"], arguments)
            # otherwise, assume it is not an undoable command
            else:
                command = CLICommand(model, usage["function"], arguments)
            break
    # matching usage not found
    else:
        print(
            f"{ERROR_COLOR}CommandError:{NORMAL_COLOR}",
            f"Incorrect usage of {commandName}\ntype 'help {commandName}' to see valid usages of {commandName}"
        )
        return

    # save backup - for undoable commands
    if isinstance(command, UndoableCLICommand):
        command.saveBackup()

    # execute the command
    status, msg = command.execute()

    # Ensure command was successful
    if not status:
        print(f"{ERROR_COLOR}ERROR:{NORMAL_COLOR} {msg}")
        return

    # push undoable commands to the history
    if isinstance(command, UndoableCLICommand):
        # add to the list of history
        command_history.push(command)

    print(f"{SUCCESS_COLOR}SUCCESS:{NORMAL_COLOR} {msg}")
    def test_executeCMD(self):
        model = UMLModel()
        command_history = CommandHistory(HISTORY_LIMIT)

        # Test1 : simple cmd
        executeCMD(model, command_history, "create_class", ["class1"])
        # ensure model has the new class
        self.assertTrue("class1" in model.classes)
        # ensure command is in history
        self.assertTrue(len(command_history.history) == 1)

        # Test2 : invalid cmd
        captured = io.StringIO()
        sys.stdout = captured
        executeCMD(model, command_history, "not_a_command", [])
        # ensure error message was given
        self.assertEqual(
            captured.getvalue(), f"{ERROR_COLOR}CommandError:{NORMAL_COLOR} "
            f"'not_a_command' is not a valid command\ntype 'help' for a list of valid commands\n"
        )

        # Test3 : invalid length of arguments
        captured = io.StringIO()
        sys.stdout = captured
        commandName = "create_field"
        executeCMD(model, command_history, commandName, ["name"])
        # ensure error message was given
        self.assertEqual(
            captured.getvalue(), f"{ERROR_COLOR}CommandError:{NORMAL_COLOR} "
            f"Incorrect usage of {commandName}\ntype 'help {commandName}' to see valid usages of {commandName}\n"
        )

        # Test4 : valid cmd with arguments that fail
        captured = io.StringIO()
        sys.stdout = captured
        executeCMD(model, command_history, "create_class", ["class1"])
        # ensure error message was given
        self.assertTrue(captured.getvalue().startswith(
            f"{ERROR_COLOR}ERROR:{NORMAL_COLOR}"))

        # Test5 : non-undoable command
        captured = io.StringIO()
        sys.stdout = captured
        command_history = CommandHistory(HISTORY_LIMIT)
        executeCMD(model, command_history, "list_classes", [])
        # ensure success
        self.assertTrue(captured.getvalue().startswith(
            f"{SUCCESS_COLOR}SUCCESS:{NORMAL_COLOR}"))
        # ensure command was not added to history
        self.assertEqual(len(command_history.history), 0)
    def test_complete_rename_class(self):
        model = UMLModel()
        model.create_class("class1")
        model.create_class("myclass")
        repl = REPL(model, CommandHistory(HISTORY_LIMIT))

        # Test 1: Tab before classname
        classnames = repl.complete_rename_class("", "rename_class ", 0, 0)
        self.assertEqual(classnames, ["class1", "myclass"])

        # Test 2: Tab in the middle of classname - only one match
        classnames = repl.complete_rename_class("cl", "rename_class cl", 0, 0)
        self.assertEqual(classnames, ["class1"])

        # Test 3: Tab after classname
        classnames = repl.complete_rename_class("class1",
                                                "rename_class class1", 0, 0)
        self.assertEqual(classnames, ["class1"])

        # Test 4: Tab before second classname
        classnames = repl.complete_rename_class("", "rename_class class1 ", 0,
                                                0)
        self.assertEqual(classnames, [])

        # Test 5: Tab after second classname
        classnames = repl.complete_rename_class(
            "newclassname", "rename_class class1 newclassname", 0, 0)
        self.assertEqual(classnames, [])
 def __init__(self,
              model=UMLModel(),
              command_history=CommandHistory(HISTORY_LIMIT)):
     cmd.Cmd.__init__(self)
     self.model = model
     # Keeps track of the commands that were performed
     self.command_history = command_history
    def test_do_create_class(self):
        model = UMLModel()
        command_history = CommandHistory(HISTORY_LIMIT)
        repl = REPL(model, command_history)

        repl.do_create_class("class1")
        # ensure class was created
        self.assertTrue("class1" in model.classes)
    def test_do_create_field(self):
        model = UMLModel()
        model.create_class("class1")
        command_history = CommandHistory(HISTORY_LIMIT)
        repl = REPL(model, command_history)

        repl.do_create_field("class1 private int age")
        # ensure field was created
        self.assertTrue(model.classes["class1"].field_index("age") != -1)
    def test_complete_rename_parameter(self):
        model = UMLModel()
        model.create_class("class1")
        model.create_method("class1", "public", "int", "method1")
        model.create_parameter("class1", "method1", "int", "a")
        model.create_parameter("class1", "method1", "int", "b")
        model.create_parameter("class1", "method1", "int", "abba")
        model.create_method("class1", "public", "int", "method2")
        model.create_method("class1", "public", "int", "mymethod")
        model.create_class("myclass")
        repl = REPL(model, CommandHistory(HISTORY_LIMIT))

        # Test 1: Tab before classname
        classnames = repl.complete_rename_parameter("", "rename_parameter ", 0,
                                                    0)
        self.assertEqual(classnames, ["class1", "myclass"])

        # Test 2: Tab in the middle of classname - only one match
        classnames = repl.complete_rename_parameter("cl",
                                                    "rename_parameter cl", 0,
                                                    0)
        self.assertEqual(classnames, ["class1"])

        # Test 3: Tab before methodname
        classnames = repl.complete_rename_parameter(
            "", "rename_parameter class1 ", 0, 0)
        self.assertEqual(classnames, ["method1", "method2", "mymethod"])

        # Test 4: Tab during methodname
        classnames = repl.complete_rename_parameter(
            "met", "rename_parameter class1 met", 0, 0)
        self.assertEqual(classnames, ["method1", "method2"])

        # Test 5: Tab before old param name
        classnames = repl.complete_rename_parameter(
            "", "rename_parameter class1 method1 ", 0, 0)
        self.assertEqual(classnames, ["a", "b", "abba"])

        # Test 6: Tab during old param name
        classnames = repl.complete_rename_parameter(
            "a", "rename_parameter class1 method1 a", 0, 0)
        self.assertEqual(classnames, ["a", "abba"])

        # Test 7: Tab after old param name
        classnames = repl.complete_rename_parameter(
            "", "rename_parameter class1 method1 abba ", 0, 0)
        self.assertEqual(classnames, [])

        # Test 8: Tab after old param name
        classnames = repl.complete_rename_parameter(
            "iLikePie", "rename_parameter class1 method1 abba iLikePie", 0, 0)
        self.assertEqual(classnames, [])
    def test_undo(self):
        model = UMLModel()
        command_history = CommandHistory(HISTORY_LIMIT)
        executeCMD(model, command_history, "create_class", ["class1"])
        # ensure class was there previously
        self.assertTrue("class1" in model.classes)

        # Test 1: undo valid command
        undo(command_history, [])
        # ensure command was undone
        self.assertTrue(len(model.classes) == 0)
        self.assertTrue(len(command_history.history) == 0)
        self.assertTrue(len(command_history.future) == 1)

        # Test 2: nothing to undo
        captured = io.StringIO()
        sys.stdout = captured
        undo(command_history, [])
        self.assertEqual(
            captured.getvalue(),
            f"{ERROR_COLOR}ERROR:{NORMAL_COLOR} no command to undo\n")
def redo(command_history: CommandHistory, args: list):
    """redoes a command in the given command history and prints whether 
    it was successful or not. the args parameter just checks to make sure
    no arguments were provided
    """
    # Ensure no args
    if len(args) != 0:
        print(f"{ERROR_COLOR}CommandError:{NORMAL_COLOR}",
              f"Incorrect usage of redo\nredo does not take any arguments")
        return

    # get undone command
    command = command_history.pop_redo()

    # ensure there was a command
    if command == None:
        print(f"{ERROR_COLOR}ERROR:{NORMAL_COLOR} no command to redo")
        return

    # redo the command
    command.execute()

    print(f"{SUCCESS_COLOR}SUCCESS:{NORMAL_COLOR} command redone succesfully")