Feedback welcome!
Commands
This library is designed to accept inputs in the form of "command arg1 arg2 arg3". If you need to use spaces in an argument, then encapsulate in double quotes.
Variables
In addition to fixed argument inputs, you can define variables which can be used in place of command/arguments by prefixing with '$'. I.e. $myvar
Setting a variable can be done using the inbuilt command 'setvar'.
Example
Code: Select all
setvar MyVar "Hello World"
print $MyVar
Enable the built-in help by calling GCon::EnableHelp(). This will provide a nicely formatted table of commands, argument hints and descriptions.
Output
You can specify your own print output handler (shown in example) callback which directs information or error messages wherever you want (Useful if you have your own in-game console as an example)
Built-in Commands
You can disable these by using GCon::UnregisterCommand()
Code: Select all
setvar [name] [value] - Defined/set a variable value.
rmvar [name] - Remove a variable.
print [msg].. - Print each argument in a new line.
help - Display help information. Can be enabled/disabled. Disabled by default.
Code: Select all
; ====================================================================================================
; Title: General Purpose Console-like Interface
; Author: IndigoFuzz
; Revision: 1
; ====================================================================================================
DeclareModule GCon
Enumeration MessageType
#Message_Info
#Message_Error
EndEnumeration
Structure sCallInfo
command.s
argCount.i
List args.s()
EndStructure
Prototype pCommandHandler(*CallInfo)
Prototype pMessageHandler(MessageType, Message.s)
; Configuration
Declare EnableHelp(Enable = #True)
; For printing to the defined message handler
Declare PrintInfo(Info.s)
Declare PrintError(Error.s)
; Fetch arguments
Declare.s AsString(*CallInfo.sCallInfo, Index)
Declare.i AsInteger(*CallInfo.sCallInfo, Index)
Declare.a AsBool(*CallInfo.sCallInfo, Index)
; Command Methods
Declare CommandNameValid(Name.s)
Declare RegisterCommand(Command.s, Arguments.s, Description.s, *Handler.pCommandHandler)
Declare UnregisterCommand(Command.s)
; Input Methods
Declare ParseProgramParams()
Declare ParseString(Input.s)
; Variable Methods
Declare SetVar(VarName.s, Value.s)
Declare.s GetVar(VarName.s, DefaultValue.s = "")
Declare VarExists(VarName.s)
Declare RemoveVar(VarName.s)
; Defines a message handler
Declare SetMessageCallback(*MessageHandler.pMessageHandler)
EndDeclareModule
Module GCon
Structure sCommandInfo
command.s
description.s
arg.s
*handler.pCommandHandler
EndStructure
Structure sGCon
Map m_command.sCommandInfo()
Map m_var.s()
*m_messageHandler.pMessageHandler
m_regEx.i
m_enableHelp.a
EndStructure
Global gGCon.sGCon
gGCon\m_regEx = CreateRegularExpression(#PB_Any, ~"(?:(['\"" + "])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))")
Procedure _InbuiltHelp(*CallInfo)
Protected NewList cmd.s()
Protected maxCmdLen.i, maxArgLen.i
With gGCon
ForEach \m_command()
AddElement(cmd()) : cmd() = \m_command()\command
If maxCmdLen < Len(cmd())
maxCmdLen = Len(cmd())
EndIf
If maxArgLen < Len(\m_command()\arg)
maxArgLen = Len(\m_command()\arg)
EndIf
Next
SortList(cmd(), #PB_Sort_Ascending)
PrintInfo("")
PrintInfo("Available Commands:")
PrintInfo("")
ForEach cmd()
If \m_command(cmd())\description <> ""
PrintInfo(" " + RSet(cmd(), maxCmdLen + 2) + " " + LSet(\m_command(cmd())\arg, maxargLen + 1) + " - " + \m_command(cmd())\description)
Else
PrintInfo(" " + cmd())
EndIf
Next
PrintInfo("")
EndWith
FreeList(cmd())
EndProcedure
; Built In Commands
Procedure _OnSetVar(*Info.GCon::sCallInfo)
If *Info\argCount = 2
Protected.s varName = LCase(AsString(*Info, 0))
Protected.s varValue = AsString(*Info, 1)
If SetVar(varName, varValue) = #False
PrintError("Could not set variable. Invalid variable name '" + varName + "'.")
EndIf
Else
PrintError(*Info\command + " expects 2 arguments.")
EndIf
EndProcedure
Procedure _OnRmVar(*Info.GCon::sCallInfo)
ForEach *Info\args()
RemoveVar(*Info\args())
Next
EndProcedure
Procedure _OnPrint(*Info.GCon::sCallInfo)
ForEach *Info\args()
PrintInfo(*Info\args())
Next
EndProcedure
RegisterCommand("setvar", "name, value", "Define/Set a variable.", @_OnSetVar())
RegisterCommand("rmvar", "name", "Remove a variable.", @_OnSetVar())
RegisterCommand("print", "message","Print a message.", @_OnPrint())
Procedure _HandleVarTokens(List Tokens.s())
Protected.s varname
ForEach Tokens()
ForEach gGCon\m_var()
If FindString(Tokens(), "$" + MapKey(gGCon\m_var()), 1, #PB_String_NoCase)
Tokens() = ReplaceString(Tokens(), "$" + MapKey(gGCon\m_var()), gGcon\m_var(), #PB_String_NoCase)
EndIf
Next
Next
ProcedureReturn #True
EndProcedure
Procedure _ParseTokens(List Tokens.s())
Protected callInfo.sCallInfo, success.a
If ListSize(Tokens()) > 0
If _HandleVarTokens(Tokens()) = #False
ProcedureReturn #False
EndIf
FirstElement(Tokens())
Protected.s cmd = LCase(Tokens())
If gGCon\m_enableHelp
Select cmd
Case "help", "?", "-h", "--help"
_InbuiltHelp(0)
ProcedureReturn #True
EndSelect
EndIf
DeleteElement(Tokens())
With callInfo
ForEach Tokens()
AddElement(\args()) : \args() = Tokens()
Next
\argCount = ListSize(Tokens())
EndWith
With gGCon
If FindMapElement(\m_command(), cmd)
Protected *handler.pCommandHandler = \m_command(cmd)\handler
If *handler
callInfo\command = cmd
*handler(@callInfo)
success = #True
EndIf
EndIf
EndWith
If success = 0
If gGCon\m_enableHelp
PrintError("Command '" + cmd + "' is not recognised. Use 'help' for assistance.")
Else
PrintError("Command '" + cmd + "' is not recognised.")
EndIf
ProcedureReturn #False
EndIf
Else
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndProcedure
Procedure EnableHelp(Enable = #True)
If Enable
gGCon\m_enableHelp = #True
Else
gGCon\m_enableHelp = #False
EndIf
EndProcedure
Procedure PrintInfo(Info.s)
If gGCon\m_messageHandler <> #Null
gGCon\m_messageHandler(#Message_Info, Info)
EndIf
EndProcedure
Procedure PrintError(Error.s)
If gGCon\m_messageHandler <> #Null
gGCon\m_messageHandler(#Message_Error, Error)
gGCon\m_messageHandler(#Message_Error, "")
EndIf
EndProcedure
Procedure.s AsString(*CallInfo.sCallInfo, Index)
If *CallInfo And Index > -1
With *CallInfo
If \argCount > Index
SelectElement(\args(), Index)
ProcedureReturn \args()
EndIf
EndWith
EndIf
ProcedureReturn ""
EndProcedure
Procedure.i AsInteger(*CallInfo.sCallInfo, Index)
If *CallInfo And Index > -1
With *CallInfo
If \argCount > Index
SelectElement(\args(), Index)
ProcedureReturn Val(\args())
EndIf
EndWith
EndIf
ProcedureReturn 0
EndProcedure
Procedure.a AsBool(*CallInfo.sCallInfo, Index)
If *CallInfo And Index > -1
With *CallInfo
If \argCount > Index
SelectElement(\args(), Index)
Select LCase(\args())
Case "true", "yes", "1", "on"
ProcedureReturn #True
EndSelect
EndIf
EndWith
EndIf
ProcedureReturn #False
EndProcedure
Procedure CommandNameValid(Name.s)
Protected.s validChars = "abcdefghijklmnopqrstuvwxyz0123456789_-."
Protected thisChar.s, ix.i
Name = LCase(Name)
If Len(Name) > 0
For ix = 1 To Len(Name)
If FindString(validChars, Mid(Name, ix, 1)) = 0
ProcedureReturn #False
EndIf
Next
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure RegisterCommand(Command.s, Arguments.s, Description.s, *Handler.pCommandHandler)
If CommandNameValid(Command) And *Handler <> #Null
Command = LCase(Command)
With gGCon
\m_command(Command)\command = Command
\m_command(Command)\arg = Arguments
\m_command(Command)\description = Description
\m_command(Command)\handler = *Handler
ProcedureReturn #True
EndWith
EndIf
ProcedureReturn #False
EndProcedure
Procedure UnregisterCommand(Command.s)
Command = LCase(Command)
If DeleteMapElement(gGCon\m_command(), Command)
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure SetVar(VarName.s, Value.s)
VarName = LCase(VarName)
If CommandNameValid(VarName)
gGCon\m_var(VarName) = Value
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure.s GetVar(VarName.s, DefaultValue.s = "")
VarName = LCase(VarName)
If FindMapElement(gGCon\m_var(), VarName)
ProcedureReturn gGCon\m_var(VarName)
EndIf
ProcedureReturn DefaultValue
EndProcedure
Procedure VarExists(VarName.s)
VarName = LCase(VarName)
If FindMapElement(gGCon\m_var(), VarName)
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure RemoveVar(VarName.s)
VarName = LCase(VarName)
DeleteMapElement(gGCon\m_var(), VarName)
EndProcedure
Procedure ParseProgramParams()
Protected ix.i
Protected NewList Tok.s()
For ix = 0 To CountProgramParameters() - 1
AddElement(Tok()) : Tok() = ProgramParameter(ix)
Next
If ListSize(Tok()) > 0
ProcedureReturn _ParseTokens(Tok())
Else
ProcedureReturn #True
EndIf
EndProcedure
Procedure ParseString(Input.s)
Protected NewList Tok.s()
Input = Trim(Input)
ExamineRegularExpression(gGCon\m_regEx, Input)
While NextRegularExpressionMatch(gGCon\m_regEx)
AddElement(Tok()) : Tok() = ReplaceString(RegularExpressionMatchString(gGCon\m_regEx), Chr(34), "")
Wend
If ListSize(Tok()) > 0
ProcedureReturn _ParseTokens(Tok())
Else
ProcedureReturn #True
EndIf
EndProcedure
Procedure SetMessageCallback(*MessageHandler.pMessageHandler)
With gGCon
\m_messageHandler = *MessageHandler
EndWith
EndProcedure
EndModule
;-
;- ====== SAMPLE ======
;-
CompilerIf #PB_Compiler_IsMainFile
; Open Console
OpenConsole("Interactive GCon")
EnableGraphicalConsole(#True)
; GCon Output Handler
Procedure MessageCallback(MessageType, Message.s)
Select MessageType
Case GCon::#Message_Info
ConsoleColor(7, 0)
Case GCon::#Message_Error
ConsoleColor(12, 0)
EndSelect
PrintN(Message)
ConsoleColor(7, 0) ; Revert to Default
EndProcedure
GCon::SetMessageCallback(@MessageCallback())
; Message Box Command
Procedure MyMessageBox(*Info.GCon::sCallInfo)
Protected.s caption, body
caption = "Information"
Select *Info\argCount
Case 1
body = GCon::AsString(*Info, 0)
Case 2
body = GCon::AsString(*Info, 0)
caption = GCon::AsString(*Info, 1)
Default
GCon::PrintError("'msgbox' expects 1-2 arguments.")
ProcedureReturn #False
EndSelect
MessageRequester(caption, body)
EndProcedure
GCon::RegisterCommand("msgbox", "body [,caption]", "Open a Message Box.", @MyMessageBox())
; Run File Command
Procedure MyRun(*Info.GCon::sCallInfo)
Protected.s file
Select *Info\argCount
Case 1
file = GCon::AsString(*Info, 0)
If FileSize(file) > -1
Define.i hFile = ReadFile(#PB_Any, file), lineIx.i
Define.s line
While Eof(hFile) = #False
lineIx + 1
line = ReadString(hFile)
If GCon::ParseString(line) = #False
GCon::PrintError("Error on line " + Str(lineIx) + ". (" + line + ")")
Break
EndIf
Wend
CloseFile(hFile)
Else
GCon::PrintError("File '" + file + "' does not exist.")
EndIf
Default
GCon::PrintError("'run' expects 1 argument.")
ProcedureReturn #False
EndSelect
EndProcedure
GCon::RegisterCommand("run", "file", "Run a file containing commands.", @MyRun())
; Enable Help Feature
GCon::EnableHelp()
; Enter Interactive Loop
Define.s input
PrintN("Interactive console example. Use 'help' for a list of commands, or 'end' to terminate the program.")
PrintN("")
Repeat
Print("> ")
input = Input()
If LCase(input) = "end"
Break
EndIf
GCon::ParseString(input)
ForEver
End 0
CompilerEndIf