Command line option parser

Posted by jmls on 15-Apr-2011 13:43

Before I go off an code  one, has anyone got a command line parser in the ABL ?

Given the string

&p1="foo&BAR" &p2="xx~"xx" &p3="at last, a normal parameter"

I would like to get an name/value array

name[1] = p1

name[2] = p2

name[3] = p3

value[1] = foo&BAR

value[2] = xx"xx

value[3] = at last, a normal parameter

All Replies

Posted by jmls on 15-Apr-2011 14:27

Hmm. First pass.

1) Does not handle errors in the parameter string (no & etc)

2) Does not handle " &" in the middle of a parameter value

works ok for this simple test case

DEF VAR a AS DotR.#Code.Model.Directive.

a = NEW DotR.#Code.Model.Directive("CustomCode &name=ExtraProperties &foo=bar").

MESSAGE a:directivename  SKIP

    a:Parametername[1] SKIP

    a:ParameterData[1] SKIP

    a:Parametername[2] SKIP

    a:ParameterData[2]

    VIEW-AS ALERT-BOX.

/*
Copyright (C) 2011 by Julian Lyndon-Smith

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
* @author      Julian Lyndon-Smith julian+psdn@dotr.com
* @version     2011.0415
*/
USING Progress.Lang.*.

ROUTINE-LEVEL ON ERROR UNDO, THROW.

CLASS DotR.#Code.Model.Directive :

  DEF PUBLIC PROPERTY DirectiveName AS CHAR        NO-UNDO GET . PRIVATE SET.
  DEF PUBLIC PROPERTY ParameterName AS CHAR EXTENT NO-UNDO GET . PRIVATE SET.
  DEF PUBLIC PROPERTY ParameterData AS CHAR EXTENT NO-UNDO GET . PRIVATE SET.
  DEF PUBLIC PROPERTY Data          AS CHAR        NO-UNDO GET . SET.
 
  DEF PUBLIC PROPERTY TrimCode AS LOGICAL NO-UNDO INIT YES GET . SET.
   
/**
* parses the supplied string
*
* @param  p_Data String to parse
*/
     CONSTRUCTOR PUBLIC Directive (p_Data AS CHAR):
          SUPER ().
          ParseDirective(p_Data).
     END CONSTRUCTOR.

/**
* parses the supplied string
*
* Each option is split into a name / value pair
* into a variable extent
*
* @param  p_Data String to parse
* @return VOID
*/
  METHOD PRIVATE VOID ParseDirective(p_Data AS CHAR):
    DEF VAR lv_ParamData AS CHAR NO-UNDO.
    DEF VAR lv_Name      AS CHAR NO-UNDO.
   
    DEF VAR lv_Index AS INT NO-UNDO.
    DEF VAR lv_i     AS INT NO-UNDO.
   
    /* example #1: DefineOption &BuildDirectory="DotR.#Code.Model" 
               #2: Copyright */              
     
    ASSIGN p_Data   = TRIM(p_Data)
           lv_Index = INDEX(p_Data," ")
           lv_name  = SUBSTR(p_Data,1,IF lv_Index EQ 0 THEN -1 ELSE lv_Index) /* -1 returns the whole string */
           p_Data   = IF lv_Index EQ 0 THEN "" /* only a parameter, no value */
                                       ELSE REPLACE(SUBSTR(p_Data,lv_Index)," &",CHR(1)) /* each parameter begins with " &" (excluding quotes) , so if we
                                                                                             replace those with a special character, we can work out how
                                                                                             many name / value there are */
           p_Data   = TRIM(p_Data,CHR(1)). /* get rid of first CHR(1) */
                                                                                                                     
    EXTENT(ParameterName) = NUM-ENTRIES(p_Data,CHR(1)). /* voila! */
    EXTENT(ParameterData) = EXTENT(ParameterName).
   
    DO lv_i = 1 TO NUM-ENTRIES(p_Data,CHR(1)): /* let's loop through all of the parameters */
      ASSIGN lv_ParamData        = TRIM(ENTRY(lv_i,p_Data,CHR(1)))
             ParameterName[lv_i] = TRIM(ENTRY(1,lv_ParamData,"="),"~" '~t~n&") /* strip any leading or trailing chars (" ' ) */
             lv_ParamData        = IF NUM-ENTRIES(lv_ParamData,"=") GT 1 THEN SUBSTR(lv_ParamData,INDEX(lv_ParamData,"=") + 1)
                                                                         ELSE ""
             lv_ParamData        = TRIM(lv_ParamData,"~" '~t~n") /* strip any leading or trailing chars (" ' ) */                                                                                         
             ParameterData[lv_i] = lv_ParamData.

      IF ParameterName[lv_i] EQ "TrimCode" THEN ASSIGN TrimCode = LOGICAL(ParameterData[lv_i]). /* special parameter that needs saving */

    END.
   
    ASSIGN DirectiveName = lv_Name.
 
    RETURN.

  END METHOD.
END CLASS.

Posted by Peter Judge on 18-Apr-2011 10:46

jmls wrote:

Hmm. First pass.

1) Does not handle errors in the parameter string (no & etc)

2) Does not handle " &" in the middle of a parameter value

works ok for this simple test case


DEF VAR a AS DotR.#Code.Model.Directive.

a = NEW DotR.#Code.Model.Directive("CustomCode &name=ExtraProperties &foo=bar").

MESSAGE a:directivename  SKIP
    a:Parametername[1] SKIP
    a:ParameterData[1] SKIP
    a:Parametername[2] SKIP
    a:ParameterData[2]

    VIEW-AS ALERT-BOX.




Tres cool.

Couple of thoughts

- how about making the constructor take 2 args: one for (Directive)Name and one for the args? (what is a Directive in this case, incidentally?)

- Can the parameter prefix (in this case "&") be parameterised? I'd think it'd be useful to be able to say

a = NEW DotR.#Code.Model.Directive("Startup Parameters", session:startup-parameters).
a:ParameterNamePrefix = '-'.   /* dash */

/* may also need*/ a:ParameterNameValueSeparator = ' '. /* space */

a:ProcessDirective().

The prefix and separators could also be ctor arguments.

-- peter

Posted by jmls on 18-Apr-2011 11:50

Merci, Mon Amie

pjudge wrote

Tres cool.

Couple of thoughts

- how about making the constructor take 2 args: one for (Directive)Name and one for the args? (what is a Directive in this case, incidentally?)

- Can the parameter prefix (in this case "&") be parameterised? I'd think it'd be useful to be able to say

a = NEW DotR.#Code.Model.Directive("Startup Parameters", session:startup-parameters).
a:ParameterNamePrefix = '-'.   /* dash */
/* may also need*/ a:ParameterNameValueSeparator = ' '. /* space */
a:ProcessDirective().




The prefix and separators could also be ctor arguments.




-- peter


Ah, a Directive is a template instruction to the code builder. A couple of examples from one of my templates:

{&! Publish NAME = "BeforeUpdate" !}

{&! DefineDefaultConstructor !}

{&! CustomCode &Name=AfterGet !}

Where "Publish", "DefineDefaultConstructor" and "CustomCode" are all methods in the code generator class that spit out appropriate code , with 0..n named parameters being passed to each method using a DotR.#Code.Model.Directive object

The {&! ... !} is a "cheat" that enables me to embed instructions into the template or source file and does not trip up the compiler, as it thinks that you are refering to a pre-processor variable ({&! ... }) which happens not to exist, so it is ignored.

It would be a trivial task to add parameterised parameter prefixes and seperators. I'll do that if you wish Something like:

a = (NEW DotR.#Code.Model.Directive())

      :Directive("CustomCode")

      :Data("--name=ExtraProperties --foo=bar")

      :ParameterNamePrefix("--")

      :ParameterNameValueSeparator(" ")

      :ProcessDirective().

Posted by Peter Judge on 18-Apr-2011 12:00

a = (NEW DotR.#Code.Model.Directive())

:Directive("CustomCode")

:Data("--name=ExtraProperties --foo=bar")

:ParameterNamePrefix("--")

:ParameterNameValueSeparator(" ")

:ProcessDirective().

Sweet like a lemon (as we say in the vernacular :). And which means really cool ).

I really like the fluent interface.

-- peter

Posted by jmls on 18-Apr-2011 12:25

for those not of a South African bent:

http://www.urbandictionary.com/define.php?term=Sweet%20like%20a%20lemon

I lived in Joburg for 7 years, and CT for a year in the eighties A

lifetime ago.

Posted by Peter Judge on 02-May-2011 15:06

works ok for this simple test case


DEF VAR a AS DotR.#Code.Model.Directive.

a = NEW DotR.#Code.Model.Directive("CustomCode &name=ExtraProperties &foo=bar").

MESSAGE a:directivename  SKIP
    a:Parametername[1] SKIP
    a:ParameterData[1] SKIP
    a:Parametername[2] SKIP
    a:ParameterData[2]

    VIEW-AS ALERT-BOX.






Just thinking about this a little more (strangely enough, I suddenly find myself needing this functionality).

I like how you get the data in, but not so much about getting it out. You have to iterate over the name extent and get the corresponding value. Have you considered using a Map for this:

MESSAGE

  a:directivename  SKIP    

  a:Parameters:Get("icfparam"):ToString() SKIP

  a:Parameters:Get("Numeric-separator"):ToString()

  VIEW-AS ALERT-BOX INFO.

If the order's really important then of course a Map isn't useful, and a List is. So I think there's value in making one property of the 2 and making it an ICollection type, so that you (me, whoever ) don't really care .

-- peter

Posted by Admin on 03-May-2011 04:49

looks like there are named parameters, so Map it is... use getKeys (or whatever) to get the collection of keys and then use get(keyName) for the value, or lower getEntries that returns a collection of key/value entry.

This thread is closed