Skip to content
Snippets Groups Projects
Select Git revision
  • de060cc11d40c9091d04bbd7157f3829039ca1a5
  • master default protected
  • 1.31
  • 4.38.7
  • 4.38.6
  • 4.38.5
  • 4.38.4
  • 4.38.3
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
23 results

Monster_Text.html

Blame
  • pathfinder.mjs 10.42 KiB
    /**
     * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
     * Node module: @schukai/monster
     *
     * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
     * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
     *
     * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
     * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
     * For more information about purchasing a commercial license, please contact schukai GmbH.
     *
     * SPDX-License-Identifier: AGPL-3.0
     */
    
    import { Base } from "../types/base.mjs";
    import {
    	isArray,
    	isInteger,
    	isObject,
    	isPrimitive,
    	isString,
    } from "../types/is.mjs";
    import { Stack } from "../types/stack.mjs";
    import {
    	validateInteger,
    	validateBoolean,
    	validateString,
    } from "../types/validate.mjs";
    
    export { Pathfinder, DELIMITER, WILDCARD };
    
    /**
     * path separator
     *
     * @private
     * @type {string}
     */
    const DELIMITER = ".";
    
    /**
     * @private
     * @type {string}
     */
    const WILDCARD = "*";
    
    /**
     * Pathfinder is a class to find a path to an object.
     *
     * 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}]}}
     * ```
     *
     * @example /examples/libraries/pathfinder/example-1/ Example 1
     * @example /examples/libraries/pathfinder/example-2/ Example 2
     * 
     * @license AGPLv3
     * @since 1.4.0
     * @copyright schukai GmbH
     * @summary Pathfinder is a class to find a path to an object.
     */
    class Pathfinder extends Base {
    	/**
    	 * Creates a new instance of the constructor.
    	 *
    	 * @param {object} object - The object parameter for the constructor.
    	 *
    	 * @throws {Error} Throws an error if the provided object parameter is a simple type.
    	 */
    	constructor(object) {
    		super();
    
    		if (isPrimitive(object)) {
    			throw new Error("the parameter must not be a simple type");
    		}
    
    		this.object = object;
    		this.wildCard = WILDCARD;
    	}
    
    	/**
    	 * set wildcard
    	 *
    	 * @param {string} wildcard
    	 * @return {Pathfinder}
    	 * @since 1.7.0
    	 */
    	setWildCard(wildcard) {
    		validateString(wildcard);
    		this.wildCard = wildcard;
    		return this;
    	}
    
    	/**
    	 *
    	 * @param {string|array} path
    	 * @since 1.4.0
    	 * @return {*}
    	 * @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) {
    		return getValueViaPath.call(this, this.object, path);
    	}
    
    	/**
    	 *
    	 * @param {string|array} path
    	 * @param {*} value
    	 * @return {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) {
    		setValueViaPath.call(this, this.object, path, value);
    		return this;
    	}
    
    	/**
    	 * Delete Via Path
    	 *
    	 * @param {string|array} path
    	 * @return {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) {
    		deleteValueViaPath.call(this, this.object, path);
    		return this;
    	}
    
    	/**
    	 *
    	 * @param {string|array} 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) {
    		try {
    			getValueViaPath.call(this, this.object, path, true);
    			return true;
    		} catch (e) {}
    
    		return false;
    	}
    }
    
    /**
     *
     * @param {*} subject
     * @param {string|array} path
     * @param {boolean} check
     * @return {Map}
     * @throws {TypeError} unsupported type
     * @throws {Error} the journey is not at its end
     * @throws {Error} unsupported action for this data type
     * @private
     */
    function iterate(subject, path, check) {
    	if (check === undefined) {
    		check = false;
    	}
    	validateBoolean(check);
    
    	const result = new Map();
    
    	if (isArray(path)) {
    		path = path.join(DELIMITER);
    	}
    
    	if (isObject(subject) || isArray(subject)) {
    		for (const [key, value] of Object.entries(subject)) {
    			result.set(key, getValueViaPath.call(this, value, path, check));
    		}
    	} else {
    		const key = path.split(DELIMITER).shift();
    		result.set(key, getValueViaPath.call(this, subject, path, check));
    	}
    
    	return result;
    }
    
    /**
     *
     * @param subject
     * @param path
     * @param check
     * @return {V|*|Map}
     * @throws {TypeError} unsupported type
     * @throws {Error} the journey is not at its end
     * @throws {Error} unsupported action for this data type
     */
    function getValueViaPath(subject, path, check) {
    	if (check === undefined) {
    		check = false;
    	}
    	validateBoolean(check);
    
    	if (!(isArray(path) || isString(path))) {
    		throw new Error(
    			"type error: a path must be a string or an array in getValueViaPath",
    		);
    	}
    
    	let parts;
    	if (isString(path)) {
    		if (path === "") {
    			return subject;
    		}
    
    		parts = path.split(DELIMITER);
    	}
    
    	let current = parts.shift();
    
    	if (current === this.wildCard) {
    		return iterate.call(this, subject, parts.join(DELIMITER), check);
    	}
    
    	if (isObject(subject) || isArray(subject)) {
    		let anchor;
    		if (subject instanceof Map || subject instanceof WeakMap) {
    			anchor = subject.get(current);
    		} else if (subject instanceof Set || subject instanceof WeakSet) {
    			current = parseInt(current);
    			validateInteger(current);
    			anchor = [...subject]?.[current];
    		} else if (typeof WeakRef === "function" && subject instanceof WeakRef) {
    			throw Error("unsupported action for this data type (WeakRef)");
    		} else if (isArray(subject)) {
    			current = parseInt(current);
    			validateInteger(current);
    			anchor = subject?.[current];
    		} else {
    			anchor = subject?.[current];
    		}
    
    		if (isObject(anchor) || isArray(anchor)) {
    			return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check);
    		}
    
    		if (parts.length > 0) {
    			throw Error(`the journey is not at its end (${parts.join(DELIMITER)})`);
    		}
    
    		if (check === true) {
    			const descriptor = Object.getOwnPropertyDescriptor(
    				Object.getPrototypeOf(subject),
    				current,
    			);
    
    			if (!subject.hasOwnProperty(current) && descriptor === undefined) {
    				throw Error("unknown value " + current);
    			}
    		}
    
    		return anchor;
    	}
    
    	throw TypeError(`unsupported type ${typeof subject} for path ${path}`);
    }
    
    /**
     *
     * @param {object} subject
     * @param {string|array} path
     * @param {*} value
     * @return {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(subject, path, value) {
    	if (!(isArray(path) || isString(path))) {
    		throw new Error("type error: a path must be a string or an array");
    	}
    
    	let parts;
    	if (isArray(path)) {
    		if (path.length === 0) {
    			return;
    		}
    
    		parts = path;
    	} else {
    		parts = path.split(DELIMITER);
    	}
    
    	let last = parts.pop();
    	const subpath = parts.join(DELIMITER);
    
    	const stack = new Stack();
    	let current = subpath;
    	while (true) {
    		try {
    			getValueViaPath.call(this, subject, 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()) {
    			const n = stack.peek().split(DELIMITER).pop();
    			if (isInteger(parseInt(n))) {
    				obj = [];
    			}
    		}
    
    		setValueViaPath.call(this, subject, current, obj);
    	}
    
    	const anchor = getValueViaPath.call(this, subject, subpath);
    
    	if (!(isObject(subject) || isArray(subject))) {
    		throw TypeError(`unsupported type: ${typeof subject} in setValueViaPath`);
    	}
    
    	if (anchor instanceof Map || anchor instanceof WeakMap) {
    		anchor.set(last, value);
    	} else if (anchor instanceof Set || anchor instanceof WeakSet) {
    		anchor.append(value);
    	} else if (typeof WeakRef === "function" && anchor instanceof WeakRef) {
    		throw Error("unsupported action for this data type in setValueViaPath");
    	} else if (isArray(anchor)) {
    		last = parseInt(last);
    		validateInteger(last);
    		assignProperty(anchor, "" + last, value);
    	} else {
    		assignProperty(anchor, last, value);
    	}
    }
    
    /**
     * @private
     * @param {object} object
     * @param {string} key
     * @param {*} value
     */
    function assignProperty(object, key, value) {
    	if (!object.hasOwnProperty(key)) {
    		object[key] = value;
    		return;
    	}
    
    	if (value === undefined) {
    		delete object[key];
    	}
    
    	object[key] = value;
    }
    
    /**
     *
     * @param {object} subject
     * @param {string} path
     * @return {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
     * @license AGPLv3
     * @since 1.6.0
     * @private
     */
    function deleteValueViaPath(subject, path) {
    	if (!(isArray(path) || isString(path))) {
    		throw new Error(
    			"type error: a path must be a string or an array in deleteValueViaPath",
    		);
    	}
    
    	let parts;
    	if (isArray(path)) {
    		if (path.length === 0) {
    			return;
    		}
    
    		parts = path;
    	} else {
    		parts = path.split(DELIMITER);
    	}
    
    	let last = parts.pop();
    	const subPath = parts.join(DELIMITER);
    
    	const anchor = getValueViaPath.call(this, subject, subPath);
    
    	if (anchor instanceof Map) {
    		anchor.delete(last);
    	} else if (
    		anchor instanceof Set ||
    		anchor instanceof WeakMap ||
    		anchor instanceof WeakSet ||
    		(typeof WeakRef === "function" && anchor instanceof WeakRef)
    	) {
    		throw Error("unsupported action for this data type in deleteValueViaPath");
    	} else if (isArray(anchor)) {
    		last = parseInt(last);
    		validateInteger(last);
    		delete anchor[last];
    	} else {
    		delete anchor[last];
    	}
    }