It is currently Sat Aug 08, 2020 9:14 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: [Module] General Purpose Console Interface
PostPosted: Sun Oct 27, 2019 2:41 pm 
Offline
User
User

Joined: Wed Sep 10, 2014 1:40 am
Posts: 40
Location: United Kingdom
A library I created for quickly creating console interfaces, though can also be used as a game console, or any other use cases where parsing commands and arguments from a string is needed.

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:
setvar MyVar "Hello World"
print $MyVar


Built-in Help
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:
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:
; ====================================================================================================
; 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

_________________
Regards!

Mike


Top
 Profile  
Reply with quote  
 Post subject: Re: [Module] General Purpose Console Interface
PostPosted: Wed Oct 30, 2019 2:42 pm 
Offline
Addict
Addict
User avatar

Joined: Sun Nov 05, 2006 11:42 pm
Posts: 4699
Location: Lyon - France
Thanks for sharing 8)
I was wondering, what is the difference between your and windows console ? :oops:

_________________
ImageThe happiness is a road...
Not a destination


Top
 Profile  
Reply with quote  
 Post subject: Re: [Module] General Purpose Console Interface
PostPosted: Wed Oct 30, 2019 7:28 pm 
Offline
User
User

Joined: Wed Sep 10, 2014 1:40 am
Posts: 40
Location: United Kingdom
Kwai chang caine wrote:
Thanks for sharing 8)
I was wondering, what is the difference between your and windows console ? :oops:


It's merely a building block for quick creation of console applications where the programmer just wants a swift way to create action/command orientated apps.

For instance; If you wanted to create a console app which could do multiple things, rather than having to write a bunch of select statements, manually create a help command, you could just include this, and define the actions/commands as procedures, and fetch the arguments with module methods quickly, and (by enabling the help action) have it deliver a nicely formatted help display with your commands, hints on what arguments are needed.

Another use case could be if someone is writing a game where they wanted an in-game console for things like cheats, or developer commands. Just include the module, and it just saves a bit of time and allows for quick creation of actions.

I created it after it dawned on me I was constantly having to write the select/case logic, manually creating help providers, and it got finicky as the applications grew, so wrote this as a drop-in solution.

_________________
Regards!

Mike


Top
 Profile  
Reply with quote  
 Post subject: Re: [Module] General Purpose Console Interface
PostPosted: Thu Oct 31, 2019 11:23 am 
Offline
Addict
Addict
User avatar

Joined: Sun Nov 05, 2006 11:42 pm
Posts: 4699
Location: Lyon - France
Aaah ok :D
Thanks for your great explanation 8)

_________________
ImageThe happiness is a road...
Not a destination


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: Taz and 22 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye