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