'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {isObject, isArray, isInteger} from '../types/is.js';
import {validateString, validateInteger} from '../types/validate.js';
import {Base} from '../types/base.js';
import {Stack} from "../types/stack.js";
/**
* path separator
*
* @private
* @type {string}
*/
const DELIMITER = '.';
/**
* you can call the method via the monster namespace `new Monster.Data.Pathfinder()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/data/pathfinder.js';
* console.log(new Monster.Data.Pathfinder())
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/data/pathfinder.js';
* console.log(new Pathfinder())
* </script>
* ```
*
* With the help of the pathfinder, values can be read and written from an object construct.
*
* ```
* new Pathfinder({
* a: {
* b: {
* f: [
* {
* g: false,
* }
* ],
* }
* }
* }).getVia("a.b.f.0.g"); // ↦ false
* ```
*
* if a value is not present or has the wrong type, a corresponding exception is thrown.
*
* ```
* new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error
* ```
*
* The `Pathfinder.exists()` method can be used to check whether access to the path is possible.
*
* ```
* new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false
* ```
*
* pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used.
*
* ```
* obj = {};
* new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}}
* ```
*
* @since 1.4.0
* @copyright schukai GmbH
* @memberOf Monster/Data
*/
class Pathfinder extends Base {
/**
* @param {array|object|Map|Set} value
* @since 1.4.0
**/
constructor(object) {
super();
this.object = object;
}
/**
*
* @param {string} path
* @since 1.4.0
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
getVia(path) {
validateString(path);
return getValueViaPath(this.object, path);
}
/**
*
* @param {string} path
* @param {*} value
* @returns {Pathfinder}
* @since 1.4.0
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
setVia(path, value) {
validateString(path);
setValueViaPath(this.object, path, value);
return this;
}
/**
* Delete Via Path
*
* @param {string} path
* @returns {Pathfinder}
* @since 1.6.0
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @throws {Error} unsupported action for this data type
*/
deleteVia(path) {
validateString(path);
deleteValueViaPath(this.object, path);
return this;
}
/**
*
* @param {string} path
* @return {bool}
* @throws {TypeError} unsupported type
* @throws {TypeError} value is not a string
* @throws {TypeError} value is not an integer
* @since 1.4.0
*/
exists(path) {
validateString(path);
try {
getValueViaPath(this.object, path, true);
return true;
} catch (e) {
}
return false;
}
}
Monster.assignToNamespace('Monster.Data', Pathfinder);
export {Monster, Pathfinder}
/**
*
* @param {*} object
* @param [string} path
* @param [boolean} check
* @returns {*}
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @private
*/
function getValueViaPath(object, path, check) {
if (path === "") {
return object;
}
let parts = path.split(DELIMITER)
let current = parts.shift();
if (isObject(object) || isArray(object)) {
let anchor;
if (object instanceof Map || object instanceof WeakMap) {
anchor = object.get(current);
} else if (object instanceof Set || object instanceof WeakSet) {
current = parseInt(current);
validateInteger(current)
anchor = [...object]?.[current];
} else if (object instanceof WeakRef) {
throw Error('unsupported action for this data type');
} else if (isArray(object)) {
current = parseInt(current);
validateInteger(current)
anchor = object?.[current];
} else {
anchor = object?.[current];
}
if (isObject(anchor) || isArray(anchor)) {
return getValueViaPath(anchor, parts.join(DELIMITER), check)
}
if (parts.length > 0) {
throw Error("the journey is not at its end (" + parts.join(DELIMITER) + ")");
}
if (check === true && !object.hasOwnProperty(current)) {
throw Error('unknown value');
}
return anchor;
}
throw TypeError("unsupported type")
}
/**
*
* @param object
* @param path
* @param value
* @returns {void}
* @throws {TypeError} unsupported type
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @private
*/
function setValueViaPath(object, path, value) {
let parts = path.split(DELIMITER)
let last = parts.pop();
let subpath = parts.join(DELIMITER);
let stack = new Stack()
let current = subpath;
while (true) {
try {
getValueViaPath(object, current, true)
break;
} catch (e) {
}
stack.push(current);
parts.pop();
current = parts.join(DELIMITER);
if (current === "") break;
}
while (!stack.isEmpty()) {
current = stack.pop();
let obj = {};
if (!stack.isEmpty()) {
let n = stack.peek().split(DELIMITER).pop();
if (isInteger(parseInt(n))) {
obj = [];
}
}
setValueViaPath(object, current, obj);
}
let anchor = getValueViaPath(object, subpath);
if (!isObject(object) && !isArray(object)) {
throw TypeError("unsupported type: " + typeof object);
}
if (anchor instanceof Map || anchor instanceof WeakMap) {
anchor.set(last, value);
} else if (anchor instanceof Set || anchor instanceof WeakSet) {
anchor.append(value)
} else if (anchor instanceof WeakRef) {
throw Error('unsupported action for this data type');
} else if (isArray(anchor)) {
last = parseInt(last);
validateInteger(last)
anchor[last] = value;
} else {
anchor[last] = value;
}
return;
}
/**
*
* @param object
* @param path
* @returns {void}
* @throws {TypeError} unsupported type
* @throws {TypeError} unsupported type
* @throws {Error} the journey is not at its end
* @throws {Error} unsupported action for this data type
* @since 1.6.0
* @private
*/
function deleteValueViaPath(object, path) {
const parts = path.split(DELIMITER)
let last = parts.pop();
const subpath = parts.join(DELIMITER);
const anchor = getValueViaPath.call(this, object, subpath);
if (anchor instanceof Map) {
anchor.delete(last);
} else if (anchor instanceof Set || anchor instanceof WeakMap || anchor instanceof WeakSet || anchor instanceof WeakRef) {
throw Error('unsupported action for this data type');
} else if (isArray(anchor)) {
last = parseInt(last);
validateInteger(last)
delete anchor[last];
} else {
delete anchor[last];
}
return;
}