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