From 1ef9a4a1ce6557c720686509da187f1203a91487 Mon Sep 17 00:00:00 2001 From: NuSkooler Date: Wed, 29 Oct 2014 22:23:44 -0600 Subject: [PATCH] * Code cleanup. WIP theme stuff. Better CPR handling, etc. --- core/ansi_escape_parser.js | 22 +++++++++---- core/ansi_term.js | 57 +------------------------------- core/art.js | 55 +++++++++++++------------------ core/bbs.js | 22 ++++++++++++- core/client.js | 26 +++------------ core/client_term.js | 8 ++--- core/config.js | 3 ++ core/theme.js | 67 +++++++++++++++++++++++++++++++++++++- core/user.js | 1 + core/view.js | 9 +++-- mods/matrix.js | 2 +- 11 files changed, 147 insertions(+), 125 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index bda63b5d..69b6f4e5 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -116,7 +116,7 @@ function ANSIEscapeParser(options) { break; } - if(self.row === 26) { + if(self.row === 26) { // :TODO: should be termHeight + 1 ? self.scrollBack++; self.row--; self.rowUpdated(); @@ -126,14 +126,10 @@ function ANSIEscapeParser(options) { self.emit('chunk', text); } - function mci(mciCode, args) { - console.log(mciCode, args); - } - function getProcessedMCI(mci) { if(self.mciReplaceChar.length > 0) { var eraseColor = ansi.sgr(self.lastFlags, self.lastFgColor, self.lastBgColor); - return eraseColor + new Array(mci.length + 1).join(self.mciReplaceChar); + return eraseColor + new Array(mci.length + 1).join(self.mciReplaceChar); } else { return mci; } @@ -167,8 +163,20 @@ function ANSIEscapeParser(options) { self.emit('mci', mciCode, id, args); + console.log(self.row + ', ' + self.column); + console.log(match[0]); - self.emit('chunk', getProcessedMCI(match[0])); + if(self.mciReplaceChar.length > 0) { + escape('m', [self.lastFlags, self.lastFgColor, self.lastBgColor]); + self.emit('chunk', ansi.sgr(self.lastFlags, self.lastFgColor, self.lastBgColor)); + literal(new Array(match[0].length + 1).join(self.mciReplaceChar)); + } else { + literal(match[0]); + } + + //literal(getProcessedMCI(match[0])); + + //self.emit('chunk', getProcessedMCI(match[0])); } } while(0 !== mciRe.lastIndex); diff --git a/core/ansi_term.js b/core/ansi_term.js index 1edc7e3c..1bfb61a8 100644 --- a/core/ansi_term.js +++ b/core/ansi_term.js @@ -22,7 +22,6 @@ exports.goHome = goHome; exports.disableVT100LineWrapping = disableVT100LineWrapping; exports.setSyncTermFont = setSyncTermFont; exports.fromPipeCode = fromPipeCode; -exports.forEachControlCode = forEachControlCode; // @@ -48,6 +47,7 @@ var CONTROL = { savePos : 's', restorePos : 'u', queryPos : '6n', + queryScreenSize : '255n', // See bansi.txt goto : 'H', // row Pr, column Pc -- same as f gotoAlt : 'f', // same as H @@ -155,61 +155,6 @@ Object.keys(CONTROL).forEach(function onControlName(name) { }; }); -// Create a reverse map of CONTROL values to their key/names - -/* -var CONTROL_REVERSE_MAP = {}; -Object.keys(CONTROL).forEach(function onControlName(name) { - var code = CONTROL[name]; - - CONTROL_REVERSE_MAP[code] = name; -}); -*/ - -var CONTROL_RESPONSE = { - 'R' : 'position', -}; - -// :TODO: move this to misc utils or such -- use here & parser -function getIntArgArray(array) { - var i = array.length; - while(i--) { - array[i] = parseInt(array[i], 10); - } - return array; -} - -// :TODO: rename this -function forEachControlCode(data, cb) { - //var re = /\u001b\[([0-9\;])*[R]/g; - - var len = data.length; - var pos = 0; - - while(pos < len) { - if(0x1b !== data[pos++] || 0x5b !== data[pos++]) { - continue; - } - - var params = ''; - - while(pos < len) { - var c = data[pos++]; - - if(((c > 64) && (c < 91)) || ((c > 96) && (c < 123))) { - c = String.fromCharCode(c); - var name = CONTROL_RESPONSE[c]; - if(name) { - params = getIntArgArray(params.split(';')); - cb(name, params); - } - } - - params += String.fromCharCode(c); - } - } -} - // Create various color methods such as white(), yellowBG(), reset(), ... Object.keys(SGR).forEach(function onSgrName(name) { var code = SGR[name]; diff --git a/core/art.js b/core/art.js index fe961118..d0af0aee 100644 --- a/core/art.js +++ b/core/art.js @@ -17,8 +17,6 @@ exports.getArt = getArt; exports.getArtFromPath = getArtFromPath; exports.display = display; exports.defaultEncodingFromExtension = defaultEncodingFromExtension; -exports.ArtDisplayer = ArtDisplayer; - var SAUCE_SIZE = 128; var SAUCE_ID = new Buffer([0x53, 0x41, 0x55, 0x43, 0x45]); // 'SAUCE' @@ -366,21 +364,10 @@ function defaultEofFromExtension(ext) { return SUPPORTED_ART_TYPES[ext.toLowerCase()].eof; } -function ArtDisplayer(client) { - if(!(this instanceof ArtDisplayer)) { - return new ArtDisplayer(client); - } - - events.EventEmitter.call(this); - - this.client = client; -} - -util.inherits(ArtDisplayer, events.EventEmitter); - // :TODO: change to display(art, options, cb) // cb(err, mci) +// :TODO: display({ art : art, client : client, ...}, cb) function display(art, options, cb) { if(!art || 0 === art.length) { cb(new Error('Missing or empty art')); @@ -413,11 +400,29 @@ function display(art, options, cb) { var mci = {}; var mciPosQueue = []; - var emitter = null; var parseComplete = false; var generatedId = 100; + var onCPR = function(pos) { + if(mciPosQueue.length > 0) { + var forMapItem = mciPosQueue.shift(); + mci[forMapItem].position = pos; + + if(parseComplete && 0 === mciPosQueue.length) { + completed(); + } + } + }; + + function completed() { + options.client.removeListener('cursor position report', onCPR); + parser.removeAllListeners(); // :TODO: Necessary??? + cb(null, mci); + } + + options.client.on('cursor position report', onCPR); + parser.on('mci', function onMCI(mciCode, id, args) { id = id || generatedId++; var mapItem = mciCode + id; @@ -441,33 +446,19 @@ function display(art, options, cb) { mciPosQueue.push(mapItem); - // :TODO: Move this out of the loop - if(!emitter) { - emitter = options.client.on('onPosition', function onPosition(pos) { - if(mciPosQueue.length > 0) { - var forMapItem = mciPosQueue.shift(); - mci[forMapItem].position = pos; - - if(parseComplete && 0 === mciPosQueue.length) { - cb(null, mci); - } - } - }); - } - - options.client.term.write(ansi.queryPos()); + options.client.term.write(ansi.queryPos(), false); // :TODO: don't convert LF's } }); parser.on('chunk', function onChunk(chunk) { - options.client.term.write(chunk); + options.client.term.write(chunk, false);// :TODO: don't convert LF's }); parser.on('complete', function onComplete() { parseComplete = true; if(0 === mciPosQueue.length) { - cb(null, mci); + completed(); } }); diff --git a/core/bbs.js b/core/bbs.js index 5692c485..757dae4b 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -119,7 +119,9 @@ function startListening() { client.on('ready', function onClientReady() { // Go to module -- use default error handler - modules.goto(conf.config.entryMod, client); + prepareClient(client, function onPrepared() { + modules.goto(conf.config.entryMod, client); + }); }); client.on('end', function onClientEnd() { @@ -156,4 +158,22 @@ function removeClient(client) { clientConnections.splice(i, 1); logger.log.debug('Connection count is now %d', clientConnections.length); } +} + +function prepareClient(client, cb) { + if('*' === conf.config.preLoginTheme) { + var theme = require('./theme.js'); + theme.getRandomTheme(function onRandTheme(err, themeId) { + if(err) { + // :TODO: how to propertly set default/fallback? + client.user.properties.art_theme_name = ''; + } else { + client.user.properties.art_theme_name = themeId; + } + cb(); + }); + } else { + client.user.properties.art_theme_name = conf.config.preLoginTheme; + cb(); + } } \ No newline at end of file diff --git a/core/client.js b/core/client.js index 5bc95037..07378189 100644 --- a/core/client.js +++ b/core/client.js @@ -7,6 +7,7 @@ var assert = require('assert'); var miscUtil = require('./misc_util.js'); var ansi = require('./ansi_term.js'); var logger = require('./logger.js'); +var user = require('./user.js'); exports.Client = Client; @@ -78,28 +79,13 @@ function Client(input, output) { this.input = input; this.output = output; this.term = new term.ClientTerminal(this.output); - - self.on('data', function onData1(data) { - //console.log(data); - - onData(data); - //handleANSIControlResponse(data); - }); - - function handleANSIControlResponse(data) { - //console.log(data); - ansi.forEachControlCode(data, function onControlResponse(name, params) { - var eventName = 'on' + name[0].toUpperCase() + name.substr(1); - console.log(eventName + ': ' + params); - self.emit(eventName, params); - }); - } + this.user = new user.User(); // // Peek at |data| and emit for any specialized handling // such as ANSI control codes or user/keyboard input // - function onData(data) { + self.on('data', function onData(data) { var len = data.length; var c; var name; @@ -163,16 +149,14 @@ function Client(input, output) { case 'R' : args = getIntArgArray(match[1].split(';')); if(2 === args.length) { - // :TODO: rename to 'cpr' or 'cursor position report' - self.emit('onPosition', args); + self.emit('cursor position report', args); } break; } } } while(0 !== dsrResponseRe.lastIndex); - // :TODO: Look for various DSR responses such as cursor position } - } + }); } require('util').inherits(Client, stream); diff --git a/core/client_term.js b/core/client_term.js index df569366..962e40e0 100644 --- a/core/client_term.js +++ b/core/client_term.js @@ -92,10 +92,10 @@ ClientTerminal.prototype.isANSI = function() { return 'ansi' === this.termType; }; -ClientTerminal.prototype.write = function(s) { - if(this.convertLF && typeof s === 'string') { +ClientTerminal.prototype.write = function(s, convertLineFeeds) { + convertLineFeeds = typeof convertLineFeeds === 'undefined' ? this.convertLF : convertLineFeeds; + if(convertLineFeeds && typeof s === 'string') { s = s.replace(/\n/g, '\r\n'); } - this.output.write(iconv.encode(s, this.outputEncoding)); -}; +}; \ No newline at end of file diff --git a/core/config.js b/core/config.js index ad29126f..9d5bfe95 100644 --- a/core/config.js +++ b/core/config.js @@ -25,7 +25,10 @@ module.exports = { this.config = { bbsName : 'Another Fine ENiGMA½ BBS', + // :TODO: probably replace this with 'firstMenu' or somthing once that's available entryMod : 'connect', + + preLoginTheme : '*', paths : { mods : paths.join(__dirname, './../mods/'), diff --git a/core/theme.js b/core/theme.js index 1e25f085..26f24192 100644 --- a/core/theme.js +++ b/core/theme.js @@ -10,6 +10,7 @@ var async = require('async'); exports.getThemeInfo = getThemeInfo; exports.getThemeArt = getThemeArt; +exports.getRandomTheme = getRandomTheme; // getThemeInfo(themeName) @@ -29,7 +30,7 @@ function getThemeInfo(themeID, cb) { } else { try { var info = JSON.parse(data); - return info; + cb(null, info); } catch(e) { cb(err); } @@ -37,6 +38,70 @@ function getThemeInfo(themeID, cb) { }); } +var availableThemes; + +function loadAvailableThemes(cb) { + // lazy init + async.waterfall( + [ + function getDir(callback) { + fs.readdir(Config.paths.art, function onReadDir(err, files) { + callback(err, files); + }); + }, + function filterFiles(files, callback) { + var filtered = files.filter(function onFilter(file) { + return fs.statSync(paths.join(Config.paths.art, file)).isDirectory(); + }); + callback(null, filtered); + }, + function populateAvailable(filtered, callback) { + filtered.forEach(function onTheme(themeId) { + getThemeInfo(themeId, function onThemeInfo(err, info) { + if(!err) { + if(!availableThemes) { + availableThemes = {}; + } + availableThemes[themeId] = info; + } + callback(null); + }); + }); + } + ], + function onComplete(err) { + if(err) { + cb(err); + return; + } + + if(!availableThemes) { + cb(new Error('No themes found')); + return; + } + + cb(null); + } + ); +} + +function getRandomTheme(cb) { + var themeIds; + if(availableThemes) { + themeIds = Object.keys(availableThemes); + cb(null, themeIds[Math.floor(Math.random() * themeIds.length)]); + } else { + loadAvailableThemes(function onThemes(err) { + if(err) { + cb(err); + } else { + themeIds = Object.keys(availableThemes); + cb(null, themeIds[Math.floor(Math.random() * themeIds.length)]); + } + }); + } +} + function getThemeArt(name, themeID, options, cb) { // allow options to be optional if(typeof cb === 'undefined') { diff --git a/core/user.js b/core/user.js index 5cdb131e..a9160c1e 100644 --- a/core/user.js +++ b/core/user.js @@ -17,6 +17,7 @@ function User() { this.id = 0; this.userName = ''; + this.properties = {}; this.isValid = function() { if(self.id <= 0 || self.userName.length < 2) { diff --git a/core/view.js b/core/view.js index 8e478929..5a78b8da 100644 --- a/core/view.js +++ b/core/view.js @@ -94,8 +94,13 @@ View.prototype.setPosition = function(pos) { assert(!(isNaN(this.position.x))); assert(!(isNaN(this.position.y))); - assert(this.position.x > 0 && this.position.x < this.client.term.termHeight); - assert(this.position.y > 0 && this.position.y < this.client.term.termWidth); + assert( + this.position.x > 0 && this.position.x <= this.client.term.termHeight, + 'X position ' + this.position.x + ' out of terminal range ' + this.client.term.termHeight); + + assert( + this.position.y > 0 && this.position.y <= this.client.term.termWidth, + 'Y position ' + this.position.y + ' out of terminal range ' + this.client.term.termWidth); }; View.prototype.getColor = function() { diff --git a/mods/matrix.js b/mods/matrix.js index 6f5e4491..20d46b69 100644 --- a/mods/matrix.js +++ b/mods/matrix.js @@ -29,7 +29,7 @@ function entryPoint(client) { // :TODO: types, random, and others? could come from conf.mods.matrix or such //art.getArt('SO-CC1.ANS'/* 'MATRIX'*/, { types: ['.ans'], random: true}, function onArt(err, theArt) { - theme.getThemeArt('MATRIX_1', 'NU-MAYAN', function onArt(err, theArt) { + theme.getThemeArt('MATRIX_1', client.user.properties.art_theme_name, function onArt(err, theArt) { //art.getArt('MATRIX_1.ANS', {}, function onArt(err, theArt) { if(!err) {