showandtell
Version:
A Javascript library providing debugger-like command-line interactivity for program state inspection and modification
118 lines (111 loc) • 3.52 kB
JavaScript
const readline = require('readline')
/**
* A container for a set of commands that also provides special quit and help
* commands. It deals with user interaction and applying state changes between
* commands.
*
* @param {Array[Command]} commands are the commands that the session should support
* @public
*/
function Session (commands) {
this.commands = commands || []
}
/**
* Start an interactive session with the user, with an initial state.
*
* @param {Object} state is the initial state to begin modifying
* @returns a promise that resolves to the new state
* @public
*/
Session.prototype.start = function (state) {
return new Promise((resolve, reject) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
})
rl.setPrompt('>>> ')
rl.prompt()
rl
.on('line', (line) => {
line = line.trim()
if (line.slice(0, 4).toUpperCase() === 'QUIT') {
// Special termination case
rl.close()
process.stdin.destroy()
resolve(state)
} else if (line.slice(0, 4).toUpperCase() === 'HELP') {
// Get information about a specific command
this._handleHelp(line)
rl.prompt()
} else {
// Invoke a specific, registered command
this._handleCommand(state, line, function (err, nextState) {
if (err) {
console.error(err)
} else {
state = nextState
}
rl.prompt()
})
}
})
.on('close', () => resolve(state))
})
}
/**
* Handle the specal help command by either listing available commands or
* showing the help message for the command provided.
*
* @param {String} line is the line of user input, with "HELP ..."
* @returns nothing
* @private
*/
Session.prototype._handleHelp = function (line) {
if (line.length > 4) {
const cmdName = line.slice(4).trim().toUpperCase()
const cmds = this.commands.filter((c) => c.name() === cmdName)
if (cmds.length === 0) {
console.error('No such command', cmdName)
} else {
console.log(cmds[0].help())
}
} else {
console.log('Input format: CMD argument1 "string argument2" argument3 ...')
console.log('QUIT - \tExit the command loop')
console.log('HELP\noptional command - \tThe name of a command to get information about. Optional')
console.log(this.commands.map((c) => c.name()).join('\n'))
}
}
/**
* Handle a command other than QUIT or HELP that is expected to have been
* registered to the session. The command will be found by name (uppercase)
* and then the arguments (the rest of the input string) will be supplied.
*
* @param {Object} state is the state at the time the command is invoked
* @param {String} input is the user input including the command name
* @param {Function} next is a callback used to move on to accepting more input
* @returns nothing
* @private
*/
Session.prototype._handleCommand = function (state, input, next) {
const firstSpace = input.indexOf(' ')
let command = ''
let argString = ''
if (firstSpace < 1) {
command = input.toUpperCase()
} else {
command = input.slice(0, firstSpace).toUpperCase()
argString = input.slice(firstSpace).trim()
}
const cmds = this.commands.filter((c) => c.name() === command)
if (cmds.length === 0) {
console.error('No such command', command)
} else {
cmds[0].execute(state, argString, next)
}
}
module.exports = {
Session
}