Greasy Fork is available in English.
ConfigManager: Manage(Get, set and update) your config with config path simply with a ruleset!
Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/449583/1334125/ConfigManager.js
/* eslint-disable no-multi-spaces */ // ==UserScript== // @name ConfigManager // @namespace ConfigManager // @version 0.8.2 // @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, storage={}) { const CM = this; const _GM_setValue = storage?.GM_setValue || GM_setValue || Err('ConfigManager: could not find GM_setValue'); const _GM_getValue = storage?.GM_getValue || GM_getValue || Err('ConfigManager: could not find GM_getValue'); const _GM_listValues = storage?.GM_listValues || GM_listValues || Err('ConfigManager: could not find GM_listValues'); const _GM_deleteValue = storage?.GM_deleteValue || GM_deleteValue || Err('ConfigManager: could not find GM_deleteValue'); 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.updateGlobal = updateGlobal; CM.getConfigVersion = getConfigVersion; CM.setDefaults = setDefaults; CM.readPath = readPath; CM.pathExists = pathExists; CM.mergePath = mergePath; CM.getBaseName = getBaseName; CM.mak###bStorage = mak###bStorage; CM.Config = new Proxy({}, { get: function(target, property, reciever) { return makeProxy(getConfig(property), [property]); function makeProxy(config, path, base) { return isObject(config) ? new Proxy(config, { get: function(target, property, reciever) { const newPath = [...path, property]; return makeProxy(inProto(target, property) ? target[property] : getConfig(newPath), [...path, property]); }, set: function(target, property, value, reciever) { return (setConfig([...path, property], value), true); }, deleteProperty: function(target, property) { const parent = getConfig(path); delete parent[property]; setConfig(path, parent); return true; } }) : config; function inProto(obj, prop) { return prop in obj && !obj.hasOwnProperty(prop); } } }, set: function(target, property, value, reciever) { return (_GM_setValue(property, value), true); }, has: function(target, property) { return _GM_listValues().includes(property); }, deleteProperty: function(target, property) { return (_GM_deleteValue(property), true); } }); Object.freeze(CM); // Get config value from path (e.g. 'Users/username/' or ['Users', 12345]) function getConfig(path) { // Split path path = arrPath(path); // Init config if need if (!(path[0] in ConfigBase)) { ConfigBase[path[0]] = Ruleset.defaultValues[path[0]]; } // Get config by path const target = path.pop(); const config = readPath(ConfigBase, path); return config[target]; } // Set config value to path function setConfig(path, value) { path = arrPath(path); const target = path.pop(); // Init config if need if (path.length && !(path[0] in ConfigBase)) { ConfigBase[path[0]] = Ruleset.defaultValues[path[0]]; } 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 { const verKey = Ruleset['version-key']; const oldConfig = ConfigBase[target]; if (isObject(value)) { hasProp(value, verKey) && (hasProp(oldConfig, verKey) ? value[verKey] !== oldConfig[verKey] : true) && Err('Shouldn\'t manually set config version to a version number differs from current version number'); value[verKey] = ConfigBase[target][verKey]; } ConfigBase[target] = value; } } function updateConfig(basename) { let updated = false; // Get updaters and config const updaters = Ruleset.updaters.hasOwnProperty(basename) ? Ruleset.updaters[basename] : []; const verKey = Ruleset['version-key']; let config = getConfig(basename); // Valid check if ([verKey, ...(Ruleset.ignores || [])].includes(basename)) { return false; } if (!updaters.length) { return save(); } // Update for (let i = (config[verKey] || 0); i < updaters.length; i++) { const updater = updaters[i]; config = updater.call(CM, config); updated = true; } // Set version and save return save(); function save() { isObject(config) && (config[verKey] = updaters.length); ConfigBase[basename] = config; return updated; } } function updateAllConfigs() { const keys = _GM_listValues(); keys.forEach((key) => (updateConfig(key))); } function updateGlobal() { let updated = false; const updaters = Ruleset.globalUpdaters || []; const verKey = Ruleset['version-key']; if (!updaters.length) { return save(); } const config = _GM_listValues().reduce((obj, key) => Object.assign(obj, { [key]: _GM_getValue(key) }), {}); // Update for (let i = (config[verKey] || 0); i < updaters.length; i++) { const updater = updaters[i]; config = updater.call(CM, config); updated = true; } // Set version and save return save(); function save() { config[verKey] = updaters.length; Object.keys(config).forEach(key => _GM_setValue(key, config[key])); return updated; } } function getConfigVersion(basename=null) { const verKey = Ruleset['version-key']; return (basename ? ConfigBase[basename] : ConfigBase)[verKey] || 0; } function setDefaults() { for (const [key, val] of Object.entries(Ruleset.defaultValues)) { !(key in ConfigBase) && (ConfigBase[key] = val); } } function mak###bStorage(path) { path = arrPath(path); return { GM_setValue: function(key, value) { setConfig([...path, key], value); }, GM_getValue: function(key, defaultValue) { const val = getConfig([...path, key]); return typeof val === 'undefined' ? defaultValue : val; }, GM_listValues: function() { return Object.keys(getConfig(path)); }, GM_deleteValue: function(key) { const parent = getConfig(path); delete parent[key]; setConfig(path, parent); } } } function readPath(obj, path) { path = arrPath(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 = arrPath(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 arrPath(path)[0]; } function getPathWithoutBase(path) { const p = arrPath(path); p.shift(); return p; } function arrPath(strpath) { return Array.isArray(strpath) ? [...strpath] : strpath.replace(/^\//, '').replace(/\/$/, '').split('/'); } 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); } }