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
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.
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
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().
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
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.
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
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.