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