From ca4b99a83e5cc8633b8f76a5c7b8b23ca97a9c02 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 8 Sep 2015 22:08:45 -0600 Subject: [PATCH] * Convert all JSON configurations (*.json) to HJSON (*.hjson) which is much more flexible for a human readable and editable configuration format * WIP "next" vs "action" changes * options.cls is now defaulted in config.js/config.hjson (default = true) * Notes/etc. --- core/asset.js | 20 ++++++++ core/config.js | 28 ++++++----- core/json_cache.js | 5 +- core/menu_module.js | 11 ++++- core/menu_util.js | 73 +++++++++++++++++++++-------- core/stats.js | 3 +- core/theme.js | 2 +- mods/{menu.json => menu.hjson} | 74 +++++++++--------------------- mods/{prompt.json => prompt.hjson} | 15 ++++++ package.json | 40 ++++++++-------- 10 files changed, 163 insertions(+), 108 deletions(-) rename mods/{menu.json => menu.hjson} (94%) rename mods/{prompt.json => prompt.hjson} (86%) diff --git a/core/asset.js b/core/asset.js index 0e709cc9..7f25a683 100644 --- a/core/asset.js +++ b/core/asset.js @@ -8,6 +8,7 @@ var _ = require('lodash'); var assert = require('assert'); exports.parseAsset = parseAsset; +exports.getAssetWithShorthand = getAssetWithShorthand; exports.getArtAsset = getArtAsset; exports.getModuleAsset = getModuleAsset; exports.resolveConfigAsset = resolveConfigAsset; @@ -42,6 +43,25 @@ function parseAsset(s) { } } +function getAssetWithShorthand(spec, defaultType) { + if(!_.isString(spec)) { + return null; + } + + if('@' === spec[0]) { + var asset = parseAsset(spec); + assert(_.isString(asset.type)); + + return asset; + } else { + return { + type : defaultType, + asset : spec, + } + } +} + +// :TODO: Convert these to getAssetWithShorthand() function getArtAsset(art) { if(!_.isString(art)) { return null; diff --git a/core/config.js b/core/config.js index b9165711..a9d7ca8e 100644 --- a/core/config.js +++ b/core/config.js @@ -5,9 +5,9 @@ var miscUtil = require('./misc_util.js'); var fs = require('fs'); var paths = require('path'); -var stripJsonComments = require('strip-json-comments'); var async = require('async'); var _ = require('lodash'); +var hjson = require('hjson'); exports.init = init; exports.getDefaultPath = getDefaultPath; @@ -22,9 +22,10 @@ function init(configPath, cb) { callback(null, { } ); } else { try { - var configJson = JSON.parse(stripJsonComments(data)); + var configJson = hjson.parse(data); callback(null, configJson); } catch(e) { + console.log(e) callback(e); } } @@ -53,7 +54,7 @@ function init(configPath, cb) { function getDefaultPath() { var base = miscUtil.resolvePath('~/'); if(base) { - return paths.join(base, '.enigma-bbs', 'config.json'); + return paths.join(base, '.enigma-bbs', 'config.hjson'); } } @@ -80,6 +81,16 @@ function getDefaultConfig() { defaultGroups : [ 'users' ] // default groups new users belong to }, + // :TODO: better name for "defaults"... which is redundant here! + /* + Concept + "theme" : { + "default" : "defaultThemeName", // or "*" + "preLogin" : "*", + "passwordChar" : "*", + ... + } + */ defaults : { theme : 'NU-MAYA', // :TODO: allow "*" here passwordChar : '*', // TODO: move to user ? @@ -94,14 +105,9 @@ function getDefaultConfig() { } }, - /* - Concept - "theme" : { - "default" : "defaultThemeName", // or "*" - "passwordChar" : "*", - ... - } - */ + menus : { + cls : true, // Clear screen before each menu by default? + }, paths : { mods : paths.join(__dirname, './../mods/'), diff --git a/core/json_cache.js b/core/json_cache.js index d3a95f00..0e9006cc 100644 --- a/core/json_cache.js +++ b/core/json_cache.js @@ -9,6 +9,7 @@ var fs = require('fs'); var Gaze = require('gaze').Gaze; var stripJsonComments = require('strip-json-comments'); var assert = require('assert'); +var hjson = require('hjson'); module.exports = exports = new JSONCache(); @@ -21,9 +22,11 @@ function JSONCache() { this.reCacheJSONFromFile = function(filePath, cb) { fs.readFile(filePath, { encoding : 'utf-8' }, function fileRead(err, data) { try { - self.cache[filePath] = JSON.parse(stripJsonComments(data)); + //self.cache[filePath] = JSON.parse(stripJsonComments(data)); + self.cache[filePath] = hjson.parse(data); cb(null, self.cache[filePath]); } catch(e) { + console.log(e) cb(e); } }); diff --git a/core/menu_module.js b/core/menu_module.js index dd295810..f1a33346 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -9,6 +9,7 @@ var ansi = require('./ansi_term.js'); var asset = require('./asset.js'); var ViewController = require('./view_controller.js').ViewController; var menuUtil = require('./menu_util.js'); +var Config = require('./config.js').config; var async = require('async'); var assert = require('assert'); @@ -27,6 +28,10 @@ function MenuModule(options) { this.menuConfig.options = options.menuConfig.options || {}; this.menuMethods = {}; // methods called from @method's + this.cls = _.isBoolean(this.menuConfig.options.cls) ? + this.menuConfig.options.cls : + Config.menus.cls; + this.initViewControllers(); this.initSequence = function() { @@ -133,6 +138,7 @@ function MenuModule(options) { return 'end' === self.menuConfig.options.pause || true === self.menuConfig.options.pause; }; + // :TODO: Convert this to process "next" instead of "action" this.nextAction = function() { if(!_.isObject(self.menuConfig.form) && !_.isString(self.menuConfig.prompt) && _.isString(self.menuConfig.action)) @@ -164,7 +170,7 @@ MenuModule.prototype.leave = function() { }; MenuModule.prototype.beforeArt = function() { - if(this.menuConfig.options.cls) { + if(this.cls) { this.client.term.write(ansi.resetScreen()); } @@ -240,7 +246,8 @@ MenuModule.prototype.finishedLoading = function() { _.isString(this.menuConfig.next)) { setTimeout(function nextTimeout() { - self.client.gotoMenuModule( { name : self.menuConfig.next } ); + menuUtil.handleNext(self.client, self.menuConfig.next); + //self.client.gotoMenuModule( { name : self.menuConfig.next } ); }, this.menuConfig.options.nextTimeout); } }; \ No newline at end of file diff --git a/core/menu_util.js b/core/menu_util.js index ce678402..d97760cc 100644 --- a/core/menu_util.js +++ b/core/menu_util.js @@ -17,11 +17,10 @@ var async = require('async'); var assert = require('assert'); var _ = require('lodash'); -var stripJsonComments = require('strip-json-comments'); - exports.loadMenu = loadMenu; exports.getFormConfigByIDAndMap = getFormConfigByIDAndMap; exports.handleAction = handleAction; +exports.handleNext = handleNext; exports.applyThemeCustomization = applyThemeCustomization; function getMenuConfig(name, cb) { @@ -30,7 +29,7 @@ function getMenuConfig(name, cb) { async.waterfall( [ function loadMenuJSON(callback) { - jsonCache.getJSON('menu.json', function loaded(err, menuJson) { + jsonCache.getJSON('menu.hjson', function loaded(err, menuJson) { callback(err, menuJson); }); }, @@ -44,7 +43,7 @@ function getMenuConfig(name, cb) { }, function loadPromptJSON(callback) { if(_.isString(menuConfig.prompt)) { - jsonCache.getJSON('prompt.json', function loaded(err, promptJson, reCached) { + jsonCache.getJSON('prompt.hjson', function loaded(err, promptJson, reCached) { callback(err, promptJson); }); } else { @@ -166,6 +165,20 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) { cb(new Error('No matching form configuration found for key \'' + mciReqKey + '\'')); } +// :TODO: Most of this should be moved elsewhere .... DRY... +function callModuleMenuMethod(client, asset, path, formData) { + if('' === paths.extname(path)) { + path += '.js'; + } + + try { + var methodMod = require(path); + methodMod[asset.asset](client.currentMenuModule, formData || { }, conf.extraArgs); + } catch(e) { + client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method'); + } +} + function handleAction(client, formData, conf) { assert(_.isObject(conf)); assert(_.isString(conf.action)); @@ -173,30 +186,16 @@ function handleAction(client, formData, conf) { var actionAsset = asset.parseAsset(conf.action); assert(_.isObject(actionAsset)); - // :TODO: Most of this should be moved elsewhere .... DRY... - function callModuleMenuMethod(path) { - if('' === paths.extname(path)) { - path += '.js'; - } - - try { - var methodMod = require(path); - methodMod[actionAsset.asset](client.currentMenuModule, formData, conf.extraArgs); - } catch(e) { - Log.error( { error : e.toString(), methodName : actionAsset.asset }, 'Failed to execute asset method'); - } - } - switch(actionAsset.type) { case 'method' : case 'systemMethod' : if(_.isString(actionAsset.location)) { - callModuleMenuMethod(paths.join(Config.paths.mods, actionAsset.location)); + callModuleMenuMethod(client, actionAsset, paths.join(Config.paths.mods, actionAsset.location, formData)); } else { if('systemMethod' === actionAsset.type) { // :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. () // :TODO: Probably better as system_method.js - callModuleMenuMethod(paths.join(__dirname, 'system_menu_method.js')); + callModuleMenuMethod(client, actionAsset, paths.join(__dirname, 'system_menu_method.js'), formData); } else { // local to current module var currentModule = client.currentMenuModule; @@ -213,6 +212,40 @@ function handleAction(client, formData, conf) { } } +function handleNext(client, nextSpec) { + assert(_.isString(nextSpec)); + + var nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu'); + + switch(nextAsset.type) { + case 'method' : + case 'systemMethod' : + if(_.isString(nextAsset.location)) { + callModuleMenuMethod(client, nextAsset, paths.join(Config.paths.mods, actionAsset.location)); + } else { + if('systemMethod' === nextAsset.type) { + // :TODO: see other notes about system_menu_method.js here + callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js')); + } else { + // local to current module + var currentModule = client.currentMenuModule; + if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) { + currentModule.menuMethods[actionAsset.asset]( { }, { } ); + } + } + } + break; + + case 'menu' : + client.gotoMenuModule( { name : nextAsset.asset } ); + break; + + default : + client.log.error( { nextSpec : nextSpec }, 'Invalid asset type for "next"'); + break; + } +} + // :TODO: This should be in theme.js // :TODO: Need to take (optional) form ID to search for (e.g. for multi-form menus) diff --git a/core/stats.js b/core/stats.js index b6e82592..7c92f479 100644 --- a/core/stats.js +++ b/core/stats.js @@ -29,4 +29,5 @@ function getUserLoginHistory(numRequested, cb) { cb(err, loginHistory); } ); -} \ No newline at end of file +} + diff --git a/core/theme.js b/core/theme.js index 013f25d6..b56fde3d 100644 --- a/core/theme.js +++ b/core/theme.js @@ -227,7 +227,7 @@ function displayThemedPause(options, cb) { async.series( [ function loadPromptJSON(callback) { - jsonCache.getJSON('prompt.json', function loaded(err, promptJson) { + jsonCache.getJSON('prompt.hjson', function loaded(err, promptJson) { if(err) { callback(err); } else { diff --git a/mods/menu.json b/mods/menu.hjson similarity index 94% rename from mods/menu.json rename to mods/menu.hjson index 1f46d9da..15da0b95 100644 --- a/mods/menu.json +++ b/mods/menu.hjson @@ -1,4 +1,9 @@ { + /* + ENiGMA½ Menu Configuration + + See http://hjson.org/ for syntax + */ /* Menu Configuration @@ -42,7 +47,6 @@ "art" : "CONNECT", "next" : "matrix", "options" : { - "cls" : true, "nextTimeout" : 1500 } }, @@ -77,38 +81,28 @@ } } } - }, - "options" : { - "cls" : true } }, "login" : { - // :TODO: may want { "prompt" : { "name" : "blah", "action" : ... }} - "prompt" : "userCredentials", + "prompt" : "userLoginCredentials", "fallback" : "matrix", "next" : "fullLoginSequenceLoginArt", - //"next" : "messageArea", "action" : "@systemMethod:login", // :TODO: support alt submit method for prompts // if present, standard filters apply. No need for multiple submit ID's // since a prompt can only utilize one: - "submit" : [ + /*"submit" : [ { - "value" : { "1" : "thing" }, - "action" : "@method:doThings" + "value" : { "password" : null }, + "action" : "@systemMethod:login" } - ], - - "options" : { - "cls" : true - } + ]*/ }, "login2" : { "art" : "USRCRED", "fallback" : "matrix", "next" : "fullLoginSequenceLoginArt", - "options" : { "cls" : true }, "form" : { "0" : { "mci" : { @@ -146,7 +140,6 @@ "logoff" : { "art" : "LOGOFF", "action" : "@systemMethod:logoff", - "options" : { "cls" : true } }, "apply" : { "art" : "APPLY", @@ -227,20 +220,17 @@ } } } - }, - "options" : { - "cls" : true } }, "fullLoginSequenceLoginArt" : { "art" : "LOGIN", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "action" : "@menu:fullLoginSequenceLastCallers" }, "fullLoginSequenceLastCallers": { "module" : "last_callers", "art" : "LASTCALL", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "config" : { "dateTimeFormat" : "ddd MMM Do h:mm a" }, @@ -248,28 +238,27 @@ }, "fullLoginSequenceSysStats" : { "art" : "SYSSTAT", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "action" : "@menu:fullLoginSequenceUserStats" }, "fullLoginSequenceUserStats" : { "art" : "USRSTAT", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "action" : "@menu:mainMenu" }, "newUserActive" : { "art" : "SO-CC1.ANS", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "action" : "@menu:currentUserStats" }, "currentUserStats" : { "art" : "userstats", - "options" : { "cls" : true, "pause" : true } + "options" : { "pause" : true } //"action" : "@menu:lastCallers" }, "mainMenu" : { "art" : "MMENU1", "desc" : "Main Menu", - "options" : { "cls" : true }, "prompt" : "menuCommand", "submit" : [ { @@ -307,7 +296,7 @@ "mainMenuLastCallers" : { "module" : "last_callers", "art" : "LASTCALL", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "config" : { "dateTimeFormat" : "ddd MMM Do h:mm a" }, @@ -315,7 +304,7 @@ }, "mainMenuUserStats" : { "art" : "USRSTAT", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "action" : "@menu:mainMenu" }, /////////////////////////////////////////////////////////////////////// @@ -325,7 +314,6 @@ "module" : "msg_area", "art" : "MSGAREA", "desc" : "Message Area", - "options" : { "cls" : true }, "prompt" : "menuCommand", "submit" : [ { @@ -359,7 +347,6 @@ "messageAreaChangeCurrentArea" : { "art" : "msg_area_list.ans", "module" : "msg_area_list", - "options" : { "cls" : true }, "fallback" : "messageArea", "form" : { "0" : { @@ -394,7 +381,6 @@ "messageAreaMessageList" : { "module" : "msg_list", "art" : "msg_list", - "options" : { "cls" : true }, "fallback" : "messageArea", "config" : { "listType" : "public" @@ -430,7 +416,6 @@ }, "messageAreaViewPost" : { "module" : "msg_area_view_fse", - "options" : { "cls" : true }, "fallback" : "messageArea", // :TODO: remove once default fallback is in place "config" : { "art" : { @@ -559,7 +544,6 @@ "messageAreaNewPost" : { "status" : "Posting message", "module" : "msg_area_post_fse", - "options" : { "cls" : true }, "fallback" : "messageArea", // :TODO: remove once default fallback is in place "config" : { "art" : { @@ -732,7 +716,6 @@ //////////////////////////////////////////////////////////////////////// "idleLogoff" : { "art" : "IDLELOG", - "options" : { "cls" : true }, "action" : "@systemMethod:logoff" }, //////////////////////////////////////////////////////////////////////// @@ -741,7 +724,7 @@ "lastCallers" :{ "module" : "last_callers", "art" : "LASTCALL.ANS", - "options" : { "cls" : true, "pause" : true }, + "options" : { "pause" : true }, "config" : { "dateTimeFormat" : "ddd MMM Do H:mm a" }, @@ -774,7 +757,6 @@ //////////////////////////////////////////////////////////////////////// "demoMain" : { "art" : "demo_selection_vm.ans", - "options" : { "cls" : true }, "form" : { "0" : { "VM" : { @@ -838,7 +820,6 @@ }, "demoEditTextView" : { "art" : "demo_edit_text_view1.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTETETETET" : { @@ -888,7 +869,6 @@ }, "demoSpinAndToggleView" : { "art" : "demo_spin_and_toggle.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTSMSMTM" : { @@ -928,7 +908,6 @@ }, "demoMaskEditView" : { "art" : "demo_mask_edit_text_view1.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTMEME" : { @@ -964,7 +943,6 @@ }, "demoMultiLineEditTextView" : { "art" : "demo_multi_line_edit_text_view1.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTMT" : { @@ -1001,7 +979,6 @@ }, "demoHorizontalMenuView" : { "art" : "demo_horizontal_menu_view1.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTHMHM" : { @@ -1038,7 +1015,6 @@ }, "demoVerticalMenuView" : { "art" : "demo_vertical_menu_view1.ans", - "options" : { "cls" : true }, "form" : { "0" : { "BTVM" : { @@ -1085,7 +1061,6 @@ }, "demoArtDisplay" : { "art" : "demo_selection_vm.ans", - "options" : { "cls" : true }, "form" : { "0" : { "VM" : { @@ -1123,20 +1098,16 @@ } }, "demoDefaultsDosAnsi" : { - "art" : "DM-ENIG2.ANS", - "options" : { "cls" : true } + "art" : "DM-ENIG2.ANS" }, "demoDefaultsDosAnsi_bw_mindgames" : { - "art" : "bw_mindgames.ans", - "options" : { "cls" : true } + "art" : "bw_mindgames.ans" }, "demoDefaultsDosAnsi_test" : { - "art" : "test.ans", - "options" : { "cls" : true } + "art" : "test.ans" }, "demoFullScreenEditor" : { "module" : "@systemModule:fse", - "options" : { "cls" : true }, "config" : { "editorType" : "netMail", "art" : { @@ -1322,7 +1293,6 @@ /* "demoEditTextView" : { "art" : "demo_edit_text_view.ans", - "options" : { "cls" : true }, "form" : { "0" : { "ET1ET2ET3ET5SM4TM6" : { diff --git a/mods/prompt.json b/mods/prompt.hjson similarity index 86% rename from mods/prompt.json rename to mods/prompt.hjson index bf4f8317..856fedb0 100644 --- a/mods/prompt.json +++ b/mods/prompt.hjson @@ -15,6 +15,21 @@ } } }, + "userLoginCredentials" : { + "art" : "USRCRED", + "mci" : { + "ET1" : { + "argName" : "username", + "maxLength" : "@config:users.usernameMax" + }, + "ET2" : { + "submit" : true, + "argName" : "password", + "password" : true, + "maxLength" : "@config:users.passwordMax" + } + } + }, "menuCommand" : { "art" : "menu_prompt.ans", "mci" : { diff --git a/package.json b/package.json index d0eb6f51..737f8d99 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,24 @@ { - "name" : "enigma-bbs", - "version" : "0.0.1-beta", - "description" : "ENiGMA½ Bulletin Board System", - "author" : "Bryan Ashby ", - "dependencies" : { - "async" : "0.9.x", - "binary" : "0.3.x", - "buffers" : "0.1.x", - "bunyan" : "1.3.x", - "iconv-lite" : "0.4.x", - "lodash" : "3.10.x", - "sqlite3" : "3.0.x", - "ssh2" : "0.4.x", - "strip-json-comments" : "1.0.x", - "node-uuid" : "1.4.x", - "moment" : "2.10.x", - "gaze" : "0.5.x", - "mkdirp" : "0.5.x", - "pty.js" : "0.2.x", - "string-format" : "0.5.x" + "name" : "enigma-bbs", + "version" : "0.0.1-beta", + "description" : "ENiGMA½ Bulletin Board System", + "author" : "Bryan Ashby ", + "dependencies" : { + "async" : "0.9.x", + "binary" : "0.3.x", + "buffers" : "0.1.x", + "bunyan" : "1.3.x", + "iconv-lite" : "0.4.x", + "lodash" : "3.10.x", + "sqlite3" : "3.0.x", + "ssh2" : "0.4.x", + "node-uuid" : "1.4.x", + "moment" : "2.10.x", + "gaze" : "0.5.x", + "mkdirp" : "0.5.x", + "pty.js" : "0.2.x", + "string-format" : "0.5.x", + "hjson" : "1.7.x" }, "engine" : "node >= 0.12.2" } \ No newline at end of file