From 477b2e5fdc879b1bd03381ea9a5553f12ee0006a Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Wed, 3 Nov 2021 22:08:46 +0100 Subject: [PATCH 1/4] create Settings library --- modules/Settings.js | 145 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 modules/Settings.js diff --git a/modules/Settings.js b/modules/Settings.js new file mode 100644 index 000000000..94767643d --- /dev/null +++ b/modules/Settings.js @@ -0,0 +1,145 @@ +/* + +Usage: + +``` +// open settings for an app +var appSettings = require('Settings').app(appname); +// read a single setting +value = appSettings.get(path, default); +// omit path to read all app settings +value = appSettings.get(); +// write a single app setting +appSettings.set(path, value) +// omit path and pass an object as values to overwrite all settings for an app +appSettings.set(values) + +// open Bangle settings +var globalSettings = require('Settings').Bangle(); +value = globalSettings.get(path, default); +// read all global settings +values = globalSettings.get(); +// write a global setting +globalSettings.set(path, value) +// overwrite all global settings +globalSettings.set(values) +``` + +For example: +``` +var settings = require('Settings').app('test'); +settings.set('foo.bar.baz', 123); // writes to 'test.settings.json' +settings.set('foo.bar.bam', 456); // updates 'test.settings.json' +// 'test.settings.json' now contains {foo:{bar:{baz:123,bam:456}}} +baz = settings.get('foo.bar.baz'); // baz = 123 +bar = settings.get('foo.bar'); // bar = {baz: 123, bam:456} +def = settings.get('asdf.jkl', 123); // def = 123 +all = settings.get(); // all = {foo: { bar: {baz: 123, bam:456} } } + +settings.set({fuz: 789}); // overwrites 'test.settings.json' +// 'test.settings.json' now contains {fuz:789} +fuz = settings.get('fuz'); // fuz = 789 +baz = settings.get('foo.bar.baz'); // baz = undefined + +wakeOnTouch = require('Settings').Bangle().get('options.wakeOnTouch', false); +``` + +*/ + +/** + * + * @param{string} file Settings file + * @param path Path to setting, omit to get complete settings object + * @param def Default value + * @return {*} Setting + */ +function get(file, path, def) { + let setting = require("Storage").readJSON(file); + if (def===undefined && ["object", "undefined"].includes(typeof path)) { + // get(app) or get(app, def): get all settings + def = path; + path = []; + } else { + path = path.split("."); + } + if (path.includes("")) { + throw "Settings: path cannot contain empty elements"; + } + while(path.length) { + const key = path.shift(); + if (typeof setting!=="object" || !(key in setting)) { + return def; + } + setting = setting[key]; + } + return setting; +} + +/** + * @param {string} file Settings file + * @param path Path to setting, omit to replace all settings + * @param value Value to store + */ +function set(file, path, value) { + if (value===undefined && typeof path==="object") { + // set(file, value): overwrite settings completely + require("Storage").writeJSON(file, path); + return; + } + path = path.split("."); // empty string is not OK (becomes [""]) + if (path.includes("")) { + throw "Settings: path cannot contain empty elements"; + } + let setting; + try {setting = get(file);} catch(e) {} // if reading old settings failed we write a fresh object + if (typeof setting!=="object") { + setting = {}; + } + let root = setting; // keep a reference to root object + const leaf = path.pop(); + while(path.length) { + const key = path.shift(); + if (!(key in setting) || typeof (setting[key])!=="object") { + setting[key] = {}; + } + setting = setting[key]; + } + setting[leaf] = value; + require("Storage").write(file, root); +} + +/** + * Open settings file + * + * @param {string} file Settings file + * @return {object} Settings setter and getter + */ +function open(file) { + return { + set: (path, val) => set(file, path, val), + get: (path, def) => get(file, path, def), + }; +} + +/** + * Open settings file directly + * Please use require('Settings').app() or require('Settings').Bangle() instead + * + * @param {string} file Settings file to open + * @return Settings object + */ +exports.open = open; + +/** + * Open app settings file + * + * @param {string} app App name for which to open settings + * @return Settings object + */ +exports.app = (app) => open(app+".settings.json"); +/** + * Open global settings file + * + * @return Settings object + */ +exports.Bangle = () => open("setting.json"); From 68569cd9babb31c4dd2c059bd2ac2bae52c96b80 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 7 Nov 2021 17:46:19 +0100 Subject: [PATCH 2/4] simplify Settings library --- modules/Settings.js | 97 ++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 63 deletions(-) diff --git a/modules/Settings.js b/modules/Settings.js index 94767643d..4b0d87230 100644 --- a/modules/Settings.js +++ b/modules/Settings.js @@ -6,21 +6,21 @@ Usage: // open settings for an app var appSettings = require('Settings').app(appname); // read a single setting -value = appSettings.get(path, default); -// omit path to read all app settings +value = appSettings.get(key, default); +// omit key to read all app settings value = appSettings.get(); // write a single app setting -appSettings.set(path, value) -// omit path and pass an object as values to overwrite all settings for an app +appSettings.set(key, value) +// omit key and pass an object as values to overwrite all settings for an app appSettings.set(values) // open Bangle settings var globalSettings = require('Settings').Bangle(); -value = globalSettings.get(path, default); +value = globalSettings.get(key, default); // read all global settings values = globalSettings.get(); // write a global setting -globalSettings.set(path, value) +globalSettings.set(key, value) // overwrite all global settings globalSettings.set(values) ``` @@ -28,20 +28,15 @@ globalSettings.set(values) For example: ``` var settings = require('Settings').app('test'); -settings.set('foo.bar.baz', 123); // writes to 'test.settings.json' -settings.set('foo.bar.bam', 456); // updates 'test.settings.json' -// 'test.settings.json' now contains {foo:{bar:{baz:123,bam:456}}} -baz = settings.get('foo.bar.baz'); // baz = 123 -bar = settings.get('foo.bar'); // bar = {baz: 123, bam:456} -def = settings.get('asdf.jkl', 123); // def = 123 -all = settings.get(); // all = {foo: { bar: {baz: 123, bam:456} } } +settings.set('foo', 123); // writes to 'test.settings.json' +settings.set('bar', 456); // updates 'test.settings.json' +// 'test.settings.json' now contains {baz:123,bam:456} +baz = settings.get('foo'); // baz = 123 +def = settings.get('jkl', 789); // def = 789 +all = settings.get(); // all = {foo: 123, bar: 456} +baz = settings.get('baz'); // baz = undefined -settings.set({fuz: 789}); // overwrites 'test.settings.json' -// 'test.settings.json' now contains {fuz:789} -fuz = settings.get('fuz'); // fuz = 789 -baz = settings.get('foo.bar.baz'); // baz = undefined - -wakeOnTouch = require('Settings').Bangle().get('options.wakeOnTouch', false); +vibrate = require('Settings').Bangle().get('vibrate', true); ``` */ @@ -49,63 +44,39 @@ wakeOnTouch = require('Settings').Bangle().get('options.wakeOnTouch', false); /** * * @param{string} file Settings file - * @param path Path to setting, omit to get complete settings object + * @param key Setting to get, omit to get all settings as object * @param def Default value * @return {*} Setting */ -function get(file, path, def) { - let setting = require("Storage").readJSON(file); - if (def===undefined && ["object", "undefined"].includes(typeof path)) { - // get(app) or get(app, def): get all settings - def = path; - path = []; - } else { - path = path.split("."); +function get(file, key, def) { + var s = require("Storage").readJSON(file); + if (def===undefined && ["object", "undefined"].includes(typeof key)) { + // get(file) or get(file, def): get all settings + return (s!==undefined) ? s : key; } - if (path.includes("")) { - throw "Settings: path cannot contain empty elements"; + if (typeof s!=="object" || !(key in s)) { + return def; } - while(path.length) { - const key = path.shift(); - if (typeof setting!=="object" || !(key in setting)) { - return def; - } - setting = setting[key]; - } - return setting; + return s[key]; } /** * @param {string} file Settings file - * @param path Path to setting, omit to replace all settings + * @param key Setting to change, omit to replace all settings * @param value Value to store */ -function set(file, path, value) { - if (value===undefined && typeof path==="object") { +function set(file, key, value) { + if (value===undefined && typeof key==="object") { // set(file, value): overwrite settings completely - require("Storage").writeJSON(file, path); + require("Storage").writeJSON(file, key); return; } - path = path.split("."); // empty string is not OK (becomes [""]) - if (path.includes("")) { - throw "Settings: path cannot contain empty elements"; + var s = require("Storage").readJSON(file,1); + if (typeof s!=="object") { + s = {}; } - let setting; - try {setting = get(file);} catch(e) {} // if reading old settings failed we write a fresh object - if (typeof setting!=="object") { - setting = {}; - } - let root = setting; // keep a reference to root object - const leaf = path.pop(); - while(path.length) { - const key = path.shift(); - if (!(key in setting) || typeof (setting[key])!=="object") { - setting[key] = {}; - } - setting = setting[key]; - } - setting[leaf] = value; - require("Storage").write(file, root); + s[key] = value; + require("Storage").write(file, s); } /** @@ -116,8 +87,8 @@ function set(file, path, value) { */ function open(file) { return { - set: (path, val) => set(file, path, val), - get: (path, def) => get(file, path, def), + set: (key, val) => set(file, key, val), + get: (key, def) => get(file, key, def), }; } From b44df86f27957b82e120de33fa9712bdb84ec977 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Mon, 8 Nov 2021 19:56:05 +0100 Subject: [PATCH 3/4] simplify Settings library some more, suggest users make their own helper --- modules/Settings.js | 107 ++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/modules/Settings.js b/modules/Settings.js index 4b0d87230..54e8b4fc2 100644 --- a/modules/Settings.js +++ b/modules/Settings.js @@ -3,50 +3,50 @@ Usage: ``` -// open settings for an app -var appSettings = require('Settings').app(appname); -// read a single setting -value = appSettings.get(key, default); +// read a single app setting +value = require('Settings').get(appname, key, default); // omit key to read all app settings -value = appSettings.get(); +value = require('Settings').get(); // write a single app setting -appSettings.set(key, value) -// omit key and pass an object as values to overwrite all settings for an app -appSettings.set(values) +require('Settings').set(appname, key, value) +// omit key and pass an object as values to overwrite all settings +require('Settings').set(appname, values) -// open Bangle settings -var globalSettings = require('Settings').Bangle(); -value = globalSettings.get(key, default); +// read Bangle settings by passing the Bangle object instead of an app name +value = require('Settings').get(Bangle, key, default); // read all global settings -values = globalSettings.get(); +values = require('Settings').get(Bangle); // write a global setting -globalSettings.set(key, value) -// overwrite all global settings -globalSettings.set(values) +require('Settings').set(Bangle, key, value) ``` For example: ``` -var settings = require('Settings').app('test'); -settings.set('foo', 123); // writes to 'test.settings.json' -settings.set('bar', 456); // updates 'test.settings.json' +require('Settings').set('test', 'foo', 123); // writes to 'test.settings.json' +require('Settings').set('test', 'bar', 456); // updates 'test.settings.json' // 'test.settings.json' now contains {baz:123,bam:456} -baz = settings.get('foo'); // baz = 123 -def = settings.get('jkl', 789); // def = 789 -all = settings.get(); // all = {foo: 123, bar: 456} -baz = settings.get('baz'); // baz = undefined +baz = require('Settings').get('test', 'foo'); // baz = 123 +def = require('Settings').get('test', 'jkl', 789); // def = 789 +all = require('Settings').get('test'); // all = {foo: 123, bar: 456} +baz = require('Settings').get('test', 'baz'); // baz = undefined -vibrate = require('Settings').Bangle().get('vibrate', true); +// read global setting +vibrate = require('Settings').get(Bangle, 'vibrate', true); + +// Hint: if your app reads multiple settings, you can create a helper function: +function s(key, def){return require('Settings').get('myapp', key, def);} +var foo = s('foo setting', 'default value'), bar = s('bar setting'); ``` */ /** + * Read setting value from file * - * @param{string} file Settings file - * @param key Setting to get, omit to get all settings as object - * @param def Default value - * @return {*} Setting + * @param {string} file Settings file + * @param {string} key Setting to get, omit to get all settings as object + * @param {*} def Default value + * @return {*} Setting value (or default if not found) */ function get(file, key, def) { var s = require("Storage").readJSON(file); @@ -61,9 +61,11 @@ function get(file, key, def) { } /** - * @param {string} file Settings file - * @param key Setting to change, omit to replace all settings - * @param value Value to store + * Write setting value to file + * + * @param {string} file Settings file + * @param {string} key Setting to change, omit to replace all settings + * @param {*} value Value to store */ function set(file, key, value) { if (value===undefined && typeof key==="object") { @@ -71,7 +73,7 @@ function set(file, key, value) { require("Storage").writeJSON(file, key); return; } - var s = require("Storage").readJSON(file,1); + var s = require("Storage").readJSON(file, 1); if (typeof s!=="object") { s = {}; } @@ -80,37 +82,24 @@ function set(file, key, value) { } /** - * Open settings file + * Read setting value * - * @param {string} file Settings file - * @return {object} Settings setter and getter + * @param {string|object} app App name or Bangle + * @param {string} key Setting to get, omit to get all settings as object + * @param {*} def Default value + * @return {*} Setting value (or default if not found) */ -function open(file) { - return { - set: (key, val) => set(file, key, val), - get: (key, def) => get(file, key, def), - }; -} +exports.get = function(app, key, def) { + return get((app===Bangle) ? setting.json : app+".settings.json", key, def); +}; /** - * Open settings file directly - * Please use require('Settings').app() or require('Settings').Bangle() instead + * Write setting value * - * @param {string} file Settings file to open - * @return Settings object + * @param {string|object} app App name or Bangle + * @param {string} key Setting to change, omit to replace all settings + * @param {*} val Value to store */ -exports.open = open; - -/** - * Open app settings file - * - * @param {string} app App name for which to open settings - * @return Settings object - */ -exports.app = (app) => open(app+".settings.json"); -/** - * Open global settings file - * - * @return Settings object - */ -exports.Bangle = () => open("setting.json"); +exports.set = function(app, key, val) { + set((app===Bangle) ? setting.json : app+".settings.json", key, val); +}; From efd12d627a000c754e60a819bc91f115d3398e14 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 20 Nov 2021 17:52:44 +0100 Subject: [PATCH 4/4] Settings library: use `appid.json`, update README.md and sanitycheck.js --- README.md | 23 ++++++++++------------- bin/sanitycheck.js | 2 ++ modules/Settings.js | 30 +++++++++++++----------------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ac80b8270..20ae8afb2 100644 --- a/README.md +++ b/README.md @@ -377,40 +377,37 @@ that handles configuring the app. When the app settings are opened, this function is called with one argument, `back`: a callback to return to the settings menu. -Usually it will save any information in `app.json` where `app` is the name +Usually it will save any information in `myappid.json` where `myappid` is the name of your app - so you should change the example accordingly. Example `settings.js` ```js // make sure to enclose the function in parentheses (function(back) { - let settings = require('Storage').readJSON('app.json',1)||{}; - function save(key, value) { - settings[key] = value; - require('Storage').write('app.json',settings); - } + function get(key, def) { return require('Settings').get('myappid', key, def); } + function set(key, value) { require('Settings').set('myappid', key, value); } const appMenu = { '': {'title': 'App Settings'}, '< Back': back, 'Monkeys': { - value: settings.monkeys||12, - onchange: (m) => {save('monkeys', m)} + value: get('monkeys', 12), + onchange: (m) => set('monkeys', m) } }; E.showMenu(appMenu) }) ``` -In this example the app needs to add `app.settings.js` to `storage` in `apps.json`. -It should also add `app.json` to `data`, to make sure it is cleaned up when the app is uninstalled. +In this example the app needs to add `myappid.settings.js` to `storage` in `apps.json`. +It should also add `myappid.json` to `data`, to make sure it is cleaned up when the app is uninstalled. ```json - { "id": "app", + { "id": "myappid", ... "storage": [ ... - {"name":"app.settings.js","url":"settings.js"}, + {"name":"myappid.settings.js","url":"settings.js"} ], "data": [ - {"name":"app.json"} + {"name":"myappid.json"} ] }, ``` diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index a84d26efd..ea45dc19b 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -209,6 +209,8 @@ apps.forEach((app,appIdx) => { // prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?) if (dataNames.includes(app.id+".settings.json") && !dataNames.includes(app.id+".json")) WARN(`App ${app.id} uses data file ${app.id+'.settings.json'} instead of ${app.id+'.json'}`) + else if (dataNames.includes(app.id+".settings.json")) + WARN(`App ${app.id} uses data file ${app.id+'.settings.json'}`) // settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?) if (fileNames.includes(app.id+".settings.json")) WARN(`App ${app.id} uses storage file ${app.id+'.settings.json'} instead of data file`) diff --git a/modules/Settings.js b/modules/Settings.js index 54e8b4fc2..8d7fba653 100644 --- a/modules/Settings.js +++ b/modules/Settings.js @@ -1,16 +1,17 @@ /* +- Read/write app settings, stored in .json +- Read/write global settings (stored in setting.json) Usage: - ``` // read a single app setting -value = require('Settings').get(appname, key, default); +value = require('Settings').get(appid, key, default); // omit key to read all app settings value = require('Settings').get(); // write a single app setting -require('Settings').set(appname, key, value) +require('Settings').set(appid, key, value) // omit key and pass an object as values to overwrite all settings -require('Settings').set(appname, values) +require('Settings').set(appid, values) // read Bangle settings by passing the Bangle object instead of an app name value = require('Settings').get(Bangle, key, default); @@ -22,9 +23,9 @@ require('Settings').set(Bangle, key, value) For example: ``` -require('Settings').set('test', 'foo', 123); // writes to 'test.settings.json' -require('Settings').set('test', 'bar', 456); // updates 'test.settings.json' -// 'test.settings.json' now contains {baz:123,bam:456} +require('Settings').set('test', 'foo', 123); // writes to 'test.json' +require('Settings').set('test', 'bar', 456); // updates 'test.json' +// 'test.json' now contains {baz:123,bam:456} baz = require('Settings').get('test', 'foo'); // baz = 123 def = require('Settings').get('test', 'jkl', 789); // def = 789 all = require('Settings').get('test'); // all = {foo: 123, bar: 456} @@ -34,7 +35,7 @@ baz = require('Settings').get('test', 'baz'); // baz = undefined vibrate = require('Settings').get(Bangle, 'vibrate', true); // Hint: if your app reads multiple settings, you can create a helper function: -function s(key, def){return require('Settings').get('myapp', key, def);} +function s(key, def) { return require('Settings').get('myapp', key, def); } var foo = s('foo setting', 'default value'), bar = s('bar setting'); ``` @@ -54,10 +55,7 @@ function get(file, key, def) { // get(file) or get(file, def): get all settings return (s!==undefined) ? s : key; } - if (typeof s!=="object" || !(key in s)) { - return def; - } - return s[key]; + return ((typeof s==="object") && (key in s)) ? s[key] : def; } /** @@ -74,9 +72,7 @@ function set(file, key, value) { return; } var s = require("Storage").readJSON(file, 1); - if (typeof s!=="object") { - s = {}; - } + if (typeof s!=="object") s = {}; s[key] = value; require("Storage").write(file, s); } @@ -90,7 +86,7 @@ function set(file, key, value) { * @return {*} Setting value (or default if not found) */ exports.get = function(app, key, def) { - return get((app===Bangle) ? setting.json : app+".settings.json", key, def); + return get((app===Bangle) ? 'setting.json' : app+".json", key, def); }; /** @@ -101,5 +97,5 @@ exports.get = function(app, key, def) { * @param {*} val Value to store */ exports.set = function(app, key, val) { - set((app===Bangle) ? setting.json : app+".settings.json", key, val); + set((app===Bangle) ? 'setting.json' : app+".json", key, val); };