www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [RFC] Ini parser

reply "Robik" <szadows gmail.com> writes:
Greetings.

Recently, I've been working on INI parser in D. The main goals 
were to keep code easy and well documented. Suggestions are 
really welcome(main reason of this thread) because it needs 
polishing and stuff.

It provides simple interface for operating on parsed file, 
including useful features like section inheriting and variable 
lookups. Here is simple example taken from README with little 
modifications:
import std.stdio;
    void main()
    {
         // Hard code the contents
         string c = "

    [def]
    name1:value1
    name2:value2

    ; override defaults
    [foo : def]
    name1=Name1 from foo. Lookup for def.name2: %name2%";

        // create parser instance
        auto iniParser = new IniParser();

        // Set ini structure details; can be ommited

        iniParser.delimChars = ['=', ':'];


        // parse
        auto ini = iniParser.parse(c);

        // write foo.name1 value
        writeln(ini.getSection("foo")["name1"].value);
    }

You can also define parsing details, like commentCharacters* and 
others. As for the keys, structure is used rather than 
associative arrays. There's also bug** that does not allow 
chaining with opCall which I hope will be fixed :).

IniStructure (result of parsing) overloads some basic operators 
allowing you to looping through it and accessing data with 
opIndex and opCall.

Feel free to share suggestions, changes, help me make it better 
:).

Repo: https://github.com/robik/DIni
* https://github.com/robik/DIni/blob/master/src/dini.d#L400
** http://d.puremagic.com/issues/show_bug.cgi?id=7210
Feb 16 2012
next sibling parent "Nathan M. Swan" <nathanmswan gmail.com> writes:
On Thursday, 16 February 2012 at 20:50:23 UTC, Robik wrote:
 Feel free to share suggestions, changes, help me make it better 
 :).
It's great, I think I could find uses for it. One thing that confuses me about the implementation is that the IniSection has an array of key-value pairs. I think it would be more efficient, and cleaner code, to use an associative array.
Feb 16 2012
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
I wonder if there is a problem with a 'standard' ini parser, in that ini
files are not really very standard.
I have seen a lot of ini files with scope (also also use this in some of my
own apps), will I be able to parse these with your parser?

[section]
{
  key = value
  key2 = value

  [subsection]
  {
    subkey = value
  }
}

?

I notice your interesting delimiters too, I've never seen anything like
that in an ini file before, where did you see that? What makes it standard?
I might like to use something like that if I had thought it was a normal
thing to use in an ini file...

On 16 February 2012 22:50, Robik <szadows gmail.com> wrote:

 Greetings.

 Recently, I've been working on INI parser in D. The main goals were to
 keep code easy and well documented. Suggestions are really welcome(main
 reason of this thread) because it needs polishing and stuff.

 It provides simple interface for operating on parsed file, including
 useful features like section inheriting and variable lookups. Here is
 simple example taken from README with little modifications:
 import std.stdio;
   void main()
   {
        // Hard code the contents
        string c = "

   [def]
   name1:value1
   name2:value2

   ; override defaults
   [foo : def]
   name1=Name1 from foo. Lookup for def.name2: %name2%";

       // create parser instance
       auto iniParser = new IniParser();

       // Set ini structure details; can be ommited

       iniParser.delimChars = ['=', ':'];


       // parse
       auto ini = iniParser.parse(c);

       // write foo.name1 value
       writeln(ini.getSection("foo")[**"name1"].value);
   }

 You can also define parsing details, like commentCharacters* and others.
 As for the keys, structure is used rather than associative arrays. There's
 also bug** that does not allow chaining with opCall which I hope will be
 fixed :).

 IniStructure (result of parsing) overloads some basic operators allowing
 you to looping through it and accessing data with opIndex and opCall.

 Feel free to share suggestions, changes, help me make it better :).

 Repo: https://github.com/robik/DIni
 * https://github.com/robik/DIni/**blob/master/src/dini.d#L400<https://github.com/robik/DIni/blob/master/src/dini.d#L400>
 ** http://d.puremagic.com/issues/**show_bug.cgi?id=7210<http://d.puremagic.com/issues/show_bug.cgi?id=7210>
Feb 16 2012
next sibling parent "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Thursday, 16 February 2012 at 22:29:30 UTC, Manu wrote:
 I have seen a lot of ini files with scope (also also use this 
 in some of my own apps), will I be able to parse these with 
 your parser?
I've never seen such a file. What software (other than yours) uses it?
Feb 16 2012
prev sibling next sibling parent "Robik" <szadows gmail.com> writes:
On Thursday, 16 February 2012 at 22:29:30 UTC, Manu wrote:
 I wonder if there is a problem with a 'standard' ini parser, in 
 that ini
 files are not really very standard.
 I have seen a lot of ini files with scope (also also use this 
 in some of my
 own apps), will I be able to parse these with your parser?

 [section]
 {
   key = value
   key2 = value

   [subsection]
   {
     subkey = value
   }
 }

 ?

 I notice your interesting delimiters too, I've never seen 
 anything like
 that in an ini file before, where did you see that? What makes 
 it standard?
 I might like to use something like that if I had thought it was 
 a normal
 thing to use in an ini file...
Also, you can "make it" simplest/standard-est as possible by disabling those features. To do that you have to set for example 'sectionInheritChar' to 0 to disable it. But to nest sections you have to use something like that: [section] key = value key2 = value [section.subsection] subkey = value Where dot in second section name is delimeter you've set up.
Feb 17 2012
prev sibling parent "Robik" <szadows gmail.com> writes:
On Thursday, 16 February 2012 at 22:29:30 UTC, Manu wrote:
 I wonder if there is a problem with a 'standard' ini parser, in 
 that ini
 files are not really very standard.
 I have seen a lot of ini files with scope (also also use this 
 in some of my
 own apps), will I be able to parse these with your parser?

 [section]
 {
   key = value
   key2 = value

   [subsection]
   {
     subkey = value
   }
 }

 ?
I parser won't parse it. To have nested sections, it uses delimeter in section names to keep it compatible with standards. If some parser does not supports nesting it still will, but sections would look like [a.b] etc. This curly braces thing can break some parsers.
Feb 17 2012
prev sibling next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
At this point you may as well just use JSON.

On Feb 16, 2012, at 2:29 PM, Manu wrote:

 I wonder if there is a problem with a 'standard' ini parser, in that =
ini files are not really very standard.
 I have seen a lot of ini files with scope (also also use this in some =
of my own apps), will I be able to parse these with your parser?
=20
 [section]
 {
   key =3D value
   key2 =3D value
=20
   [subsection]
   {
     subkey =3D value
   }
 }
=20
 ?
=20
 I notice your interesting delimiters too, I've never seen anything =
like that in an ini file before, where did you see that? What makes it = standard?
 I might like to use something like that if I had thought it was a =
normal thing to use in an ini file...
=20
 On 16 February 2012 22:50, Robik <szadows gmail.com> wrote:
 Greetings.
=20
 Recently, I've been working on INI parser in D. The main goals were to =
keep code easy and well documented. Suggestions are really welcome(main = reason of this thread) because it needs polishing and stuff.
=20
 It provides simple interface for operating on parsed file, including =
useful features like section inheriting and variable lookups. Here is = simple example taken from README with little modifications:
 import std.stdio;
   void main()
   {
        // Hard code the contents
        string c =3D "

   [def]
   name1:value1
   name2:value2
=20
   ; override defaults
   [foo : def]
   name1=3DName1 from foo. Lookup for def.name2: %name2%";
=20
       // create parser instance
       auto iniParser =3D new IniParser();
=20
       // Set ini structure details; can be ommited

       iniParser.delimChars =3D ['=3D', ':'];
=20
=20
       // parse
       auto ini =3D iniParser.parse(c);
=20
       // write foo.name1 value
       writeln(ini.getSection("foo")["name1"].value);
   }
=20
 You can also define parsing details, like commentCharacters* and =
others. As for the keys, structure is used rather than associative = arrays. There's also bug** that does not allow chaining with opCall = which I hope will be fixed :).
=20
 IniStructure (result of parsing) overloads some basic operators =
allowing you to looping through it and accessing data with opIndex and = opCall.
=20
 Feel free to share suggestions, changes, help me make it better :).
=20
 Repo: https://github.com/robik/DIni
 * https://github.com/robik/DIni/blob/master/src/dini.d#L400
 ** http://d.puremagic.com/issues/show_bug.cgi?id=3D7210
=20
Feb 16 2012
parent reply "Marco Leise" <Marco.Leise gmx.de> writes:
Am 16.02.2012, 23:34 Uhr, schrieb Sean Kelly <sean invisibleduck.org>:

 At this point you may as well just use JSON.
Listen to this guy, he's right. JSON allows hierarchies and arrays, strings, numbers and booleans as values. It is clearly defined and as light-weight as an INI file (compared to XML). I stored game replays in JSON format for http://aichallenge.org/ (gzip compressed and served via HTTP). I found it very flexible for the data structures we came up with and portable since most programming languages have a standard JSON parser.
Feb 16 2012
parent Manu <turkeyman gmail.com> writes:
On 17 February 2012 01:57, Marco Leise <Marco.Leise gmx.de> wrote:

 Am 16.02.2012, 23:34 Uhr, schrieb Sean Kelly <sean invisibleduck.org>:


  At this point you may as well just use JSON.

 Listen to this guy, he's right. JSON allows hierarchies and arrays,
 strings, numbers and booleans as values. It is clearly defined and as
 light-weight as an INI file (compared to XML). I stored game replays in
 JSON format for http://aichallenge.org/ (gzip compressed and served via
 HTTP). I found it very flexible for the data structures we came up with and
 portable since most programming languages have a standard JSON parser.
Sure, I would certainly use JSON now, but we're talking about reading existing data right? Or what's the point of ini at all?
Feb 16 2012
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
True, but JSON didn't exist/wasn't well known when these formats were in
use.

On 17 February 2012 00:34, Sean Kelly <sean invisibleduck.org> wrote:

 At this point you may as well just use JSON.

 On Feb 16, 2012, at 2:29 PM, Manu wrote:

 I wonder if there is a problem with a 'standard' ini parser, in that ini
files are not really very standard.
 I have seen a lot of ini files with scope (also also use this in some of
my own apps), will I be able to parse these with your parser?
 [section]
 {
   key = value
   key2 = value

   [subsection]
   {
     subkey = value
   }
 }

 ?

 I notice your interesting delimiters too, I've never seen anything like
that in an ini file before, where did you see that? What makes it standard?
 I might like to use something like that if I had thought it was a normal
thing to use in an ini file...
 On 16 February 2012 22:50, Robik <szadows gmail.com> wrote:
 Greetings.

 Recently, I've been working on INI parser in D. The main goals were to
keep code easy and well documented. Suggestions are really welcome(main reason of this thread) because it needs polishing and stuff.
 It provides simple interface for operating on parsed file, including
useful features like section inheriting and variable lookups. Here is simple example taken from README with little modifications:
 import std.stdio;
   void main()
   {
        // Hard code the contents
        string c = "

   [def]
   name1:value1
   name2:value2

   ; override defaults
   [foo : def]
   name1=Name1 from foo. Lookup for def.name2: %name2%";

       // create parser instance
       auto iniParser = new IniParser();

       // Set ini structure details; can be ommited

       iniParser.delimChars = ['=', ':'];


       // parse
       auto ini = iniParser.parse(c);

       // write foo.name1 value
       writeln(ini.getSection("foo")["name1"].value);
   }

 You can also define parsing details, like commentCharacters* and others.
As for the keys, structure is used rather than associative arrays. There's also bug** that does not allow chaining with opCall which I hope will be fixed :).
 IniStructure (result of parsing) overloads some basic operators allowing
you to looping through it and accessing data with opIndex and opCall.
 Feel free to share suggestions, changes, help me make it better :).

 Repo: https://github.com/robik/DIni
 * https://github.com/robik/DIni/blob/master/src/dini.d#L400
 ** http://d.puremagic.com/issues/show_bug.cgi?id=7210
Feb 16 2012
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2012-02-16 21:50, Robik wrote:
 Greetings.

 Recently, I've been working on INI parser in D. The main goals were to
 keep code easy and well documented. Suggestions are really welcome(main
 reason of this thread) because it needs polishing and stuff.

 It provides simple interface for operating on parsed file, including
 useful features like section inheriting and variable lookups. Here is
 simple example taken from README with little modifications:
 import std.stdio;
 void main()
 {
 // Hard code the contents
 string c = "

 [def]
 name1:value1
 name2:value2

 ; override defaults
 [foo : def]
 name1=Name1 from foo. Lookup for def.name2: %name2%";

 // create parser instance
 auto iniParser = new IniParser();

 // Set ini structure details; can be ommited

 iniParser.delimChars = ['=', ':'];


 // parse
 auto ini = iniParser.parse(c);

 // write foo.name1 value
 writeln(ini.getSection("foo")["name1"].value);
 }
Why the need for ".value"? It would guess because opIndex returns IniKey which both contains the key and the value. I would sugest you add a "alias this" pointing to "value". Something like this. struct IniKey { string name; string value; alias value this; } -- /Jacob Carlborg
Feb 16 2012
parent "Robik" <szadows gmail.com> writes:
On Friday, 17 February 2012 at 07:27:52 UTC, Jacob Carlborg wrote:
 On 2012-02-16 21:50, Robik wrote:
 Greetings.

 Recently, I've been working on INI parser in D. The main goals 
 were to
 keep code easy and well documented. Suggestions are really 
 welcome(main
 reason of this thread) because it needs polishing and stuff.

 It provides simple interface for operating on parsed file, 
 including
 useful features like section inheriting and variable lookups. 
 Here is
 simple example taken from README with little modifications:
 import std.stdio;
 void main()
 {
 // Hard code the contents
 string c = "

 [def]
 name1:value1
 name2:value2

 ; override defaults
Thanks, it's awesome idea. Added.
 [foo : def]
 name1=Name1 from foo. Lookup for def.name2: %name2%";

 // create parser instance
 auto iniParser = new IniParser();

 // Set ini structure details; can be ommited

 iniParser.delimChars = ['=', ':'];


 // parse
 auto ini = iniParser.parse(c);

 // write foo.name1 value
 writeln(ini.getSection("foo")["name1"].value);
 }
Why the need for ".value"? It would guess because opIndex returns IniKey which both contains the key and the value. I would sugest you add a "alias this" pointing to "value". Something like this. struct IniKey { string name; string value; alias value this; }
Feb 17 2012
prev sibling next sibling parent "Robert Jacques" <sandford jhu.edu> writes:
On Thu, 16 Feb 2012 14:50:22 -0600, Robik <szadows gmail.com> wrote:
 Greetings.
[snip]
         // write foo.name1 value
         writeln(ini.getSection("foo")["name1"].value);
I'd recommend using opDispatch to enable the following syntax: writeln( ini.foo.name1 ); Skimming the code, I see a lot of re-implementation of std.algorithm, to say nothing of the more general issue of using O(N) linear search instead of an O(1) hash table.
Feb 17 2012
prev sibling next sibling parent bioinfornatics <bioinfornatics fedoraproject.org> writes:
I have wrote a ini parser it can use bidirectional range

example of ini file able to parse:
_______________________________________
[sectionA]
    param1=3Dvalue1
    param2=3Dvalue2
    [[subSectionA]]
        param1sub=3Dvalue1sub
[sectionB]
    param3=3Dvalue3
    ; I am a comment
    param4=3Dvalue4
[sectionC]
    param5=3Dvalue5
    param6=3Dvalue6
_______________________________________


Example to code who use ini parser

_______________________________________
import std.string;
import std.stdio;
import std.ini;

void main( ){
    writeln( "0 - Starting test" );
    IniFile tester =3D open("myConfig.ini");
    writeln( "Ini file loaded" );
    writefln( "1 - Name: %s, level: %d", tester.name, tester.level);
    writefln( "2 - childs:\n%s,", tester.childs );
    writefln( "3 - object is null: %s", tester is null );
    writefln( "4 - number of section: %d", tester.length );
    writefln( "5 - sectionA: %s", tester.get("sectionA") );
    writefln( "6 - sectionA.param1: %s",
tester.get("sectionA")["param1"] );
    writefln( "7 - next section:\n%s", tester.front );
    writefln( "8 - next section:\n%s", tester.front );

}
----------------OUTPUT------------------------
0 - Starting test
Ini file loaded
1 - Name: root, level: 0
2 - childs:
[[sectionA]
param1=3Dvalue1
param2=3Dvalue2
[[subSectionA]]
param1sub=3Dvalue1sub
, [sectionB]
param3=3Dvalue3
param4=3Dvalue4
, [sectionC]
param5=3Dvalue5
param6=3Dvalue6
],
3 - object is null: false
4 - number of section: 3
5 - sectionA: [sectionA]
param1=3Dvalue1
param2=3Dvalue2
[[subSectionA]]
param1sub=3Dvalue1sub

6 - sectionA.param1: value1
7 - next section:
[sectionA]
param1=3Dvalue1
param2=3Dvalue2
[[subSectionA]]
param1sub=3Dvalue1sub

8 - next section:
[sectionA]
param1=3Dvalue1
param2=3Dvalue2
[[subSectionA]]
param1sub=3Dvalue1sub

_______________________________________



The code
_______________________________________
module std.ini;

private import std.stream       : BufferedFile;
private import std.string       : format, stripLeft, stripRight;
private import std.array        : split;
private import std.stdio        : File;
private import std.stdio        : writeln, writefln, writef;
private import std.exception    : Exception;


/**
 * parse is a method for parse a INI file or config file.
 *
 * Returns: A Section object with all information
 *
 * Examples:
 * --------------------
 * import std.ini;
 * string filePath  =3D "~/myGreatSetup.conf";
 * Section sections =3D configFile.open( filePath );
 * --------------------
 */

IniFile open( string filePath ){
    Section         root            =3D new Section("root",
0);           // root section
    Section         currentSection  =3D
root;                             // reference to current section
    Section         nextSection     =3D null;
    File            iniFile         =3D File( filePath, "r" );
    foreach( line; iniFile.byLine()
){                                  // read line by line
        try{
            line =3D line.stripLeft().stripRight();
            if( line =3D=3D "" || line[0] =3D=3D ';'
){                            // empty line line or comment line
                continue;
            }
            else if( line[0] =3D=3D '['
){                                  // section start
                nextSection =3D getSection( cast(string)line
);           // get newest section
                if( currentSection.level < nextSection.level
){         // currentSection.level < nextSection.level
                    currentSection.addChild( nextSection
);             // add a child to current section
                    currentSection =3D
nextSection;                       // now current section go to next one
                }
                else if( currentSection.level =3D=3D nextSection.level
){   // currentSection.level =3D nextSection.level
                    currentSection =3D
currentSection.rewind( currentSection.parent.level );
                    currentSection.addChild( nextSection );
                    currentSection =3D nextSection;
                }

else{                                                   //
currentSection.level > nextSection.level
                    currentSection =3D
currentSection.rewind( nextSection.level - 1);
                    currentSection.addChild( nextSection );
                    currentSection =3D nextSection;
                }
            }

else{                                                       // read
information corresponding to a section
                string[] words =3D split(cast(string)line,
"=3D");          // get key / value peer
                foreach( ref string word; words )

word.stripRight().stripLeft();                      // remove space,
before and after word
                currentSection[ words[0] ] =3D words[1];
            }
        }
        catch(Exception e){
            writeln( "Error: config file seem to not not follow
specification!" );
            writeln( e.msg );
            writefln( "Line: %s", line );
        }
    }
    root.shrink;
    return root;
}

alias Section IniFile;
class Section{
    private:
        string          _name;
        Section         _parent;
        Section[]       _childs;
        size_t          _level;
        size_t          _numberOfChild;
        string[string]  _dict;

    public:
        /**
         * Constructor for a Section object
         *
         * Params: name level
         */
        this(string name, size_t level){
            this._name           =3D name;
            this._level          =3D level;
            this._childs         =3D [];
            this._numberOfChild  =3D 0;
            this._dict           =3D null;
        }

        /**
         * Constructor for copy Section object
         *
         * Params: name parent level childs numberOfChild dict
         */
        this( string name, Section parent, size_t level, Section[]
childs, size_t numberOfChild, string[string] dict ){
            this._name           =3D name;
            this._level          =3D level;
            this._childs.length  =3D childs.length;
            foreach(size_t index, child; childs)
                this._childs[index] =3D child.dup;
            this._numberOfChild  =3D numberOfChild;
            this._dict           =3D dict;
        }

        /**
         * addChild is used for add a subsection to current section
         *
         * Params: Section
         */
        void addChild( ref Section section ){
            if( _numberOfChild >=3D _childs.length )
                _childs.length      =3D _childs.length + 5;        //
resize +5 for not resize 1 by 1
            section.parent          =3D this;
            _childs[_numberOfChild] =3D section;
            _numberOfChild++;
        }

        /**
         * Resize object to same size as data contained by the object
         */
         property void shrink(){
            _childs.length =3D _numberOfChild;
            foreach( child; _childs )
                child.shrink;
        }

        /**
         * get return the subsection where name equal name given
         *
         * Params: name
         *
         * Retuns: Section, null if not found
         */
        Section get( string name ){
            Section section     =3D null;
            bool    isSearching =3D true;
            size_t  index       =3D 0;
            while( isSearching ){
                if( index >=3D _numberOfChild )
                    isSearching =3D false;
                else if( _childs[index].name =3D=3D name ){
                    isSearching =3D false;
                    section =3D _childs[index].dup;
                }
                index++;
            }
            return section;
        }


        /**
         * opIndex
         * Acces to a value in current Section by giving his key
         */
        string opIndex( string key ){
            return _dict[key];
        }

        /**
         * opIndexAssign
         * Append a pair key/value in current Section
         */
        void opIndexAssign( string  value, string key ){
            _dict[key.idup] =3D value.idup;
        }

        /**
         * rewind is used for come back to parent at level given
         *
         * Params: level
         */
        Section rewind( size_t levelToGo
){                            // rewind to parent level x
            Section section     =3D null;
            if( _level =3D=3D levelToGo)
                section =3D this;
            else if( _level >=3D levelToGo)
                section =3D _parent.rewind( levelToGo );
            else
                throw new Exception("You try to go back when current
section is lower where level you want to go!");
            return section;
        }

        /**
         * toString used for print current object state
         *
         * Returns: a string
         */
        override string toString(){
            string content =3D "";
            string start   =3D "";
            string end     =3D "";
            if( _name !=3D "root" ){
                foreach(i; 0 .. _level){
                    start   ~=3D "[";
                    end     ~=3D "]";
                }
                content ~=3D start ~ _name ~ end ~ "\n"; // [section1] ...
[[section2]]
                foreach( key, value; _dict )
                    content ~=3D "%s=3D%s\n".format( key, value );
            }
            foreach(child; _childs){
                content ~=3D child.toString();
            }
            return content.idup;
        }

         property Section dup(){
            return new Section( this._name, this.parent, this._level,
this._childs, this._numberOfChild, this._dict );
        }

         property string name(){
            return _name.idup;
        }

         property Section parent(){
            return _parent;
        }

         property Section parent(Section section){
            return _parent =3D section;
        }

         property Section[] childs(){
            return _childs.dup;
        }

         property size_t level(){
            return _level;
        }

         property size_t length(){
            return _numberOfChild;
        }

         property string[] keys(){
            return _dict.keys;
        }

         property string[] values(){
            return _dict.values;
        }

         property void rehash(){
            _dict.rehash;
            foreach(child; _childs)
                child.rehash;
        }

         property bool empty(){
            return _numberOfChild =3D=3D 0;
        }

         property Section front(){
            return childs[0];
        }

         property Section back(){
            return  rewind( _parent.level );
        }

        void popFront(){
            _childs =3Dchilds[1..$];
        }

        void popBack(){
            Section[] reversed =3D new Section[]( _level ) ;
            while( _level !=3D 0 ){
                reversed[ _level - 1 ] =3D this.back;
            }
        }

        Section save(){
            return this;
        }

}


/**
 * getSection create a Section line with corresponding line
 *
 * Returns: Section object
 *
 * Examples:
 * --------------------
 * string line      =3D "[default]";
 * Section section  =3D getSection( line );
 * --------------------
 */
Section getSection( string lineSection ){
    size_t  level           =3D 0;
    size_t  position        =3D 0;
    string  name            =3D "";
    // get level
    while( lineSection[level] =3D=3D '[' ){
        level++;
    }
    position =3D level;
    // get section name
    while( lineSection[position] !=3D ']' ){
        name ~=3D lineSection[position];
        position++;
    }
    return new Section(name, level);
}
Feb 21 2012
prev sibling parent "Suliman" <evermind live.ru> writes:
It would be nice to add overload of getKey method so you can use 
something like this:
this.optionalkey = config.getKey("key1", "defaultValue");

it's need for set default value if needed value can't be found on 
config.
Jan 20 2015