1 /**
2  * Structured INI
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 deprecated module ae.utils.serialization.sini;
15 deprecated:
16 
17 import std.exception;
18 import std.range;
19 import std.string;
20 import std.traits;
21 
22 import ae.utils.meta.binding;
23 import ae.utils.meta.reference;
24 
25 struct IniParser(R)
26 {
27 	static void setValue(S, Sink)(S[] segments, S value, Sink sink)
28 	{
29 		if (segments.length == 0)
30 			sink.handleString(value);
31 		else
32 		{
33 			struct Reader
34 			{
35 				// https://issues.dlang.org/show_bug.cgi?id=12318
36 				void dummy() {}
37 
38 				void read(Sink)(Sink sink)
39 				{
40 					setValue(segments[1..$], value, sink);
41 				}
42 			}
43 			Reader reader;
44 			sink.traverse(segments[0], boundFunctorOf!(Reader.read)(&reader));
45 		}
46 	}
47 
48 	static S readSection(R, S, Sink)(ref R r, S[] segments, Sink sink)
49 	{
50 		if (segments.length)
51 		{
52 			struct Reader
53 			{
54 				// https://issues.dlang.org/show_bug.cgi?id=12318
55 				void dummy() {}
56 
57 				S read(Sink)(Sink sink)
58 				{
59 					return readSection(r, segments[1..$], sink);
60 				}
61 			}
62 			Reader reader;
63 			return sink.traverse(segments[0], boundFunctorOf!(Reader.read)(&reader));
64 		}
65 
66 		while (!r.empty)
67 		{
68 			auto line = r.front.chomp().stripLeft();
69 
70 			scope(success) r.popFront();
71 			if (line.empty)
72 				continue;
73 			if (line[0] == '#' || line[0] == ';')
74 				continue;
75 
76 			if (line.startsWith('['))
77 			{
78 				line = line.stripRight();
79 				enforce(line[$-1] == ']', "Malformed section line (no ']')");
80 				return line[1..$-1];
81 			}
82 
83 			auto pos = line.indexOf('=');
84 			enforce(pos > 0, "Malformed value line (no '=')");
85 			auto name = line[0..pos].strip;
86 			segments = name.split(".");
87 			enforce(segments.length, "Malformed value line (empty name)");
88 			setValue(segments, line[pos+1..$].strip, sink);
89 		}
90 		return null;
91 	}
92 
93 	void parseIni(R, Sink)(R r, Sink sink)
94 	{
95 		auto nextSection = readSection(r, typeof(r.front)[].init, sink);
96 
97 		while (nextSection)
98 			nextSection = readSection(r, nextSection.split("."), sink);
99 	}
100 }
101 
102 /// Parse a structured INI from a range of lines, into a user-defined struct.
103 T parseIni(T, R)(R r)
104 	if (isInputRange!R && isSomeString!(ElementType!R))
105 {
106 	import ae.utils.serialization.serialization;
107 
108 	T result;
109 	auto parser = IniParser!R();
110 	parser.parseIni(r, deserializer(&result));
111 	return result;
112 }
113 
114 unittest
115 {
116 	static struct File
117 	{
118 		struct S
119 		{
120 			string n1, n2;
121 			int[string] a;
122 		}
123 		S s;
124 	}
125 
126 	auto f = parseIni!File
127 	(
128 		q"<
129 			s.n1=v1
130 			s.a.foo=1
131 			[s]
132 			n2=v2
133 			a.bar=2
134 		>".splitLines()
135 	);
136 
137 	assert(f.s.n1=="v1");
138 	assert(f.s.n2=="v2");
139 	assert(f.s.a==["foo":1, "bar":2]);
140 }
141 
142 unittest
143 {
144 	import ae.utils.serialization.serialization;
145 	import std.conv;
146 
147 	static struct Custom
148 	{
149 		struct Section
150 		{
151 			string name;
152 			string[string] values;
153 		}
154 		Section[] sections;
155 
156 		enum isSerializationSink = true;
157 
158 		auto traverse(Reader)(wstring name, Reader reader)
159 		{
160 			sections.length++;
161 			auto p = &sections[$-1];
162 			p.name = to!string(name);
163 			return reader(deserializer(&p.values));
164 		}
165 
166 		void handleString(S)(S s) { assert(false); }
167 	}
168 
169 	auto c = parseIni!Custom
170 	(
171 		q"<
172 			[one]
173 			a=a
174 			[two]
175 			b=b
176 		>"w.splitLines()
177 	);
178 
179 	assert(c == Custom([Custom.Section("one", ["a" : "a"]), Custom.Section("two", ["b" : "b"])]));
180 }
181 
182 // ***************************************************************************
183 
184 /// Convenience function to load a struct from an INI file.
185 /// Returns .init if the file does not exist.
186 S loadIni(S)(string fileName)
187 {
188 	S s;
189 
190 	import std.file;
191 	if (fileName.exists)
192 		s = fileName
193 			.readText()
194 			.splitLines()
195 			.parseIni!S();
196 
197 	return s;
198 }