Page 1 of 1

cCMDL. Commandline Class.

Posted: Sat Nov 03, 2007 2:03 pm
by Hroudtwolf
Hi,

I made a little commandline tool-class for one of my projects.
With it, it is very easy to get/check arguments of the current commandline.

@Procedural developers
It is not necessary to start flamewars against sense of OOP ;-)


Code: Select all

; PureBasic-Lounge.de
; Author: Hroudtwolf
; Date: 03. November 2007
; OS: Windows, Linux, Mac
; Demo: Yes


Interface IcCMDL
   IsArg       (sKey.s)
   GetArg.s    (lIndex.l)
   GetArgIndex (sKey.s)
   CountArgs   ()
   Done        ()
EndInterface

Structure cCMDL 
   *VTABLE
   
   *ArgList
   lArgs    .l
EndStructure

Structure tCMD
   sValue .s
   lHash  .l
EndStructure

; **************************************************************
; * Internal function
; **************************************************************

Procedure.l _cCMDL_Hash (sString.s)
   Protected *Source    .CHARACTER = @sString
   Protected lHash      .l         = #Null
   Protected lX         .l         = #Null
   
   While *Source\c
      lHash = (lHash << 4) + *Source\c
      lX = lHash & $F0000000
      *Source + SizeOf (CHARACTER)
   Wend
   
   ProcedureReturn lHash
EndProcedure 

; **************************************************************
; * *Obj\IsArg (sKey.s)
; **************************************************************
; * Checks whether a key exist in commandline arguments.
; * 
; * sKey             - In Example "/debug".
; **************************************************************
; * Returns:
; * True/False
; **************************************************************
Procedure cCMDL_IsArg (*This.cCMDL , sKey.s)
   Protected lCount     .l
   Protected lKeyHash   .l = _cCMDL_Hash (sKey)
   Protected *CurrentKey.tCMD
   
   
   For lCount = 0 To *This\lArgs - 1
      *CurrentKey = *This\ArgList + (lCount * SizeOf (tCMD))
      If lKeyHash = *CurrentKey\lHash
         ProcedureReturn #True
      EndIf
   Next lCount
   
   ProcedureReturn #False
EndProcedure

; **************************************************************
; * *Obj\GetArg (lIndex.l)
; **************************************************************
; * Retrieves an argument from given index.
; * 
; * lIndex           - The index of the argument you want to get
; **************************************************************
; * Returns:
; * Argument string
; **************************************************************
Procedure.s cCMDL_GetArg (*This.cCMDL , lIndex.l)
   Protected *CurrentKey.tCMD
   
   If lIndex < 0 Or lIndex > *This\lArgs - 1
      ProcedureReturn  ""
   EndIf
   
   *CurrentKey = *This\ArgList + (lIndex * SizeOf (tCMD))
   ProcedureReturn *CurrentKey\sValue
EndProcedure

; **************************************************************
; * *Obj\GetArgIndex (sKey.s)
; **************************************************************
; * Retrieves the index of a given argument key.
; * 
; * sKey             - In Example "/debug".
; **************************************************************
; * Returns:
; * LONG | Index number
; **************************************************************
Procedure cCMDL_GetArgIndex (*This.cCMDL , sKey.s)
   Protected lCount     .l
   Protected lKeyHash   .l = _cCMDL_Hash (sKey)
   Protected *CurrentKey.tCMD
   
   
   For lCount = 0 To *This\lArgs - 1
      *CurrentKey = *This\ArgList + (lCount * SizeOf (tCMD))
      If lKeyHash = *CurrentKey\lHash
         ProcedureReturn lCount
      EndIf
   Next lCount
   
   ProcedureReturn -1
EndProcedure

; **************************************************************
; * *Obj\CountArgs ()
; **************************************************************
; * Retrieves the number if commandline arguments
; * 
; **************************************************************
; * Returns:
; * LONG
; **************************************************************
Procedure cCMDL_CountArgs (*This.cCMDL)
   ProcedureReturn *This\lArgs 
EndProcedure

; **************************************************************
; * *Obj\Done ()
; **************************************************************
; * Releases the whole object.
; * 
; **************************************************************
; * Returns:
; * null
; **************************************************************
Procedure cCMDL_Done (*This.cCMDL)
   FreeMemory (*This\ArgList)
   FreeMemory (*This)
   ProcedureReturn #Null
EndProcedure
   
; **************************************************************
; * Constructor
; **************************************************************
ProcedureDLL CreateObject_cCMDL ()
   Protected *This   .cCMDL = AllocateMemory (SizeOf (cCMDL))
   Protected lCountOf.l     = CountProgramParameters ()
   Protected lCount  .l
   Protected *TempLst.tCMD
   Static    *VTABLE_cCMDL
    
   If Not lCountOf
      FreeMemory (*This)
      ProcedureReturn #Null
   EndIf 
      
   If Not *VTABLE_cCMDL
      *VTABLE_cCMDL = AllocateMemory (SizeOf (IcCMDL ))
      If Not *VTABLE_cCMDL
         ProcedureReturn #Null
      EndIf
      PokeL (*VTABLE_cCMDL + OffsetOf (IcCMDL\IsArg       ()) , @cCMDL_IsArg         ())
      PokeL (*VTABLE_cCMDL + OffsetOf (IcCMDL\GetArg      ()) , @cCMDL_GetArg        ())
      PokeL (*VTABLE_cCMDL + OffsetOf (IcCMDL\GetArgIndex ()) , @cCMDL_GetArgIndex   ())
      PokeL (*VTABLE_cCMDL + OffsetOf (IcCMDL\CountArgs   ()) , @cCMDL_CountArgs     ()) 
      PokeL (*VTABLE_cCMDL + OffsetOf (IcCMDL\Done        ()) , @cCMDL_Done          ())  
   EndIf
   
   *This\VTABLE = *VTABLE_cCMDL
   
   For lCount = 0 To lCountOf - 1
      If Not *TempLst
         *This\ArgList   = AllocateMemory (SizeOf (tCMD))
         *TempLst        = *This\ArgList
         *TempLst\sValue = ProgramParameter (lCount)
         *TempLst\lHash  = _cCMDL_Hash (*TempLst\sValue)
         Else
         *TempLst = ReAllocateMemory (*This\ArgList , (lCount + 1) * SizeOf (tCMD))
         If Not *TempLst
            If *This\ArgList
               FreeMemory (*This\ArgList)
            EndIf
            FreeMemory (*This)
            ProcedureReturn #Null
         EndIf
         *This\ArgList = *TempLst
         *TempLst      = *This\ArgList + (lCount * SizeOf (tCMD))
         *TempLst\sValue = ProgramParameter (lCount)
         *TempLst\lHash  = _cCMDL_Hash (*TempLst\sValue)
      EndIf
   Next lCount
   
   *This\lArgs = lCountOf
   
   ProcedureReturn *This
EndProcedure

;-------------- Test -------------------------------------------------

; Put '/debug /load "myfile.txt"' to your commandline for example.

*test.IcCMDL = CreateObject_cCMDL ()
OpenConsole ()
   If *test\IsArg ("/debug")
      PrintN ("Debug mode: On")
      Else
      PrintN ("Debug mode: Off")
   EndIf
   If *test\IsArg ("/load")
      lIndex = *test\GetArgIndex ("/load")
      PrintN ("Loading file: " + *test\GetArg (lIndex + 1))
   EndIf
   Input ()
CloseConsole ()
Best regards

Wolf

Posted: Sat Nov 03, 2007 2:08 pm
by srod
Very nice indeed. This was to be one of the next classes I was going to write in my conversion to oop, but you've saved me the bother. :)

I'll add this to my collection of utility classes.

Thanks.

Posted: Sat Nov 03, 2007 3:18 pm
by akj
Please forgive ny ignorance, but what are the advantages in using this commandline tool class rather than the built-in PB commands CountProgramParameters() and ProgramParameter() ?

Posted: Sat Nov 03, 2007 6:10 pm
by Hroudtwolf
@srod

Thx.
Your'e welcome :)

Please forgive ny ignorance,...
I don't see any ignorance.
I think you doesn't look in the source. ^^ *Joke*

Sure..., it's better to show you the little advantage of this class in two examples.

Maybe, you want to look whether keys are set in the commandline (in Example: /debug & /color)....
Procedure with ProgramParameter ():

Code: Select all

If Not CountProgramParameters ()
   End
EndIf
While lCount < CountProgramParameters () -1
   If ProgramParameter (lCount) = "/debug"
      Debug "Tadarata. Key '/debug' was set."
   EndIf
   If ProgramParameter (lCount) = "/color"
      Debug "Tadarata. Key '/debug' was set."
   EndIf
   lCount + 1
Wend
Procedure with my class:

Code: Select all

*test.IcCMDL = CreateObject_cCMDL ()
If *test\IsArg ("/debug")
   Debug "Tadarata. Key '/debug' was set."
EndIf
If *test\IsArg ("/color")
   Debug "Tadarata. Key '/color' was set."
EndIf 
In addition, it is very simple to determine the index of any key.
It's just a little assistance.


Best regards

Wolf

Posted: Sat Nov 03, 2007 7:17 pm
by akj
@Hroudtwolf
Thanks for your reply. However your COM example is bugged as it crashes with "Invalid memory access" if no command line parameters are supplied. I think it should be changed to:

Code: Select all

*test.IcCMDL = CreateObject_cCMDL ()
If *test
  If *test\IsArg ("/debug")
     Debug "Tadarata. Key '/debug' was set."
  EndIf
  If *test\IsArg ("/color")
     Debug "Tadarata. Key '/color' was set."
  EndIf
EndIf
Now the code length is 9 lines, very similar in length to your non-COM version of 12 lines, and I'm finding it hard to see the advantage of the formar, as it more complex and presumably much slower.

Incidently, there are bugs in lines 4 and 9 of your non-COM example.

Posted: Sat Nov 03, 2007 7:32 pm
by Hroudtwolf
Yes. To check the object pointer is important. 'Cause the object will only be created if a commandline does exist.

Sorry. But if ya don't see the advantage, you are just blind.
Not only the count of lines are the advantage.
It is the handy usability of this class.

For ME, it is very useful.
And I didn't create this little class unfounded.

Best regards

Wolf

PS: Your'e free to use it, or not ;-)

Posted: Sat Nov 03, 2007 9:11 pm
by srod
I'm with you wolfie! :wink:

Posted: Sat Nov 03, 2007 9:47 pm
by Hroudtwolf
Thx :)
Let us build a OOP gang to accroach the world domination. ^^

Posted: Sun Nov 04, 2007 12:56 pm
by #NULL
but that said advantage is not really oop-related. actually for the IsArg()-thing i would prefer a simple function like this:
(not well tested)

Code: Select all

Procedure isArg(arg.s)
  Static NewList args.s()
  Static init.l = 0
  Static i.l
  Protected ret.l = #False
  
  If Not init
    init=1
    For i=0 To CountProgramParameters()-1
      AddElement(args())
      args() = ProgramParameter()
      Debug args()
    Next
  EndIf
  
  ForEach args()
    If args() = arg
      ret = #True
      Break
    EndIf
  Next
  
  ProcedureReturn ret
EndProcedure
..but maybe i'm not having the right overview about the context, and neither did i fully check you class.
but thanks for sharing good code. i like oop style and i'm using it often, but in a very loose way.
the biggest advantage is the oop-interface for usage, but that's only an advantage for people who like it :P .

..i just see now for my code, if i would want to access the arg-list from different procedures for different purposes (as you do), there come the first complications. so for that oop is much better maybe.
[sorry, probably you will never a class posted without those "do you need that"s :lol: ]

Posted: Sun Nov 04, 2007 3:46 pm
by Hroudtwolf
Hi,

I never said, "this is only possible in OOP style" ;-)
All my codes are capsuled in classes. That is my unique codingstyle.
I don't think, I've to explain more about this.
'Cause everybody should know the advantages of such a method.

Let us assume, a objectmanager is our central and global interface to all of our objects and our cCMDL interface haves two methode more...
(All my classes does have such a standard interface)

Code: Select all

Interface IcCMDL
   ; Standard
   SendMessage (lMsgID.l , *ParamA , *ParamA)
   PostBox         (*lptrMsg.LONG , *lptrParamA.LONG , *lptrParamA.LONG)
   ; Class internal
   IsArg       (sKey.s)
   GetArg.s    (lIndex.l)
   GetArgIndex (sKey.s)
   CountArgs   ()
   Done        ()
EndInterface

With such a method, this is possible--->

Code: Select all

If *MyOM\SendMessage ("cmdl" , #CMDL_MSG_ISARG , @"/load" , #Null)
   *MyOM\SendMessage ("otherobject" , #OOBJ_MSG_DOANYTHING , #Null , #Null)
   Else
   *MyOM\SendMessage ("errorhandler" , #OOBJ_MSG_POSTERROR , @"Wrong arguments." , #Null)
EndIf
'Cause the objectmanager can control other objects by ONE interface.

Code: Select all

Interface IcSTDINTERFACE
   SendMessage (lMsgID.l , *ParamA , *ParamA)
   PostBox         (*lptrMsg.LONG , *lptrParamA.LONG , *lptrParamA.LONG)
EndInterface

And this is for ME, elysium of a proper programingstyle.

I hope you see the facts in spite of my funny english. ^^

Best regards :)

Wolf

Posted: Sun Nov 04, 2007 4:16 pm
by #NULL
that makes sense and can be useful indeed.
thanks for the explanations and ideas :)

Posted: Tue Nov 06, 2007 11:04 am
by pdwyer
Forcing OOP in this situation still seems like an overkill to me. The code below achieves the same thing but (to me) it's easier to read and understand (and debug). If the language was OOP then you wouldn't have this under-the-hood OOP code there in front of you but it's not so I probably wouldn't bother unless the task was really lending itself to being easier to understand as instanciated objects. If you had a swarm algorithm OOP would probably make it easier to encapsulate complex movements for each member, Neurons in an ANN, stuff like that.

But hey, I'm one of those anti java kind of people so it's just my opinion 8)

(Case insensitive)

Code: Select all

Declare.s GetCMDParam(Param.s)
Declare.l GetCMDSwitch(Switch.s)

;using exe commandline: -i infile -o outfile -switch  (you could catch /switch too I suppose)

; Returns
    ; outfile
    ; infile
    ; 1
    ; 0

 
Debug GetCMDParam("-O")
Debug GetCMDParam("-i")
Debug GetCMDSwitch("-switch")
Debug GetCMDSwitch("-MissingSwitch")

;===========================================

Procedure.s GetCMDParam(Param.s)

    Param = LCase(Param)
    
    For i = 0 To CountProgramParameters() -1
        If  LCase(ProgramParameter(i)) = Param
            ProcedureReturn ProgramParameter(i+1)
        EndIf   
    Next   

    ProcedureReturn ""
    
EndProcedure

;===========================================

Procedure.l GetCMDSwitch(Switch.s)

    Switch = LCase(Switch)
    
    For i = 0 To CountProgramParameters() -1
        If  LCase(ProgramParameter(i)) = Switch
            ProcedureReturn #True
        EndIf   
    Next   

    ProcedureReturn #False   

EndProcedure

;===========================================

Posted: Tue Nov 06, 2007 4:42 pm
by Hroudtwolf
I'll post OOP code never more, here. ^^

Posted: Wed Nov 07, 2007 1:13 am
by pdwyer
:lol:

Sorry if that sounded a bit strong.

I tried to pepper my response with "to me" and "my opinion". I'm sure there's plenty of people who prefer your method to mine and I wasn't looking to start an OOP war, just some discussion.

Actually I'm not anti-oop, I just think that classes should be created for instanciating objects. I've just never understood why people (Mainly Java heretics :twisted: ) do OOP for something that won't ever have more than one object created for it. You only ever have one cmdline object created though (I think).

Anyway, didn't mean to rain on your parade :wink:

Posted: Wed Nov 07, 2007 12:23 pm
by srod
Whilst I agree with your point about instantiation and that creating a class for an object which will only be instantiated once in an application is, in a sense, 'overkill', there is still the encapsulation aspect of it all.

Wrapping up such functionality as offered here in a simple object which can be simply used and abused and otherwise forgotten about is quite nice, particularly in large projects. I now find myself doing this for more than a few classes in my applications, as much for portability as anything else as I can simply 'plug' these albeit simple classes into other programs without thinking about it. These include classes for managing preferences, language files etc.

Of course you can achieve all this quite readily with non-oop methods and so, like everything, it comes down to a matter of personal preference and mine is most definitely to use oop even in simple cases. I just find it a very tidy way of programming.

Yep, I likes it! :)