"use strict"; const defaultValue = require("default-value"); const asExpression = require("as-expression"); // FIXME: Move to stand-alone package, clearly document that it is not order-sensitive function ensureObject(object, property) { if (object[property] == null) { object[property] = {}; } } module.exports = function createNamedTreeBuilder(options = {}) { let parentKey = options.parentKey; let childrenKey = options.childrenKey; let treatRootAsParent = defaultValue(options.treatRootAsParent, true); let root = {}; let done = false; return { add: function (path, item) { if (done === true) { throw new Error(`done() was called on the builder; no further modifications are possible`); } else { let lastItem = root; for (let i = 0; i < path.length; i++) { let segment = path[i]; // eslint-disable-next-line no-loop-func let childrenContainer = asExpression(() => { if (childrenKey != null) { ensureObject(lastItem, childrenKey); return lastItem[childrenKey]; } else { return lastItem; } }); ensureObject(childrenContainer, segment); let child = childrenContainer[segment]; let setParent = ( parentKey != null && child[parentKey] == null && (treatRootAsParent || i >= 1) ); if (setParent) { child[parentKey] = lastItem; } if (i === path.length - 1) { // Last segment, this is where we want to put the item data child = childrenContainer[segment] = { ... item, ... child }; } lastItem = child; } } }, done: function () { done = true; return root; } }; }; // let builder = module.exports({ childrenKey: "children", parentKey: "parent" }); // builder.add([], { description: "root" }); // builder.add(["a"], { description: "root -> a" }); // builder.add(["b", "c"], { description: "root -> b -> c" }); // builder.add(["b"], { description: "root -> b" }); // builder.add(["a", "c"], { description: "root -> a -> c" }); // builder.add(["a", "c", "d"], { description: "root -> a -> c -> d" }); // console.dir(builder.done(), { depth: null });