1 /******************************************************************************* 2 3 copyright: Copyright (c) 2007 Kris Bell. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Initial release: Oct 2007 8 9 author: Kris 10 11 Simple serialization for text-based name/value pairs. 12 13 *******************************************************************************/ 14 15 module tango.io.stream.Map; 16 17 private import tango.io.stream.Lines, 18 tango.io.stream.Buffered; 19 20 private import Text = tango.text.Util; 21 22 private import tango.io.device.Conduit; 23 24 /******************************************************************************* 25 26 Provides load facilities for a properties stream. That is, a file 27 or other medium containing lines of text with a name=value layout. 28 29 *******************************************************************************/ 30 31 class MapInput(T) : Lines!(T) 32 { 33 /*********************************************************************** 34 35 Propagate ctor to superclass. 36 37 ***********************************************************************/ 38 39 this (InputStream stream) 40 { 41 super (stream); 42 } 43 44 /*********************************************************************** 45 46 Load properties from the provided stream, via a foreach. 47 48 We use an iterator to sweep text lines, and extract lValue 49 and rValue pairs from each one, The expected file format is 50 as follows: 51 52 * $(PRE 53 *x = y 54 *abc = 123 55 *x.y.z = this is a single property 56 * 57 *# this is a comment line) 58 59 Note that the provided name and value are actually slices 60 and should be copied if you intend to retain them (using 61 name.dup and value.dup where appropriate.) 62 63 ***********************************************************************/ 64 65 final int opApply (scope int delegate(ref const(T)[] name, ref const(T)[] value) dg) 66 { 67 int ret; 68 69 foreach (line; super) 70 { 71 auto text = Text.trim (line); 72 73 // comments require '#' as the first non-whitespace char 74 if (text.length && (text[0] != '#')) 75 { 76 // find the '=' char 77 auto i = Text.locate (text, cast(T) '='); 78 79 // ignore if not found ... 80 if (i < text.length) 81 { 82 auto name = Text.trim (text[0 .. i]); 83 auto value = Text.trim (text[i+1 .. $]); 84 if ((ret = dg (name, value)) != 0) 85 break; 86 } 87 } 88 } 89 return ret; 90 } 91 92 /*********************************************************************** 93 94 Load the input stream into an AA. 95 96 ***********************************************************************/ 97 98 final MapInput load (ref const(T)[][T[]] properties) 99 { 100 foreach (name, value; this) 101 properties[name] = value; 102 return this; 103 } 104 } 105 106 107 /******************************************************************************* 108 109 Provides write facilities on a properties stream. That is, a file 110 or other medium which will contain lines of text with a name=value 111 layout. 112 113 *******************************************************************************/ 114 115 class MapOutput(T) : OutputFilter 116 { 117 private const(T)[] eol; 118 119 private __gshared immutable const(T)[] prefix = "# "; 120 private __gshared immutable const(T)[] equals = " = "; 121 version (Win32) 122 private __gshared immutable const(T)[] NL = "\r\n"; 123 version (Posix) 124 private __gshared immutable const(T)[] NL = "\n"; 125 126 /*********************************************************************** 127 128 Propagate ctor to superclass. 129 130 ***********************************************************************/ 131 132 this (OutputStream stream, const(T)[] newline = NL) 133 { 134 super (BufferedOutput.create (stream)); 135 eol = newline; 136 } 137 138 /*********************************************************************** 139 140 Append a newline to the provided stream. 141 142 ***********************************************************************/ 143 144 final MapOutput newline () 145 { 146 sink.write (eol); 147 return this; 148 } 149 150 /*********************************************************************** 151 152 Append a comment to the provided stream. 153 154 ***********************************************************************/ 155 156 final MapOutput comment (const(T)[] text) 157 { 158 sink.write (prefix); 159 sink.write (text); 160 sink.write (eol); 161 return this; 162 } 163 164 /*********************************************************************** 165 166 Append name & value to the provided stream. 167 168 ***********************************************************************/ 169 170 final MapOutput append (const(T)[] name, const(T)[] value) 171 { 172 sink.write (name); 173 sink.write (equals); 174 sink.write (value); 175 sink.write (eol); 176 return this; 177 } 178 179 /*********************************************************************** 180 181 Append AA properties to the provided stream. 182 183 ***********************************************************************/ 184 185 final MapOutput append (const(T)[][T[]] properties) 186 { 187 foreach (key, value; properties) 188 append (key, value); 189 return this; 190 } 191 } 192 193 194 195 /******************************************************************************* 196 197 *******************************************************************************/ 198 199 debug (UnitTest) 200 { 201 import tango.io.Stdout; 202 import tango.io.device.Array; 203 204 unittest 205 { 206 auto buf = new Array(200); 207 auto input = new MapInput!(char)(buf); 208 auto output = new MapOutput!(char)(buf); 209 210 const(char)[][char[]] map; 211 map["foo"] = "bar"; 212 map["foo2"] = "bar2"; 213 output.append(map).flush(); 214 215 map = map.init; 216 input.load (map); 217 assert (map["foo"] == "bar"); 218 assert (map["foo2"] == "bar2"); 219 } 220 }