Home Reference Source

stdlib/keyvalues.js

/**
 * Wrapper class for interop with Source Engine
 * 
 * Usage:<br>
 * ```let a = new KeyValues();```<br>
 * ```a["color"] = new Color();```<br>
 * ```b["userData"] = new KeyValues();```<br>
 * ```b["userData"]["foo"] = "bar";```
 */
export class KeyValues {

	/**
	 * Serializes this KeyValues into a format supported by the Source Engine
	 * @param {string} name - The key name of this KeyValues
	 * @type {string}
	 */
	_serialize(name) {
		if (!name)
			throw new Error("name parameter not set");

		var result = "\"" + name + "\"\n{\n";

		var keys = Object.keys(this);
		for(var i = 0; i < keys.length; i++) {
			let key = keys[i];
			let value = this[key];

			if (value instanceof KeyValues) {
				result += value._serialize(key);
			} else if (value instanceof Color) {
				result += "\"" + key + "\" \"" + value.r + " " + value.g + " " + value.b + " " + value.a + "\"\n";
			} else {
				result += "\"" + key + "\" \"" + value + "\"\n";
			}
		}

		result += "}\n";

		return result;
	}

	/**
	 * Creates a new construction context
	 */
	static __startContext() {
		if (KeyValues.__current !== undefined) {
			throw new Error("Another KeyValues construction is busy, unable to start a second context.");
		}
		KeyValues.__current = [new KeyValues()];
	}

	/**
	 * Destroys the current context and returns the constructed KeyValues
	 * @type {KeyValues}
	 */
	static __stopContext() {
		if (KeyValues.__current === undefined) {
			throw new Error("No context started, please create a new one using __startContent");
		}

		if (KeyValues.__current.length != 1) {
			throw new Error("Too many values on the stack, please pop until the root node");
		}

		let result = KeyValues.__current[0];
		KeyValues.__current = undefined;
		return result;
	}

	/**
	 * Creates a new child node and pushes it on the construction stack
	 * This is now the current active node
	 * @param {string} name
	 */
	static __pushNode(name) {
		let node = new KeyValues();
		let cur = KeyValues.__current[KeyValues.__current.length - 1];
		cur[name] = node;
		KeyValues.__current.push(node);
	}

	/**
	 * Pops a node from the construction stack
	 */
	static __popNode() {
		KeyValues.__current.pop();
	}

	/**
	 * Sets a value for a given key
	 * @param {string} name
	 * @param value
	 */
	static __setValue(name, value) {
		let cur = KeyValues.__current[KeyValues.__current.length - 1];
		cur[name] = value;
	}

	static fromObject(object) {
		if (!object)
			throw new Error("Object property not set");
		
		var result = new KeyValues()

		var keys = Object.keys(object);
		for(var i = 0; i < keys.length; i++) {
			var k = keys[i];
			var prop = object[k];

			if (prop instanceof Object)
				result[k] = KeyValues.fromObject(k, prop);
			else
				result[k] = prop;
		}

		return result
	}
}