Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

INI files aka map streams.

Moderators: kris

Posted: 09/14/08 07:47:50

Hi i've noticed that the properties have been deprecated in favor of the newer map stream. Firstly I think the delegate that gets passed the parameters was much neater. Secondly and more importantly neither of them support sections which i think is a very major part of ini files. http://en.wikipedia.org/wiki/INI_file The majority if not all software expect the options to be defined in a certain section and thats how they find they by. And how is it supposed to work if the same key is used in multiple sections.

The way I see it working in a very sparse sort of way is that there is a collection of sections and each section is a special kind of collection of keys and values where you can set the value of a key and if it doesnt exists in the section it will create a line otherwise overwrite the old value with the new. I haven't got round to trying out the container but maybe this could be used if it could preserve the formatting and comments.

I would like to contribute tango. How is the decisions made for inclusion of new features.

Author Message

Posted: 09/14/08 08:35:55

Neither Properties nor MapStream was ever really considered to be an INI reader/writer. However, it is possible that this should be revisited, at least if support is contributed.

However, it is not likely that Properties will be un-deprecated.

As for contributions, a decision is made based on quality, argumentation, need, etc.

Posted: 10/17/08 23:21:09

I created an INI library which I needed for my own use but could it please be part of tango if there isn't already one.

/*******************************************************************************

        copyright:      Copyright (c) 2008 Tim Matthews. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Initial release: October 2008

        author:         Tim Matthews

*******************************************************************************/

module Ini;

private import tango.text.stream.LineIterator;

private import tango.io.Buffer;

private import tango.text.Util;

/*******************************************************************************

        A simple library for read/write access to INI files including
        minimal preservation of comments ";...." (only keeps comments
        if next to [sectionName] or key = value).

*******************************************************************************/



/*******************************************************************************

        INI Section class. Each key = value is stored under a
        section in the INI file with a name enclosed in '[' and ']'

*******************************************************************************/

class IniSection(T) : LineIterator!(T)
{
        public T[] name; //name of this section
        public T[] comment; //comment of this section
        public T[][T[]] kv; //keys, values
        public T[][T[]] kc; //keys, comments

        /***********************************************************************
        
                Iterate through each line and create the keys and values as
                their found

        ***********************************************************************/

        void load()
        {
                foreach (T[] line; super)
                {
                        auto i = locate (line, ';');
                        auto text = trim (line[0..i]);
                        i += T.sizeof;
                        T[] cmnt;

                        if (i < line.length)
                                cmnt = trim (line[i..$]);

                        i = locate (text, '=');

                        if (i<text.length)
                        {
                                auto key = trim (text[0..i]);
                                i += T.sizeof;
                                kv[key] = trim (text[i..$]);

                                if (cmnt !is null)
                                        kc[key] = cmnt;
                        }
                }
        }

        /***********************************************************************
        
                Called by INI to return this formatted correctly for storage
                back in an INI file

        ***********************************************************************/

        T[] save()
        {
                T[] newData = "["~name~"]";
                if (comment !is null)
                        newData ~= " ;" ~ comment;

                version (Win32)
                        newData ~= \r;
                newData ~= \n;

                T[] key;
                T[] val;
                foreach (key, val; kv)
                {
                        newData ~= key ~ " = " ~ val;

                        if (key in kc)
                                newData ~= " ;" ~ kc[key];

                        version (Win32)
                                newData ~= \r;
                        newData ~= \n;
                }
                return newData;
        }

        /***********************************************************************
        
                Construct a new INI section named: <name> using the specified
                InputStream stream

        ***********************************************************************/

        this (T[] name, InputStream stream)
        {
                auto i = locate (name, ';');
                this.name = trim (name[0..locate(name[0..i],']')]);
                i += T.sizeof;
                if (i<name.length)
                        comment = trim (name[i..$]);

                super (stream);
                load;
        }
}

/*******************************************************************************

        INI class is the container class for all the ini sections

*******************************************************************************/

class Ini(T) : LineIterator!(T)
{

        /***********************************************************************
        
                Array of ordered INI sections within this INI

        ***********************************************************************/

        public IniSection!(T)[] sections;

        /***********************************************************************
        
                Used during load

        ***********************************************************************/

        private T[] lastName;
        private T[] lastData;

        /***********************************************************************
        
                Delete all INI sections and make ready for a clean load

        ***********************************************************************/

        void clear()
        {
                sections = null;
                lastName = "NO_NAME"; //used for when key=value appears before [name]
                lastData = null;        
                super.clear;    
        }

        /***********************************************************************
        
                Iterate each line creating INI sections as their found

        ***********************************************************************/

        void load()
        {
                void addSect()
                {
                        if (lastData !is null)
                        {
                                sections ~= new IniSection!(T) (lastName, new Buffer (lastData) );
                                lastData = null;
                        }
                }

                (cast (IConduit.Seek) conduit).seek(0);

                foreach (T[] line; super)
                {
                        auto text = trim (line);

                        if (text.length==0) continue;

                        if (text[0] is '[')
                        {
                                addSect();
                                lastName = text[0+T.sizeof..$];
                        }
                        else
                                lastData ~= text ~ \n;

                }
                addSect();
        }

        /***********************************************************************
        
                Save all INI sections back to the conduit

        ***********************************************************************/

        void save()
        {       
                (cast (IConduit.Seek) conduit).seek(0);

                foreach (IniSection!(T) sect; this)
                {
                        conduit.write (sect.save);
                        version (Win32)
                                conduit.write (\r);
                        conduit.write (\n);
                }

                conduit.flush;  
        }

        /***********************************************************************
        
                Foreach IniSection in Ini

        ***********************************************************************/

        int opApply (int delegate (inout IniSection!(T) ) dg)
        {
                int result = 0;
                foreach (IniSection!(T) section; sections)
                {
                        result = dg (section);
                        if (result) break;
                }
                return result;
        }

        /***********************************************************************
        
                Construct a new Ini and load IniSections from conduit

        ***********************************************************************/

        this (IConduit.Seek conduit)
        {
                super (cast(IConduit)conduit);
                clear;
                load;
        }

}


And some sample code:

module testIni;

import Ini;
import tango.io.Stdout;
import tango.io.device.FileConduit;
import tango.io.Console;


int main(char[][] args)
{
        auto fc = new FileConduit ("C:\\test.ini", FileConduit.ReadWriteOpen);
        auto ini = new Ini.Ini!(char) (fc);

        IniSection!(char) sect;

        foreach (sect; ini)
        {
                Stdout ("["~sect.name~"]");

                if (sect.comment !is null)
                        Stdout(" ;"~sect.comment).newline;
                else
                        Stdout(\n);

                foreach (char[] key, char[]val ; sect.kv)
                {
                        Stdout (key~" = "~val);
                        if (key in sect.kc)
                                Stdout(" ;"~sect.kc[key]).newline;
                        else
                                Stdout(\n);
                }
                
                Stdout (\n);
        }
        

        Stdout ("\n\nFinished reading ini file. Press any key to continue").newline;

        Cin.get;

        return 0;
}




Also this page needs to be fixed: http://dsource.org/projects/tango/wiki/ChapterTextTools#INIFilesakaProperties