| 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"); } } |