ConfigManager

ConfigManager: Manage(Get, set and update) your config with config path simply with a ruleset!

目前為 2022-08-15 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/449583/1081638/ConfigManager.js

/* eslint-disable no-multi-spaces */

// ==UserScript==
// @name               ConfigManager
// @namespace          ConfigManager
// @version            0.1
// @description        ConfigManager: Manage(Get, set and update) your config with config path simply with a ruleset!
// @author             PY-DNG
// @license            GPL-v3
// @grant              GM_setValue
// @grant              GM_getValue
// @grant              GM_listValues
// @grant              GM_deleteValue
// ==/UserScript==

function ConfigManager(Ruleset) {
	const CM = this;
	const ConfigBase = new Proxy({}, {
		get: function(target, property, reciever) {
			return GM_getValue(property);
		},
		set: function(target, property, value, reciever) {
			return (GM_setValue(property, value), true);
		},
		has: function(target, property) {
			return GM_listValues().includes(property);
		}
	});

	CM.getConfig = getConfig;
	CM.setConfig = setConfig;
	CM.updateConfig = updateConfig;
	CM.updateAllConfigs = updateAllConfigs;
	CM.readPath = readPath;
	CM.pathExists = pathExists;
	CM.mergePath = mergePath;
	CM.getBaseName = getBaseName;
	Object.freeze(CM);

	// Get config value from path (e.g. 'Users/username/' or ['Users', 12345])
	function getConfig(path) {
		// Split path
		path = Array.isArray(path) ? path : path.split('/');

		// Init config if need
		if (!GM_listValues().includes(path[0])) {
			ConfigBase[path[0]] = Ruleset.defaultValues[path[0]];
		}

		// Get config by path
		const target = path.pop();
		let config = readPath(ConfigBase, path);
		return config[target];
	}

	// Set config value to path
	function setConfig(path, value) {
		path = Array.isArray(path) ? path : path = path.split('/');
		const target = path.pop();

		if (path.length > 0) {
			const basekey = path.shift();
			const baseobj = ConfigBase[basekey];
			let config = readPath(baseobj, path);
			if (isObject(config)) {
				config[target] = value;
				ConfigBase[basekey] = baseobj;
			} else {
				Err('Attempt to set a property to a non-object value')
			}
		} else {
			ConfigBase[target] = value;
		}
	}

	function updateConfig(path) {
		const basename = getBaseName(path);
		if (!Ruleset.updaters.hasOwnProperty(basename)) {return;}
		const updaters = Ruleset.updaters[basename];
		const verKey = Ruleset['version-key'];
		const config = getConfig(path);
		for (let i = config[verKey]; i < updaters.length; i++) {
			const updater = updaters[i];
			config = updater(config)
		}
		config[verKey] = updaters.length;
		setConfig(path, config);
	}

	function updateAllConfigs() {
		const keys = GM_listValues();
		keys.forEach((key) => (updateConfig(key)));
	}

	function readPath(obj, path) {
		path = [...path];
		while (path.length > 0) {
			const key = path.shift();
			if (isObject(obj) && hasProp(obj, key)) {
				obj = obj[key]
			} else {
				Err('Attempt to read a property that is not exist (reading "' + key + '" in path "' + path + '")')
			}
		}
		return obj;
	}

	function pathExists(obj, path) {
		path = [...path];
		while (path.length > 0) {
			const key = path.shift();
			if (isObject(obj) && hasProp(obj, key)) {
				obj = obj[key]
			} else {
				return false
			}
		}
		return true;
	}

	function mergePath() {
		return Array.from(arguments).join('/')
	}

	function getBaseName(path) {
		return path.split('/')[0];
	}

	function getPathWithoutBase(path) {
		const p = path.split('/');
		p.shift();
		return p;
	}

	function isObject(obj) {
		return typeof obj === 'object' && obj !== null;
	}

	function hasProp(obj, prop) {
		return obj === ConfigBase ? prop in obj : obj.hasOwnProperty(prop);
	}

	// type: [Error, TypeError]
	function Err(msg, type=0) {
		throw new [Error, TypeError][type](msg);
	}
}