digitalmars.D - [RFC] Ini parser
- Robik (43/43) Feb 16 2012 Greetings.
- Nathan M. Swan (5/7) Feb 16 2012 It's great, I think I could find uses for it.
- Manu (19/58) Feb 16 2012 I wonder if there is a problem with a 'standard' ini parser, in that ini
- Vladimir Panteleev (3/6) Feb 16 2012 I've never seen such a file. What software (other than yours)
- Robik (11/34) Feb 17 2012 Also, you can "make it" simplest/standard-est as possible by
- Robik (6/22) Feb 17 2012 I parser won't parse it. To have nested sections, it uses
- Sean Kelly (16/80) Feb 16 2012 ini files are not really very standard.
- Marco Leise (2/3) Feb 16 2012 Listen to this guy, he's right. JSON allows hierarchies and arrays, stri...
- Manu (3/12) Feb 16 2012 Sure, I would certainly use JSON now, but we're talking about reading
- Manu (3/81) Feb 16 2012 True, but JSON didn't exist/wasn't well known when these formats were in
- Jacob Carlborg (12/41) Feb 16 2012 Why the need for ".value"? It would guess because opIndex returns IniKey...
- Robik (2/54) Feb 17 2012
- Robert Jacques (5/8) Feb 17 2012 I'd recommend using opDispatch to enable the following syntax:
- bioinfornatics (372/372) Feb 21 2012 I have wrote a ini parser it can use bidirectional range
- Suliman (5/5) Jan 20 2015 It would be nice to add overload of getKey method so you can use
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
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
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
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
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
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
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
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
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.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?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
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 inifiles are not really very standard.I have seen a lot of ini files with scope (also also use this in some ofmy 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 likethat 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 normalthing 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 tokeep 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, includinguseful 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 allowingyou 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
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
On Friday, 17 February 2012 at 07:27:52 UTC, Jacob Carlborg wrote:On 2012-02-16 21:50, Robik wrote:Thanks, it's awesome idea. Added.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; }
Feb 17 2012
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
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
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