1 /**
2  * SDLang configuration module
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2018-09-03
8  */
9 
10 module uniconf.sdlang;
11 
12 private
13 {
14     import std.algorithm.searching : any;
15     import std.array : empty;
16 
17     import sdlang : Tag, parseSource, ParseException, Value, Attribute;
18 
19     import uniconf.core : UniConfException;
20     import uninode.node : isUniNode;
21 }
22 
23 
24 /**
25  * Conert SDL to UniConf
26  */
27 UniConf toUniConf(UniConf)(auto ref Tag root) @safe
28     if (isUniNode!UniConf)
29 {
30     UniConf convertVal(ref Value val) @trusted
31     {
32         if (val.convertsTo!bool)
33             return UniConf(val.get!bool);
34         else if (val.convertsTo!long)
35             return UniConf(val.get!long);
36         else if (val.convertsTo!string)
37             return UniConf(val.get!string);
38         else if (val.convertsTo!double)
39             return UniConf(val.get!double);
40         else if (val.convertsTo!(ubyte[]))
41             return UniConf(val.get!(ubyte[]));
42         else
43             return UniConf();
44     }
45 
46     UniConf convert(ref Tag tag) @trusted
47     {
48         // annonymous tag return values
49         if (tag.tags.empty && tag.attributes.empty && tag.values.length)
50         {
51             if (tag.values.length == 1)
52                 return convertVal(tag.values[0]);
53             else
54             {
55                 UniConf[] arr = new UniConf[tag.values.length];
56                 foreach (size_t i, Value v; tag.values)
57                     arr[i] = convertVal(v);
58                 return UniConf(arr);
59             }
60         }
61 
62         // if exists tags or attributes then mapping
63         if (tag.tags.length || tag.attributes.length)
64         {
65             UniConf[string] map;
66             UniConf[] values;
67 
68             foreach(Attribute a; tag.attributes)
69                 map[a.name] = convertVal(a.value);
70 
71             if (tag.values.length == 1 && tag.values[0].convertsTo!string)
72                 map["__name"] = convertVal(tag.values[0]);
73             else
74             {
75                 values.reserve(tag.values.length);
76                 foreach (Value val; tag.values)
77                     values ~= convertVal(val);
78             }
79 
80             foreach (Tag subTag; tag.tags)
81             {
82                 auto subNode = convert(subTag);
83                 if (subTag.name.empty)
84                     values ~= subNode;
85                 else
86                 {
87                     if (auto exNode = subTag.name in map)
88                     {
89                         if (exNode.canSequence)
90                             *exNode ~= subNode;
91                         else if (subTag.name != "__name")
92                             map[subTag.name] = UniConf([*exNode, subNode]);
93                     }
94                     else
95                         map[subTag.name] = subNode;
96                 }
97             }
98 
99             if (values.length)
100                 map["__values"] = UniConf(values);
101 
102             return UniConf(map);
103         }
104         else
105             return UniConf();
106     }
107 
108     return convert(root);
109 }
110 
111 
112 /**
113  * Convert UniConf to SDLang
114  */
115 Tag toSDLang(UniConf)(auto ref const UniConf root) @safe 
116     if (isUniNode!UniConf)
117 {
118     Value convertNode(UniConf node)
119     {
120         switch(node.tag)
121         {
122             case UniConf.Tag.nil:
123                 return Value();
124             case UniConf.Tag.boolean:
125                 return Value(cast()node.get!bool);
126             case UniConf.Tag.integer:
127                 return Value(cast()node.get!long);
128             case UniConf.Tag.uinteger:
129                 return Value(cast(long)node.get!ulong);
130             case UniConf.Tag.floating:
131                 return Value(cast()node.get!double);
132             case UniConf.Tag.text:
133                 return Value(cast()node.get!string);
134             case UniConf.Tag.raw:
135                 return Value(cast(ubyte[])node.get!(ubyte[]));
136             default:
137                 return Value();
138         }
139     }
140 
141     Tag convert(ref const UniConf node) @trusted
142     {
143         Tag ret = new Tag();
144 
145         if (node.canSequence)
146         {
147             foreach (ref const UniConf subNode; node)
148             {
149                 if (subNode.canSequence || subNode.canMapping)
150                     ret.add(convert(subNode));
151                 else
152                     ret.add(convertNode(subNode));
153             }
154         }
155         else if (node.canMapping)
156         {
157             foreach (string name, ref const UniConf subNode; node)
158             {
159                 if (subNode.canSequence && subNode.getSequence.any!((n) => n.canMapping))
160                 {
161                     foreach (ref const UniConf subSubNode; subNode)
162                     {
163                         Tag subTag = convert(subSubNode);
164                         subTag.name = name;
165                         ret.add(subTag);
166                     }
167                 }
168                 else if (name == "__values")
169                 {
170                     foreach (ref const UniConf subVal; subNode)
171                     {
172                         if (subVal.canSequence || subVal.canMapping)
173                             ret.add(convert(subVal));
174                         else
175                             ret.add(convertNode(subVal));
176                     }
177                 }
178                 else if (name == "__name")
179                 {
180                     if (subNode.canSequence && subNode.getSequence.length)
181                         ret.add(convertNode(subNode[0]));
182                     else
183                         ret.add(convertNode(subNode));
184                 }
185                 else
186                 {
187                     Tag subTag = convert(subNode);
188                     subTag.name = name;
189                     ret.add(subTag);
190                 }
191             }
192         }
193         else
194             ret.add(convertNode(node));
195 
196         return ret;
197     }
198 
199     return () @trusted {
200         Tag ret = convert(root);
201         if (ret.tags.length == 0 && ret.values.length > 0)
202         {
203             Tag rootTag = new Tag();
204             rootTag.add(ret);
205             return rootTag;
206         }
207         return ret;
208     } ();
209 }
210 
211 @("Should work convert sdl to config and back")
212 @system unittest
213 {
214     import uninode.tree : UniTree;
215 
216     enum sdl = `
217 "anno1" 1
218 logger "console" {
219     appender "console"
220     level "debugv"
221     item "node1" {
222         order 1
223     }
224     item "node2"
225     item "node3"
226 }
227 `;
228 
229     UniTree conf = parseSDLang!UniTree(sdl);
230     Tag sdlRoot = toSDLang(conf);
231     assert (sdlRoot.tags.length == 2);
232     assert (sdlRoot.tags[1].values.length == 2);
233     assert (sdlRoot.tags[1].values[0].get!string == "anno1");
234 
235     assert (sdlRoot.tags[0].values.length == 1);
236     assert (sdlRoot.tags[0].values[0].get!string == "console");
237     assert (sdlRoot.tags[0].tags.length == 5);
238 }
239 
240 
241 /**
242  * Parse UniConf from string
243  */
244 UniConf parseSDLang(UniConf)(string data) @trusted
245     if (isUniNode!UniConf)
246 {
247     Tag root;
248     try
249         root = parseSource(data);
250     catch(ParseException e)
251         throw new UniConfException("Error loading sdlang from string:",
252                 e.file, e.line, e);
253     return toUniConf!UniConf(root);
254 }
255 
256 
257 /**
258  * Convert UniConf to json string
259  */
260 string saveSDLang(UniConf)(auto ref const UniConf node, string indent="    ") @trusted
261 {
262     auto rootTag = toSDLang!UniConf(node);
263     return rootTag.toSDLDocument(indent);
264 }
265 
266 @("Should work parseSDLang and saveSDLang method")
267 @safe unittest
268 {
269     import uninode.tree : UniTree;
270 
271     enum sdl = `client {
272     host "localhost"
273     port 44L
274 }
275 `;
276 
277     const conf = parseSDLang!UniTree(sdl);
278     assert (conf.get!int("client.port") == 44);
279     assert (conf.get!string("client.host") == "localhost");
280     const sdls = saveSDLang(conf);
281     assert (sdl == sdls);
282 }
283