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 }