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] [EINKLAPPEN]
Private
Type TFoundCmd Field Cmd:TCommand Field AvailableFreeOpt:Int 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)) Return ReturnMe End Function Method Decrement:Byte() Self.AvailableFreeOpt = Self.AvailableFreeOpt - 1 Return (Self.AvailableFreeOpt >= 0) End Method Method HasOption:Byte(Option:String) Local IsSpecial:Byte = Self.Cmd.HasSpecialOption(Option) If (IsSpecial) Return Decrement() Else Return Self.Cmd.HasNormalOption(Option) EndIf End Method End Type
Public
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
Type TCommand Global ARG_STRING:String = "[str]" Global ARG_NUMBER:String = "[num]"
Field CmdName:String Field RegOptions:String[] Field CmdExec:Byte(SetOpt:String[]) 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 Method CallFunc:Byte(Options:String[]) If (Self.CmdExec = Null Or Not Self.CmdExec(Options)) Return False Else Return True 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
Type TCommandProcessor Field RegisteredCmds:TList
Function Create:TCommandProcessor() Local ReturnMe:TCommandProcessor = New TCommandProcessor ReturnMe.RegisteredCmds = CreateList() Return ReturnMe End Function 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)) 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) Local InpSplit:String[] = Inp.Split("") 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)) ListAddLast(ParsedInpList, PrepareStringArgument(InpSplit, x, x)) Else ListAddLast(ParsedInpList, str) 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() For Local x:Int = 0 To (Len(Token) - 1) Local AddMe:TList = CreateList() Local ThisToken:String = Trim(Lower(Token[x]))
If (ThisToken = "") Continue EndIf Local ThisCmd:TFoundCmd = TFoundCmd.Create(Self.FindCmd(ThisToken)) If (ThisCmd = Null) DebugLog("Command not found: '" + ThisToken + "'") Continue EndIf ListAddLast(AddMe, ThisCmd.Cmd.CmdName) 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)) NextCmd = True Else If (ThisCmd.HasOption(ThisOption)) ListAddLast(AddMe, Token[x]) Else DebugLog("Option '" + ThisOption + "' is not registered to command '" + (ThisCmd.Cmd.CmdName) + "'") EndIf EndIf Until NextCmd x = x - 1 ListAddLast(FoundCmds, AddMe) Next Return FoundCmds End Method Method Parse(Inp:String) Local Token:String[] = Self.Tokenize(Inp) If (Len(Token) = 0) DebugLog("No commands found") Return EndIf
Local ArgsAndOptions:TList = GetArgList(Token) If (CountList(ArgsAndOptions) = 0) DebugLog("Not able to parse '" + Inp + "'") Return EndIf For Local x:TList = EachIn ArgsAndOptions Local CmdName:String = String(x.First()) 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
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) = "#") 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] [EINKLAPPEN] 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
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] [EINKLAPPEN] 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.
|