1 /**
2  * The module contains the object UniTree
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: 2020-03-03
8  */
9 
10 module uninode.tree;
11 
12 private
13 {
14     import std.format : fmt = format;
15     import std.array : split;
16     import std.typecons : Nullable;
17 
18     import uninode.node;
19 }
20 
21 
22 /// Delimiter char for config path
23 enum DELIMITER_CHAR = ".";
24 
25 
26 /**
27  * A [UniTree] struct
28  */
29 struct UniTree
30 {
31     private
32     {
33         alias Node = UniNodeImpl!UniTree;
34         alias node this;
35     }
36 
37     /**
38      * Node implementation
39      */
40     Node node;
41 
42     /**
43      * Common constructor
44      */
45     this(T)(auto ref T val) inout pure nothrow @safe @nogc
46         if ((isUniNodeInnerType!T && !isRawData!T) || is (T == typeof(null)))
47     {
48         node = Node(val);
49     }
50 
51     /**
52      * Common constructor
53      */
54     this(T)(auto ref T val) inout pure nothrow @safe
55         if (isRawData!T || isUniNodeArray!(T, Node))
56     {
57         node = Node(val);
58     }
59 
60     /**
61      * Common constructor
62      */
63     this(T)(auto ref T val) inout pure @safe
64         if (isUniNodeMapping!(T, Node))
65     {
66         node = Node(val);
67     }
68 
69     /**
70      * Sequence constructor
71      */
72     this(T...)(auto ref T val) inout pure nothrow @safe
73     {
74         node = Node(val);
75     }
76 
77     /**
78      * Compares two `UniNode`s for equality.
79      */
80     bool opEquals(const(UniTree) rhs) const pure @safe
81     {
82         return node.opEquals(rhs);
83     }
84 
85     /**
86      * Returns the hash of the `UniNode`'s current value.
87      */
88     size_t toHash() const nothrow @safe
89     {
90         return node.toHash();
91     }
92 
93     /**
94      * Convert UniNode to primitive type
95      */
96     inout(T) get(T)() inout pure @safe
97     {
98         return node.get!T;
99     }
100 
101     /**
102      * Get the node
103      *
104      * Params:
105      * path = The path to the desired site
106      *
107      * Example:
108      * ---
109      * node.get!int("foo.bar");
110      * ---
111      */
112     inout(T) get(T)(string path) inout pure @safe
113     {
114         auto nodePtr = findNode(path);
115         if (!nodePtr)
116             throw new UniNodeException(fmt!"Not found subtree '%s'"(path));
117         static if (is (T : UniTree))
118             return *nodePtr;
119         else
120             return nodePtr.node.get!T;
121     }
122 
123     /**
124      * Convert UniNode to sequence
125      */
126     inout(UniTree[]) getSequence() inout pure @safe
127     {
128         return node.getSequence();
129     }
130 
131     /**
132      * Convert UniNode to sequence
133      */
134     inout(UniTree[]) getSequence(string path) inout pure @safe
135     {
136         auto nodePtr = findNode(path);
137         if (nodePtr)
138             return nodePtr.node.getSequence();
139         else
140             throw new UniNodeException(fmt!"Not found subtree '%s'"(path));
141     }
142 
143     /**
144      * Convert UniNode to mapping
145      */
146     inout(UniTree[string]) getMapping() inout pure @safe
147     {
148         return node.getMapping();
149     }
150 
151     /**
152      * Convert UniNode to mapping
153      */
154     inout(UniTree[string]) getMapping(string path) inout pure @safe
155     {
156         auto nodePtr = findNode(path);
157         if (nodePtr)
158             return nodePtr.node.getMapping();
159         else
160             throw new UniNodeException(fmt!"Not found subtree '%s'"(path));
161     }
162 
163     /**
164      * Convert UniNode to primitive type or return alternative value
165      */
166     inout(T) getOrElse(T)(T alt) inout pure nothrow @safe
167     {
168         return node.getOrElse!T(alt);
169     }
170 
171     /**
172      * Convert UniNode to primitive type or return alternative value
173      */
174     inout(T) getOrElse(T)(string path, T alt) inout pure @safe
175     {
176         auto nodePtr = findNode(path);
177         if (nodePtr)
178         {
179             static if (is (T : UniTree))
180                 return *nodePtr;
181             else
182                 return nodePtr.node.getOrElse!T(alt);
183         }
184         else
185             return alt;
186     }
187 
188     /**
189      * Convert UniNode to optional primitive type
190      */
191     Nullable!(const(T)) opt(T)() const pure @safe
192     {
193         return node.opt!T;
194     }
195 
196     /**
197      * Convert UniNode to optional primitive type
198      */
199     Nullable!(T) opt(T)() pure @safe
200     {
201         return node.opt!T;
202     }
203 
204     /**
205      * Get the tree
206      *
207      * Params:
208      * path = The path to the desired site
209      *
210      * Example:
211      * ---
212      * node.opt!int("foo.bar");
213      * ---
214      */
215     Nullable!(const(T)) opt(T)(string path) const pure @safe
216     {
217         auto nodePtr = findNode(path);
218         if (nodePtr)
219         {
220             static if (is (T : UniTree))
221                 return Nullable!(const(T))(*nodePtr);
222             else
223                 return nodePtr.node.opt!T;
224         }
225         else
226             return Nullable!(const(T)).init;
227     }
228 
229     /**
230      * Get the tree
231      *
232      * Params:
233      * path = The path to the desired site
234      *
235      * Example:
236      * ---
237      * node.opt!int("foo.bar");
238      * ---
239      */
240     Nullable!(T) opt(T)(string path) pure @safe
241     {
242         auto nodePtr = findNode(path);
243         if (nodePtr)
244         {
245             static if (is (T : UniTree))
246                 return Nullable!(T)(*nodePtr);
247             else
248                 return nodePtr.node.opt!T;
249         }
250         else
251             return Nullable!(T).init;
252     }
253 
254     /**
255      * Getting node or throw exception
256      */
257     inout(T) getOrThrown(T, E = UniNodeException)(lazy string msg,
258             size_t line = __LINE__, string file = __FILE__) inout pure @safe
259     {
260         try
261             return node.get!T;
262         catch (Exception e)
263             throw new E(msg, file, line);
264     }
265 
266     /**
267      * Getting node or throw exception with path
268      */
269     inout(T) getOrThrown(T, E = UniNodeException)(string path, lazy string msg,
270             size_t line = __LINE__, string file = __FILE__) inout pure @safe
271     {
272         auto nodePtr = findNode(path);
273         if (!nodePtr)
274             throw new E(msg, file, line);
275         static if (is (T : UniTree))
276             return *nodePtr;
277         else
278         {
279             try
280                 return nodePtr.node.get!T;
281             catch (Exception e)
282                 throw new E(msg, file, line);
283         }
284     }
285 
286     /**
287      * Checking for the presence of the node in the specified path
288      *
289      * It the node is an object, the we try to find the embedded objects in the specified path
290      *
291      * Params:
292      *
293      * path = The path to the desired site
294      */
295     inout(UniTree)* opBinaryRight(string op)(string path) inout
296         if (op == "in")
297     {
298         return findNode(path);
299     }
300 
301     /**
302      * Recursive merge properties
303      *
304      * When the merger is not going to existing nodes
305      * If the parameter is an array, it will their concatenation
306      *
307      * Params:
308      *
309      * src = Source properties
310      */
311     UniTree opBinary(string op)(auto ref UniTree src)
312         if ("~" == op)
313     {
314         if (src.canNil)
315             return this; // bitcopy this
316 
317         if (this.canNil)
318             return src; //bintcopy src
319 
320         void mergeNode(ref UniTree dst, ref UniTree src) @safe
321         {
322             if (dst.canNil)
323             {
324                 if (src.canMapping)
325                     dst = UniTree.emptyMapping;
326 
327                 if (src.canSequence)
328                     dst = UniTree.emptySequence;
329             }
330 
331             if (dst.canMapping && src.canMapping)
332             {
333                 foreach (string key, ref UniTree ch; src)
334                 {
335                     if (auto tg = key in dst)
336                         mergeNode(*tg, ch);
337                     else
338                         dst[key] = ch;
339                 }
340             }
341             else if (dst.canSequence && src.canSequence)
342                 dst = UniTree(dst.getSequence ~ src.getSequence);
343         }
344 
345         UniTree ret;
346         mergeNode(ret, this);
347         mergeNode(ret, src);
348         return ret;
349     }
350 
351 private:
352 
353     /**
354      * Getting node in the specified path
355      *
356      * It the node is an object, the we try to find the embedded objects in the specified path
357      *
358      * Params:
359      *
360      * path = The path to the desired site
361      */
362     inout(UniTree)* findNode(string path) inout pure @safe
363     {
364         auto names = path.split(DELIMITER_CHAR);
365 
366         inout(UniTree)* findPath(inout(UniTree)* node, string[] names) inout
367         {
368             if(names.length == 0)
369                 return node;
370 
371             immutable name = names[0];
372             if (node.canMapping)
373                 if (auto chd = name in (*node).node)
374                     return findPath(chd, names[1..$]);
375 
376             return null;
377         }
378 
379         if (names.length > 0)
380             return findPath(&this, names);
381 
382         return null;
383     }
384 }
385