CommandProcessor

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

 

CO2

ehemals "SirMO"

Betreff: CommandProcessor

BeitragSa, Aug 22, 2015 15:18
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo,

im Zuge eines privaten Projekts entstand ein Commandline-Processor, mit dem es möglich ist auf einfachstem Wege Commandline-Befehle und dessen Optionen aus einem String zu parsen und auszuführen. Zunächst der Quellcode, den ihr natürlich frei verwenden und verändern dürft BlitzMax: [AUSKLAPPEN]
Rem
Autor: Marius Otto
Datum: 20.08.2015
Beschr.: Commandline-Processor. Hiermit können Kommandozeilen-Parameter definiert und mit einer Funktion verknüpft werden
End Rem


Private
' ### FoundCmd ##################################################################################################################
Type TFoundCmd
' *** Instanz-Variablen **************************************************************************
Field Cmd:TCommand
Field AvailableFreeOpt:Int

' *** Konstruktion *******************************************************************************
Function Create:TFoundCmd(Cmd:TCommand)
If (Cmd = Null) Then Return Null

Local ReturnMe:TFoundCmd = New TFoundCmd
ReturnMe.Cmd = Cmd
ReturnMe.AvailableFreeOpt = (Cmd.CountOption(TCommand.ARG_STRING) + Cmd.CountOption(TCommand.ARG_NUMBER)) ' Alle frei wählbaren
Return ReturnMe
End Function

' *** Methoden ***********************************************************************************
Method Decrement:Byte()
Self.AvailableFreeOpt = Self.AvailableFreeOpt - 1

Return (Self.AvailableFreeOpt >= 0)
End Method

Method HasOption:Byte(Option:String)
' Prüfen, ob es sich um eine Special-Option handelt oder um eine normale
Local IsSpecial:Byte = Self.Cmd.HasSpecialOption(Option)

If (IsSpecial)
Return Decrement()
Else
Return Self.Cmd.HasNormalOption(Option)
EndIf
End Method
End Type

Public
' ### Helper ####################################################################################################################
Function IsANumber:Byte(Val:String)
If (Len(Val) = 0)
Return False
EndIf

For Local x:Int = 0 To (Len(Val) - 1)
Local ToAsc:Int = Asc(Mid(Val, (x + 1), 1))

If (ToAsc < 48 Or ToAsc > 57)
Return False
EndIf
Next

Return True
End Function

' ### Command ###################################################################################################################
Type TCommand
' *** Konstanten *********************************************************************************
Global ARG_STRING:String = "[str]"
Global ARG_NUMBER:String = "[num]"

' *** Instanz-Variablen **************************************************************************
Field CmdName:String
Field RegOptions:String[]
Field CmdExec:Byte(SetOpt:String[])

' *** Konstruktion *******************************************************************************
Function Create:TCommand(CmdName:String, RegOptions:String[], CmdExec:Byte(SetOpt:String[]))
Local ReturnMe:TCommand = New TCommand
ReturnMe.CmdName = CmdName
ReturnMe.RegOptions = RegOptions
ReturnMe.CmdExec = CmdExec
Return ReturnMe
End Function

' *** Methoden ***********************************************************************************
Method CallFunc:Byte(Options:String[])
If (Self.CmdExec = Null Or Not Self.CmdExec(Options))
Return False ' Error
Else
Return True ' Everything fine
EndIf
End Method

Method HasSpecialOption:Byte(Option:String)
For Local x:String = EachIn Self.RegOptions
If ((x = TCommand.ARG_STRING) Or (x = TCommand.ARG_NUMBER And IsANumber(Trim(Option))))
Return True
EndIf
Next

Return False
End Method

Method HasNormalOption:Byte(Option:String)
For Local x:String = EachIn Self.RegOptions
If ((Trim(Lower(x)) = Trim(Lower(Option))))
Return True
EndIf
Next

Return False
End Method

Method CountOption:Int(Option:String)
Local RetVal:Int = 0

For Local x:String = EachIn Self.RegOptions
If (Trim(Lower(x)) = Trim(Lower(Option)))
RetVal = RetVal + 1
EndIf
Next

Return RetVal
End Method
End Type

' ### CommandProcessor ##########################################################################################################
Type TCommandProcessor
' *** Instanz-Variablen **************************************************************************
Field RegisteredCmds:TList

' *** Konstruktion *******************************************************************************
Function Create:TCommandProcessor()
Local ReturnMe:TCommandProcessor = New TCommandProcessor
ReturnMe.RegisteredCmds = CreateList()
Return ReturnMe
End Function

' *** Methoden ***********************************************************************************
Method AddCommand(Cmd:TCommand)
If (Cmd <> Null)
If (Cmd.CmdName = "")
DebugLog("Can't add command with no name")
Return
EndIf

If (Cmd.CmdExec = Null)
DebugLog("Can't add command without function")
Return
EndIf

ListAddLast(Self.RegisteredCmds, Cmd)
EndIf
End Method

Method FindClosingQuote:String(Arr:String[], MomPos:Int, Skip:Int Var)
If (MomPos < 0 Or MomPos >= Len(Arr)) ' Out of Bounds
Return ""
EndIf

Local RetVal:String = ""

For Local x:Int = MomPos To (Len(Arr) - 1)
Local str:String = Arr[x]
Local add:String = ""

If (x > MomPos)
add = " "
EndIf

RetVal = RetVal + add + str

If (Right(str, 1) = Chr(34))
Exit
EndIf
Next

Skip = x

Return RetVal
End Method

Method PrepareStringArgument:String(Arr:String[], MomPos:Int, Skip:Int Var)
Local str:String = FindClosingQuote(Arr, MomPos, Skip)

Return Replace(str, Chr(34), "")
End Method

Method Tokenize:String[](Inp:String)
' Split Inp to strings including texts
' And return that array
Local InpSplit:String[] = Inp.Split("") ' Splitting String at the spaces + delete mehr spaces
Local ParsedInpList:TList = CreateList()

For Local x:Int = 0 To (Len(InpSplit) - 1)
Local str:String = Trim(InpSplit[x])

If (Left(str, 1) = Chr(34)) ' "
' We have to find the other "
ListAddLast(ParsedInpList, PrepareStringArgument(InpSplit, x, x))
Else
ListAddLast(ParsedInpList, str) ' Add the string
EndIf
Next

Local RetVal:String[CountList(ParsedInpList)]
Local Index:Int = 0

For Local ParsedStr:String = EachIn ParsedInpList
RetVal[Index] = ParsedStr
Index = Index + 1
Next

Return RetVal
End Method

Method FindCmd:TCommand(Token:String)
For Local x:TCommand = EachIn Self.RegisteredCmds
If (Trim(Lower(Token)) = Trim(Lower(x.CmdName)))
Return x
EndIf
Next

Return Null
End Method

Method GetArgList:TList(Token:String[])
Local FoundCmds:TList = CreateList() '<TList<String>>

For Local x:Int = 0 To (Len(Token) - 1)
Local AddMe:TList = CreateList()

Local ThisToken:String = Trim(Lower(Token[x]))

If (ThisToken = "")
Continue ' no empty commands
EndIf

Local ThisCmd:TFoundCmd = TFoundCmd.Create(Self.FindCmd(ThisToken))

If (ThisCmd = Null)
DebugLog("Command not found: '" + ThisToken + "'")
Continue
EndIf

ListAddLast(AddMe, ThisCmd.Cmd.CmdName)

' Now we have the command, so we have to find its options:
Local NextCmd:Byte = False
Repeat
x = x + 1

If (x >= Len(Token))
NextCmd = True
Continue
EndIf

Local ThisOption:String = Trim(Lower(Token[x]))

If (Self.FindCmd(ThisOption)) ' That is another arg
NextCmd = True
Else ' yay, it's an option
If (ThisCmd.HasOption(ThisOption)) ' And it's registered
ListAddLast(AddMe, Token[x])
Else
DebugLog("Option '" + ThisOption + "' is not registered to command '" + (ThisCmd.Cmd.CmdName) + "'")
EndIf
EndIf
Until NextCmd

x = x - 1

' At the end we have to add them to the list:
ListAddLast(FoundCmds, AddMe)
Next

Return FoundCmds
End Method

Method Parse(Inp:String)
' Parse the inp by tokenizing it and calling the command's function-ptr
' Plus logging stuff
Local Token:String[] = Self.Tokenize(Inp)

If (Len(Token) = 0) ' There are no Arguments
DebugLog("No commands found")
Return
EndIf

' Now that we have the tokenized arguments we can make arguments and their params together:
Local ArgsAndOptions:TList = GetArgList(Token)

If (CountList(ArgsAndOptions) = 0)
DebugLog("Not able to parse '" + Inp + "'")
Return
EndIf

' Going through every found cmd:
For Local x:TList = EachIn ArgsAndOptions
Local CmdName:String = String(x.First()) ' The first entry is the cmd itself.
Local ArgLen:Int = (CountList(x) - 1)
Local CmdArgs:String[] = Null

If (ArgLen > 0)
CmdArgs = New String[ArgLen]

For Local Index:Int = 1 To ArgLen
CmdArgs[(Index - 1)] = String(x.ValueAtIndex(Index))
Next
EndIf

Local ThisCmd:TCommand = Self.FindCmd(CmdName)

If (ThisCmd = Null)
DebugLog("Command not found '" + CmdName + "'")
Continue
EndIf

If (Not ThisCmd.CallFunc(CmdArgs))
DebugLog("Failed to execute '" + CmdName + "'")
EndIf
Next
End Method
End Type

' ### Functions #################################################################################################################
Function ParseFile(ComProc:TCommandProcessor, File:String)
If (ComProc = Null)
DebugLog("Parser not available")
Return
EndIf

Local Stream:TStream = ReadFile(File)

If (Stream = Null)
DebugLog("Unable to open file '" + File + "'")
Return
EndIf

DebugLog("Parsing file '" + File + "'...")

Repeat
Local Line:String = Trim(Stream.ReadLine())

If (Line = "" Or Left(Line, 1) = "#") ' Nothing or Comment
Continue
EndIf

ComProc.Parse(Line)
Until Eof(Stream)

DebugLog("Finished parsing")

CloseFile(Stream)
End Function


Der Basistype ist TCommandProcessor, für den verschiedene Commands und deren Optionen definiert werden können. Mit TCommandProcessor.Parse() kann ein angegebener String geparsed werden, die bekannten Commands werden ausgeführt, nicht bekannte erzeugen eine Fehlermeldung im DebugLog(). Mit der Funktion ParseFile() kann eine Datei geparsed werden.

Ein Beispiel BlitzMax: [AUSKLAPPEN]
Local CmdProc:TCommandProcessor = TCommandProcessor.Create()

CmdProc.AddCommand(TCommand.Create("add", [TCommand.ARG_NUMBER, TCommand.ARG_NUMBER], cmd_add))
CmdProc.AddCommand(TCommand.Create("sub", [TCommand.ARG_NUMBER, TCommand.ARG_NUMBER], cmd_sub))
CmdProc.AddCommand(TCommand.Create("print", [TCommand.ARG_STRING], cmd_print))
CmdProc.AddCommand(TCommand.Create("coolstuff", ["-f", "-g", "-h"], cmd_coolstuff))

Local Try2ParseMeErrors:String = "unregisteredcmd add achar 5 sub 2 53 7 add add print " + Chr(34) + "Hello World!" + Chr(34) + " coolstuff -f -g unregisteredarg"
DebugLog(Try2ParseMeErrors)

CmdProc.Parse(Try2ParseMeErrors)

DebugLog("*************************************************")

Local Try2ParseMeOk:String = "add 3 5 sub 6 3 print " + Chr(34) + "Dies wird funktionieren" + Chr(34) + " coolstuff -f coolstuff -f -g -h"
DebugLog(Try2ParseMeOk)

CmdProc.Parse(Try2ParseMeOk)

End

' *** Functions ***********************************************************************************
Rem
Die Funktionen sollten true zurückgeben um anzuzeigen, dass sie fehlerfrei durchgelaufen sind. Geben
Sie false zurück wird vom CommandProcessor eine Fehlermeldung generiert.
End Rem

Function cmd_add:Byte(Options:String[])
If (Len(Options) = 2)
DebugLog("add: " + Options[0] + " + " + Options[1] + " = " + (Int(Options[0]) + Int(Options[1])))
Return True
Else
Return False
EndIf
End Function

Function cmd_sub:Byte(Options:String[])
If (Len(Options) = 2)
DebugLog("sub: " + Options[0] + " - " + Options[1] + " = " + (Int(Options[0]) - Int(Options[1])))
Return True
Else
Return False
EndIf
End Function

Function cmd_print:Byte(Options:String[])
If (Len(Options) = 1)
DebugLog("print: " + Options[0])
Return True
Else
Return False
EndIf
End Function

Function cmd_coolstuff:Byte(Options:String[])
Local fisSet:Byte = False
Local gisSet:Byte = False
Local hisSet:Byte = False

For Local x:String = EachIn Options
If (Trim(Lower(x)) = "-h")
hisSet = True
ElseIf (Trim(Lower(x)) = "-g")
gisSet = True
ElseIf (Trim(Lower(x)) = "-f")
fisSet = True
EndIf
Next

If (fisSet)
Print "Yay, it's -f"
EndIf

If (gisSet)
Print "I'm doing some stuff"
EndIf

If (hisSet)
Print "42"
EndIf

Return True
End Function


Ausgabe des Beispiels Code: [AUSKLAPPEN]
DebugLog:unregisteredcmd add achar 5 sub 2 53 7 add add print "Hello World!" coolstuff -f -g unregisteredarg
DebugLog:Command not found: 'unregisteredcmd'
DebugLog:Option 'achar' is not registered to command 'add'
DebugLog:Option '7' is not registered to command 'sub'
DebugLog:Option 'unregisteredarg' is not registered to command 'coolstuff'
DebugLog:Failed to execute 'add'
DebugLog:sub: 2 - 53 = -51
DebugLog:Failed to execute 'add'
DebugLog:Failed to execute 'add'
DebugLog:print: Hello World!
Yay, it's -f
I'm doing some stuff
DebugLog:*************************************************
DebugLog:add 3 5 sub 6 3 print "Dies wird funktionieren" coolstuff -f coolstuff -f -g -h
DebugLog:add: 3 + 5 = 8
DebugLog:sub: 6 - 3 = 3
DebugLog:print: Dies wird funktionieren
Yay, it's -f
Yay, it's -f
I'm doing some stuff
42


Ich denke es sollte selbsterklärend sein, falls nicht, fragen... Wer es brauchen kann darf es wie oben erwähnt frei verwenden.
mfG, CO²

Sprachen: BlitzMax, C, C++, C#, Java
Hardware: Windows 7 Ultimate 64-Bit, AMX FX-6350 (6x3,9 GHz), 32 GB RAM, Nvidia GeForce GTX 750 Ti

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group