123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
/*******************************************************************************

        copyright:      Copyright (c) 2007 Kris Bell. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Initial release: Oct 2007

        author:         Kris

        Simple serialization for text-based name/value pairs.

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

module tango.io.stream.Map;

private import tango.io.stream.Lines,
               tango.io.stream.Buffered;

private import Text = tango.text.Util;

private import tango.io.device.Conduit;

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

        Provides load facilities for a properties stream. That is, a file
        or other medium containing lines of text with a name=value layout.

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

class MapInput(T) : Lines!(T)
{
        /***********************************************************************

                Propagate ctor to superclass.

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

        this (InputStream stream)
        {
                super (stream);
        }

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

                Load properties from the provided stream, via a foreach.

                We use an iterator to sweep text lines, and extract lValue
                and rValue pairs from each one, The expected file format is
                as follows:

        * $(PRE
        *x = y
        *abc = 123
        *x.y.z = this is a single property
        *
        *# this is a comment line)

                Note that the provided name and value are actually slices
                and should be copied if you intend to retain them (using
                name.dup and value.dup where appropriate.)

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

        final int opApply (scope int delegate(ref const(T)[] name, ref const(T)[] value) dg)
        {
                int ret;

                foreach (line; super)
                        {
                        auto text = Text.trim (line);

                        // comments require '#' as the first non-whitespace char
                        if (text.length && (text[0] != '#'))
                           {
                           // find the '=' char
                           auto i = Text.locate (text, cast(T) '=');

                           // ignore if not found ...
                           if (i < text.length)
                              {
                              auto name = Text.trim (text[0 .. i]);
                              auto value = Text.trim (text[i+1 .. $]);
                              if ((ret = dg (name, value)) != 0)
                                   break;
                              }
                           }
                        }
                return ret;
        }

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

                Load the input stream into an AA.

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

        final MapInput load (ref const(T)[][T[]] properties)
        {
                foreach (name, value; this)
                         properties[name] = value;
                return this;
        }
}


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

        Provides write facilities on a properties stream. That is, a file
        or other medium which will contain lines of text with a name=value
        layout.

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

class MapOutput(T) : OutputFilter
{
        private const(T)[] eol;

        private enum const(T)[] prefix = "# ";
        private enum const(T)[] equals = " = ";
        version (Win32)
                 private enum const(T)[] NL = "\r\n";
        version (Posix)
                 private enum const(T)[] NL = "\n";

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

                Propagate ctor to superclass.

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

        this (OutputStream stream, const(T)[] newline = NL)
        {
                super (BufferedOutput.create (stream));
                eol = newline;
        }

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

                Append a newline to the provided stream.

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

        final MapOutput newline ()
        {
                sink.write (eol);
                return this;
        }

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

                Append a comment to the provided stream.

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

        final MapOutput comment (const(T)[] text)
        {
                sink.write (prefix);
                sink.write (text);
                sink.write (eol);
                return this;
        }

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

                Append name & value to the provided stream.

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

        final MapOutput append (const(T)[] name, const(T)[] value)
        {
                sink.write (name);
                sink.write (equals);
                sink.write (value);
                sink.write (eol);
                return this;
        }

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

                Append AA properties to the provided stream.

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

        final MapOutput append (const(T)[][T[]] properties)
        {
                foreach (key, value; properties)
                         append (key, value);
                return this;
        }
}



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

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

debug (UnitTest)
{
        import tango.io.Stdout;
        import tango.io.device.Array;

        unittest
        {
                auto buf = new Array(200);
                auto input = new MapInput!(char)(buf);
                auto output = new MapOutput!(char)(buf);

                const(char)[][char[]] map;
                map["foo"] = "bar";
                map["foo2"] = "bar2";
                output.append(map).flush();

                map = map.init;
                input.load (map);
                assert (map["foo"] == "bar");
                assert (map["foo2"] == "bar2");
        }
}