https://github.com/ExpelliarmusSuperComp/Expelliarmus
Raw File
Tip revision: 83a8b7d8fc3d3b7dd4ac7ef5df4c02bd648bc526 authored by ExpelliarmusSuperComp on 07 August 2023, 14:18:01 UTC
LICENSE
Tip revision: 83a8b7d
CLI.py
import cmd
import os
import glob
import shutil
import readline
from Expelliarmus import Expelliarmus
from RepositoryDatabase import RepositoryDatabase
from StaticInfo import StaticInfo

# for correct path completion (https://stackoverflow.com/questions/16826172/filename-tab-completion-in-cmd-cmd-of-python)
readline.set_completer_delims(' \t\n')


def _complete_rel_path(path):
    if not path.startswith("/"):
        if os.path.isdir(path):
            return glob.glob(os.path.join(path, '*'))
        else:
            return glob.glob(path+'*')
    else:
        return None

def _complete_arg_list(text, arglist):
    return [i for i in arglist if i.startswith(text)]

class bcolors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

class MainInterpreter(cmd.Cmd):
    prompt = bcolors.OKBLUE + "(Expelliarmus) " + bcolors.ENDC
    _availableArgsList = ("vmis", "packages", "baseimages")
    _availableArgsReassembly = []
    _availableArgsEvaluateFunctions = ["decomposition1", "decomposition2", "reassembly", "similarity"]
    _availableArgsEvaluateOptions = ["--repetitions=", "--path="]

    def __init__(self):
        cmd.Cmd.__init__(self)
        print StaticInfo.cliLogo
        print StaticInfo.cliIntro
        print "\n\n\n\n"
        print StaticInfo.cliIntroHelp

        self.exp = Expelliarmus()

        with RepositoryDatabase() as repo:
            numVMIs = repo.getNumberOfVMIs()
            numBases = repo.getNumberOfBaseImages()
            numPkgs = repo.getNumberOfPackages()
            self._availableArgsReassembly = repo.getAllVmiNames()
            self._availableArgsReassembly.append("all")

        digits = max(len(numVMIs),len(numBases),len(numPkgs))
        print "State of Repository Storage:\n"
        print "\tVMIs:        {0:>{width}s}".format(numVMIs, width=digits)
        print "\tBase Images: {0:>{width}s}".format(numBases, width=digits)
        print "\tPackages:    {0:>{width}s}".format(numPkgs, width=digits)
        print "\nSupported VMI formats: " + ",".join(StaticInfo.validVMIFormats) + "\n\n\n\n"


    def emptyline(self):
        pass

    def do_help(self, arg):
        if arg:
            cmd.Cmd.do_help(self, arg)
        else:
            print ""
            print "Expelliarmus: " + StaticInfo.cliIntro + "\n"
            print "The following Commands are available. Type \"help name\" to get a summary about the command \"name\" and how to use it."
            print "Any path given by the user to specify files or folders has to be relative to the working directory of this program."
            print ""
            print "\tlist       - show information about VMI components currently stored"
            print "\tinspect    - inspect VMIs and define main services"
            print "\tdecompose  - decompose VMIs"
            print "\treassemble - reassemble VMIs"
            print "\tevaluate   - tool to evaluate this program"
            print "\treset      - reset local repository of VMI components"
            print "\texit       - exit program"
            print ""

    def do_list(self, items):
        if items == "vmis":
            self.exp.printVMIs()
        elif items == "packages":
            self.exp.printPackages()
        elif items == "baseimages":
            self.exp.printBaseImages()
        else:
            print "\"%s\" not recognized. Type \"help list\" for possible components to list" % items

    def complete_list(self, text, line, begidx, endidx):
        return [i for i in self._availableArgsList if i.startswith(text)]

    def help_list(self):
        print "\nUsage: list { vmis | packages | baseimages }"
        print ""
        print "\tShows a complete list of VMIs/Packages/Base images that are currently stored in the repository.\n"

    def do_inspect(self, line):
        if line.startswith("/"):
            print "Error: \"%s\" is not a valid path. Please try again with a path relative to the directory of this program." % line
        elif os.path.isfile(line):
            self.exp.inspectVMI(line)
        elif os.path.isdir(line):
            self.exp.inspectVMIsInFolder(line)
        else:
            print "Error: \"%s\" is not a valid path." % line

    def complete_inspect(self, text, line, begidx, endidx):
        return _complete_rel_path(text)

    def help_inspect(self):
        print "\n" \
              "Usage: inspect path\n\n" \
              "\tInspect the VMI specified by \"path\" or all vmis in folder specified by \"path\".\n" \
              "\tThis process allows the user to specify main services for VMIs.\n" \
              "\tCorresponding .meta files required for decomposition are created in the same folder as the inspected VMI(s).\n" \
              "\t" + StaticInfo.cliHintPath + "\n"

    def do_decompose(self, line):
        if line.startswith("/"):
            print "Error: \"%s\" is not a valid path. Please try again with a path relative to the directory of this program." % line
        elif os.path.isfile(line):
            self.exp.decomposeVMI(line)
            with RepositoryDatabase() as repo:
                self._availableArgsReassembly = repo.getAllVmiNames()
                self._availableArgsReassembly.append("all")
        elif os.path.isdir(line):
            self.exp.decomposeVMIsInFolder(line)
            with RepositoryDatabase() as repo:
                self._availableArgsReassembly = repo.getAllVmiNames()
                self._availableArgsReassembly.append("all")
        else:
            print "Error: \"%s\" is not a valid path." % line

    def help_decompose(self):
        print "\nUsage: decompose path"
        print "\n\tDecompose the VMI specified by \"path\" or all VMIs in folder specified by \"path\"."
        print "\tRequires a .meta file for each VMI to be decomposed. This file can be created with command \"inspect\".\n"

    def complete_decompose(self, text, line, begidx, endidx):
        return _complete_rel_path(text)

    def do_reassemble(self, line):
        if line == "all":
            self.exp.reassembleAllVMIs()
        elif line in self._availableArgsReassembly:
            self.exp.reassembleVMI(line)
        else:
            print "Error: VMI name \"%s\" not recognized." % line

    def help_reassemble(self):
        print "\nUsage: reassemble { name | all }"
        print "\n\tReassemble the VMI specified by \"name\" or \"all\" VMIs stored in the repository."
        print "\tA list of available VMIs can be obtained through \"list vmis\".\n"

    def complete_reassemble(self, text, line, begidx, endidx):
        return [i for i in self._availableArgsReassembly if i.startswith(text)]

    def parseRepetitions(self, text):
        try:
            return int(text.rsplit("=", 1)[1])
        except ValueError as e:
            print "Error: %s is not a valid number" % text.rsplit("=", 1)[1]
            return None

    def do_evaluate(self,line):
        args = line.split()
        repetitions = None
        path = None
        func = None

        # no arguments
        if len(args) == 0:
            print "Error: missing arguments. Please consult \"help evaluate\"."
            return

        # one argument, has to be function
        if len(args) < 2:
            func = args[0]

        # two arguments, first option, second functionality
        elif len(args) == 2:
            if args[0].startswith ("--path="):
                path = args[0][7:]
            elif args[0].startswith ("--repetitions="):
                repetitions = self.parseRepetitions(args[0])
                if not repetitions: return
            else:
                print "Error: With two arguments, first has to be an option. Please consult \"help evaluate\"."
                return
            func = args[1]

        # three arguments, two options, one functionality
        elif len(args) == 3:
            # first option
            if args[0].startswith ("--path="):
                path = args[0][7:]
            elif args[0].startswith ("--repetitions="):
                repetitions = self.parseRepetitions(args[0])
                if not repetitions: return
            else:
                print "Error: With three arguments, first two have to be options. Please consult \"help evaluate\"."
                return
            # second option
            # first was repetitions, second has to be path
            if repetitions and args[1].startswith ("--path="):
                path = args[1][7:]
            # first was path, second has to be repetitions
            elif path and args[1].startswith ("--repetitions="):
                repetitions = self.parseRepetitions(args[1])
                if not repetitions: return
            else:
                print "Error: With three arguments, first two have to be options. Please consult \"help evaluate\"."
                return
            func = args[2]

        else:
            print "Error: to many arguments. Please consult \"help evaluate\"."
            return

        # if path set, verify
        if path:
            # check if path is directory
            if path and not os.path.isdir(path):
                print "Error: \"%s\" is not a directory" % path
                return
            # check if path is relative
            if path.startswith("/"):
                print "Error: \"%s\" is not a valid path. Please try again with a path relative to the directory of this program." % path
                return
            # check if all meta files exist
            if not self.exp.verifySourceFolder(path):
                print "Error: Verification of source folder failed. See explanation above."
                return

        # check if functionality valid
        if not func in self._availableArgsEvaluateFunctions:
            print "Error: Functionality \"%s\" not recognized" % func
            return

        # if repetitions not set, set to default
        if not repetitions:
            repetitions = 5

        # check if evaluation folder exists
        if not os.path.isdir(StaticInfo.relPathLocalEvaluation):
            os.mkdir(StaticInfo.relPathLocalEvaluation)

        # check if evaluation folder is empty
        if len(os.listdir(StaticInfo.relPathLocalEvaluation)) > 0:
            print "Evaluation folder \"%s\" is not empty. Please back up any evaluation files in this folder and remove them." % StaticInfo.relPathLocalEvaluation
            return

        # start evaluation
        print ""
        print "\nEvaluated Functionality:              " + func
        if path:
            print "Folder with Images to evaluate:       " + path
        print "Number of repetitions for evaluation: " + str(repetitions)
        print ""

        if repetitions > 5:
            print "You chose to repeat the evaluation process %i times. Depending on the data set, this might take a long time." % repetitions
            answer = raw_input("\tAre you sure you want to continue, yes or no?")
            if answer == "yes":
                pass
            else:
                return

        if func == "similarity":
            if not path:
                print "Error: no path set. Please consult \"help evaluate\"."
                return
            print "\n"
            self.exp.evaluateSimBetweenAll(path)
        elif func == "decomposition1":
            if not path:
                print "Error: no path set. Please consult \"help evaluate\"."
                return
            print "This evaluation requires the local repository to be reset and the directory \"%s\" to be cleared." % StaticInfo.relPathLocalVMIFolder
            if self.do_reset("") and self.clearVmiFolder():
                print "\n"
                self.exp.evaluateDecomposition(path,repetitions, resetBeforeEachDecomposition=False)
        elif func == "decomposition2":
            if not path:
                print "Error: no path set. Please consult \"help evaluate\"."
                return
            print "This evaluation requires the local repository to be reset and the directory \"%s\" to be cleared." % StaticInfo.relPathLocalVMIFolder
            if self.do_reset("") and self.clearVmiFolder():
                print "\n"
                self.exp.evaluateDecomposition(path, repetitions, resetBeforeEachDecomposition=True)
        elif func == "reassembly":
            print "This evaluation requires the directory \"%s\" to be cleared." % StaticInfo.relPathLocalVMIFolder
            if self.clearVmiFolder():
                print "\n"
                self.exp.evaluateReassembly(repetitions)
        else:
            print "Error: Functionality \"%s\" not recognized" % func

    def help_evaluate(self):
        print "\nUsage: evaluate [options] { decomposition1 | decomposition2 | reassembly | similarity }"
        print "\n\tEvaluates the given functionality and saves results in folder \"Evaluations\"."
        print "\nFunctionalities:"
        print "\tdecomposition1"
        print "\t\tEvaluates the decomposition process exploiting semantic redundancy (i.e. using a local repository)."
        print "\t\tOption \"--path\" has to be set to specify a source folder for VMIs (These will only be copied, not manipulated)."
        print "\n\tdecomposition2"
        print "\t\tEvaluates the decomposition process without exploiting semantic redundancy (i.e. not using a local repository)."
        print "\t\tOption \"--path\" has to be set to specify a source folder for VMIs (These will only be copied, not manipulated)."
        print "\n\treassembly"
        print "\t\tEvaluates the reassembly process using any VMI present in the local repository."
        print "\t\tOption \"--path\" is ignored."
        print "\n\tsimilarity"
        print "\t\tEvaluates the similarity between each VMI in source folder."
        print "\t\tOption \"--path\" has to be set to specify a source folder for VMIs (These will not be manipulated)."
        print "\nOptions:"
        print "\t--repetitions=x"
        print "\t\tSpecify number of repetitions for evaluation (default is 5), not applicable for similarity."
        print "\n\t--path=x"
        print "\t\tSpecify source folder with VMIs for evaluation (ignored for evaluation of reassembly)"
        print "\t\tMeta Files for all VMIs have to exist. Files in this folder are not manipulated."
        print ""

    def complete_evaluate(self, text, line, begidx, endidx):
        strings = line.split(" ")
        # complete first argument (option or function)
        if len(strings) == 2:
            # complete path
            if text.startswith("--path="):
                #return _complete_rel_path(text[7:])
                return  list( "--path=" + x for x in _complete_rel_path(text[7:]))
            # complete option
            elif text.startswith("-"):
                return _complete_arg_list(text, self._availableArgsEvaluateOptions)
            else:
                return _complete_arg_list(text, self._availableArgsEvaluateFunctions)
        # complete second argument (option or function)
        elif len(strings) == 3:
            # option
            if text.startswith("-"):
                # complete path
                if text.startswith("--path="):
                    return list("--path=" + x for x in _complete_rel_path(text[7:]))
                # complete option
                else:
                    return _complete_arg_list(text, self._availableArgsEvaluateOptions)
            # function
            else:
                return _complete_arg_list(text, self._availableArgsEvaluateFunctions)
        # complete third argument (function)
        elif len(strings) == 4:
            return _complete_arg_list(text, self._availableArgsEvaluateFunctions)
        else:
            return None

    def do_evaluateOLD(self,line):
        args = line.split()
        if len(args) < 2:
            print "Error: Command evaluate requires at least two arguments."
            return
        elif len(args) == 2:
            repetitions = 5
            func = args[0]
            path = args[1]
        elif len(args) == 3:
            if args[0].startswith("--repetitions="):
                try:
                    repetitions = int(args[0].rsplit("=",1)[1])
                except ValueError as e:
                    print "Error: %s is not a valid number" % args[0].rsplit("=",1)[1]
                    return
                except IndexError as e:
                    print "Error: expected \"--repetitions=x\", received \"%s\" instead." % args[0]
                    return
            else:
                print "Error: with three arguments, first has to be a valid option. \"%s\" was not recognized" % args[0]
                return
            func = args[1]
            path = args[2]
        else:
            print "Error: to many arguments"
            return

        # check if path is directory
        if not os.path.isdir(path):
            print "Error: \"%s\" is not a directory" % path
            return
        # check if path is relative
        if path.startswith("/"):
            print "Error: \"%s\" is not a valid path. Please try again with a path relative to the directory of this program." % path
            return

        # check if all meta files exist
        if not self.exp.verifySourceFolder(path):
            print "Error: Verification of source folder failed. See explanation above."
            return

        # check if evaluation folder exists
        if not os.path.isdir(StaticInfo.relPathLocalEvaluation):
            os.mkdir(StaticInfo.relPathLocalEvaluation)

        # check if evaluation folder is empty
        if len(os.listdir(StaticInfo.relPathLocalEvaluation)) > 0:
            print "Evaluation folder \"%s\" is not empty. Please back up any evaluation files in this folder and remove them." % StaticInfo.relPathLocalEvaluation
            return

        # start evaluation
        print "Evaluation"
        print "\tEvaluated Functionality:              " + func
        print "\tFolder with Images to evaluate:       " + path
        print "\tNumber of repetitions for evaluation: " + str(repetitions)

        if func == "similarity":
            print "\n"
            self.exp.evaluateSimBetweenAll(path)
        elif func == "decomposition1":
            print "This evaluation requires the local repository to be reset and the directory \"%s\" to be cleared." % StaticInfo.relPathLocalVMIFolder
            if self.do_reset("") and self.clearVmiFolder():
                print "\n"
                self.exp.evaluateDecomposition(path,repetitions, resetBeforeEachDecomposition=False)
        elif func == "decomposition2":
            print "This evaluation requires the local repository to be reset and the directory \"%s\" to be cleared." % StaticInfo.relPathLocalVMIFolder
            if self.do_reset("") and self.clearVmiFolder():
                print "\n"
                self.exp.evaluateDecomposition(path, repetitions, resetBeforeEachDecomposition=True)
        elif func == "reassembly":
            print "not implemented yet."
        else:
            print "Error: Functionality \"%s\" not recognized" % func

    def complete_evaluateOLD(self, text, line, begidx, endidx):
        strings = line.split(" ")
        # complete first argument (option or function)
        if len(strings) == 2:
            if text.startswith("-"):
                return _complete_arg_list(text, self._availableArgsEvaluateOptions)
            elif text != "":
                return _complete_arg_list(text, self._availableArgsEvaluateFunctions)
            else:
                return None
        # complete second argument (function or path)
        elif len(strings) == 3:
            # first argument was option, next has to be function
            if strings[1].startswith("-"):
                return _complete_arg_list(text, self._availableArgsEvaluateFunctions)
            # first argument was function, next has to be path
            else:
                return _complete_rel_path(text)
        # complete third argument
        elif len(strings) == 4:
            # second argument was function, next is path
            if strings[2] in self._availableArgsEvaluateFunctions:
                return _complete_rel_path(text)
            # second argument was path, no further args required
            else:
                return None
        else:
            return None

    def do_reset(self, line):
        """
        Reset the repository: Removes all base images, packages, user data and meta data.
        """
        print "Attention: This operation will reset the whole repository -> Baseimages, packages, user data and meta data will be removed!"
        answer = raw_input("\tAre you sure you want to continue, yes or no?")
        if answer == "yes":
            print "\tResetting repository..."
            self.exp.resetRepo()
            print "\tRepository reset."
            return True
        else:
            return False

    def clearVmiFolder(self):
        print "Attention: This operation will clear the directory \"%s\"!" % StaticInfo.relPathLocalVMIFolder
        answer = raw_input("\tAre you sure you want to continue, yes or no?")
        if answer == "yes":
            if os.path.isdir(StaticInfo.relPathLocalVMIFolder):
                shutil.rmtree(StaticInfo.relPathLocalVMIFolder)
            os.mkdir(StaticInfo.relPathLocalVMIFolder)
            print "\tDirectory cleared."
            return True
        else:
            return False

    def do_exit(self, line):
        """
        Exit the program
        """
        return True
back to top