repl-promise
Advanced tools
Comparing version 0.0.4 to 0.0.5
421
lib/index.js
@@ -5,9 +5,12 @@ // repl-promise/lib/index.js | ||
var _ = require('lodash'); | ||
var chalk = require('chalk'); | ||
var Q = require('q'); | ||
var chalk = require('chalk'); | ||
var util = require('util'); | ||
var vm = require('vm'); | ||
var _ = require('lodash'); | ||
var dlog = require('debug')('repl-promise'); | ||
var LineReader = require('./LineReader.js'); | ||
var plog = require('debug')('repl-promise'); | ||
// If the error is that we've unexpectedly ended the input, | ||
@@ -17,4 +20,4 @@ // then let the user try to recover by adding more input. | ||
return e && | ||
e.name === 'SyntaxError' && | ||
/^(Unexpected end of input|Unexpected token :)/.test(e.message); | ||
e.name === 'SyntaxError' && | ||
/^(Unexpected end of input|Unexpected token :)/.test(e.message); | ||
} | ||
@@ -27,75 +30,10 @@ | ||
function LineReader(inputStream, endOfInput) { | ||
if (!(this instanceof LineReader)) { | ||
return new LineReader(inputStream); | ||
} | ||
function ReplServer() { | ||
if (!(this instanceof ReplServer)) { | ||
return new ReplServer(); | ||
} | ||
var self = this; | ||
var self = this; | ||
self.inputStream = inputStream; | ||
self.remaining = ''; | ||
self.lines = []; | ||
self.pendingDeferred = null; | ||
self.done = false; | ||
self.isDone = function() { | ||
return self.done && self.lines.length===0; | ||
}; | ||
self.fullfill = function() { | ||
var line = self.lines.shift(); | ||
if (line) | ||
line = line + '\n'; | ||
self.pendingDeferred.resolve(line); | ||
var result = self.pendingDeferred.promise; | ||
self.pendingDeferred = null; | ||
return result; | ||
}; | ||
self.readLine = function() { | ||
if (self.pendingDeferred !== null) { | ||
throw new Error('LineReader.readLine called before pendingDeferred fulfilled'); | ||
} | ||
self.pendingDeferred = Q.defer(); | ||
if (self.isDone()) { | ||
endOfInput.resolve(null); | ||
return self.fullfill(); | ||
} | ||
if (self.lines.length > 0) { | ||
return self.fullfill(); | ||
} | ||
return self.pendingDeferred.promise; | ||
}; | ||
process.on('SIGINT', function() { | ||
self.remaining = ''; | ||
self.lines = ['']; | ||
if (self.pendingDeferred !== null) { | ||
self.fullfill(); | ||
} | ||
}); | ||
inputStream.on('data', function(chunk) { | ||
self.remaining = self.remaining + chunk.toString(); | ||
var newLines = self.remaining.split('\n'); | ||
self.lines = self.lines.concat(_.initial(newLines)); | ||
self.remaining = _.last(newLines); | ||
if (self.pendingDeferred!==null && self.lines.length>0) { | ||
self.fullfill(); | ||
} | ||
}); | ||
inputStream.on('end', function() { | ||
self.done = true; | ||
}); | ||
} | ||
function identityTransformResult(result) { | ||
return new Q(result); | ||
} | ||
function start(options, _initialContext) { | ||
self.init = function(options, _initialContext) { | ||
options = options || {}; | ||
@@ -105,11 +43,17 @@ options.input = options.input || process.stdin; | ||
options.prompt = options.prompt || '> '; | ||
options.transformResult = options.transformResult || identityTransformResult; | ||
options.writer = options.writer || _writer; | ||
var echoInputToOutput = !options.output.isTTY || !options.input.isTTY; | ||
function _writer(obj) { | ||
return util.inspect(obj, {depth: null, colors: options.output.isTTY }); | ||
} | ||
self.echoInputToOutput = !options.output.isTTY || !options.input.isTTY; | ||
self.options = options; | ||
var initialContext = { | ||
require: require, | ||
console: console, | ||
process: process, | ||
Q: Q, | ||
require: require, | ||
console: console, | ||
process: process, | ||
Q: Q, | ||
}; | ||
@@ -121,190 +65,187 @@ | ||
var sandbox = vm.createContext(initialContext); | ||
var bufferedInput = ''; | ||
var nullScript = vm.createScript('null;', {filename: 'null.vm', displayErrors: false}); | ||
self.bufferedInput = ''; | ||
self.context = vm.createContext(initialContext); | ||
self.endOfSessionPromise = self.newSession(); | ||
function promiseOutput(line) { | ||
if (line === '') | ||
dlog('promiseOutput: Empty line'); | ||
return self; | ||
}; | ||
var deferred = Q.defer(); | ||
self.promiseOutput = function(line) { | ||
if (line === '') | ||
plog('promiseOutput: Empty line'); | ||
// This may seem like a case where we could use Q.nbind or deferred.makeNodeResolver(), | ||
// but output.write uses a callback that only takes an err argument, and we need | ||
// to call deferred.resolve(line), i.e. with line as an argument. | ||
options.output.write(line, "utf8", function(err) { | ||
if (err) { | ||
deferred.reject(err); | ||
} | ||
else { | ||
deferred.resolve(line); | ||
} | ||
}); | ||
var deferred = Q.defer(); | ||
return deferred.promise; | ||
} | ||
// This may seem like a case where we could use Q.nbind or deferred.makeNodeResolver(), | ||
// but output.write uses a callback that only takes an err argument, and we need | ||
// to call deferred.resolve(line), i.e. with line as an argument. | ||
self.options.output.write(line, "utf8", function(err) { | ||
if (err) { | ||
deferred.reject(err); | ||
} | ||
else { | ||
deferred.resolve(line); | ||
} | ||
}); | ||
function promptForInput() { | ||
var prompt = options.prompt; | ||
if (bufferedInput !== '') | ||
prompt = '... '; | ||
if (options.output.isTTY) | ||
prompt = chalk.bold.blue(prompt); | ||
return promiseOutput(prompt); | ||
} | ||
return deferred.promise; | ||
}; | ||
function compileAndExecuteScript(text) { | ||
var executeScriptDeferred = Q.defer(); | ||
self.promptForInput = function() { | ||
var prompt = self.options.prompt; | ||
if (self.bufferedInput !== '') | ||
prompt = '... '; | ||
if (self.options.output.isTTY) | ||
prompt = chalk.bold.blue(prompt); | ||
return self.promiseOutput(prompt); | ||
}; | ||
var isBufferingInput = bufferedInput !== ''; | ||
if (bufferedInput !== '') { | ||
text = bufferedInput + text; | ||
bufferedInput = ''; | ||
dlog('buffered input:', bufferedInput.length); | ||
} | ||
self.compileAndExecuteScript = function(text) { | ||
if (text===null && self.lineReader.isDone()) | ||
return new Q(null); | ||
// var (symbol) = (expression) | ||
var scopeVar = /^\s*var\s*([_\w\$]+)\s*=\s*(.*)$/m; | ||
var matches = scopeVar.exec(text); | ||
var isScopedVar = !isBufferingInput && matches!==null && matches.length===3; | ||
dlog('Is scoped var:', isScopedVar); | ||
var executeScriptDeferred = Q.defer(); | ||
var symbol; | ||
if (isScopedVar) { | ||
symbol = matches[1]; | ||
text = matches[2]; // change text that will be compiled & executed! | ||
dlog('var assignment', symbol, text); | ||
} | ||
var isBufferingInput = self.bufferedInput !== ''; | ||
if (self.bufferedInput !== '') { | ||
text = self.bufferedInput + text; | ||
self.bufferedInput = ''; | ||
plog('buffered input:', self.bufferedInput.length); | ||
} | ||
var script; | ||
try { | ||
script = vm.createScript(text, {filename: 'repl.vm', displayErrors: false}); | ||
dlog('text successfully compiled'); | ||
// (var) (symbol) = (expression) | ||
var assignmentExpr = /^(\s*|var\s+)([_\w\$]+)\s*=\s*(.*)$/m; | ||
var matches = assignmentExpr.exec(text); | ||
var isScopedVar = matches!==null; | ||
// var isScopedVar = !isBufferingInput && matches!==null && matches.length===3; | ||
plog('Is scoped var:', isScopedVar); | ||
var result; | ||
try { | ||
result = script.runInNewContext(sandbox); | ||
dlog('text successfully executed'); | ||
if (!isScopedVar) { | ||
dlog('simple expression result resolved'); | ||
executeScriptDeferred.resolve(result); | ||
} | ||
else { | ||
if (!Q.isPromise(result)) { | ||
dlog('simple expression assignment resolved'); | ||
sandbox[symbol] = result; | ||
executeScriptDeferred.resolve(undefined); | ||
} | ||
else { | ||
dlog('promise expression assignment deferred'); | ||
result.then(function (resolvedResult) { | ||
dlog('promise expression assignment resolved'); | ||
sandbox[symbol] = resolvedResult; | ||
executeScriptDeferred.resolve(undefined); | ||
}); | ||
} | ||
} | ||
} | ||
catch (err) { | ||
dlog('script execution failed'); | ||
console.error(chalk.bold.red(err)); | ||
executeScriptDeferred.resolve(null); | ||
return executeScriptDeferred.promise; | ||
} | ||
} | ||
catch (e) { | ||
var err; | ||
if (isRecoverableError(e)) { | ||
dlog('recoverable error in compilation'); | ||
if (isScopedVar) { | ||
text = util.format('var %s = %s', symbol, text); | ||
} | ||
bufferedInput = text; | ||
executeScriptDeferred.resolve(undefined); | ||
} | ||
else { | ||
dlog('nonrecoverable error in compilation'); | ||
err = e; | ||
executeScriptDeferred.reject(err); | ||
} | ||
} | ||
return executeScriptDeferred.promise; | ||
var symbol; | ||
if (isScopedVar) { | ||
symbol = matches[2]; | ||
text = matches[3]; // change text that will be compiled & executed! | ||
plog('var assignment', symbol, text); | ||
} | ||
function printResult(result) { | ||
if (result === null || result === undefined) { | ||
return new Q(result); | ||
var script; | ||
try { | ||
script = vm.createScript(text, {filename: 'repl.vm', displayErrors: false}); | ||
plog('text successfully compiled'); | ||
var result; | ||
try { | ||
result = script.runInNewContext(self.context); | ||
plog('text successfully executed'); | ||
if (!isScopedVar) { | ||
plog('simple expression result resolved'); | ||
executeScriptDeferred.resolve(result); | ||
} | ||
else { | ||
var resultAsString = util.inspect(result, {depth: null, colors: options.output.isTTY }); | ||
return promiseOutput(resultAsString + '\n'); | ||
if (!Q.isPromise(result)) { | ||
plog('simple expression assignment resolved'); | ||
self.context[symbol] = result; | ||
if (matches[1].trim() === 'var') | ||
result = undefined; | ||
executeScriptDeferred.resolve(result); | ||
} | ||
else { | ||
plog('promise expression assignment deferred'); | ||
result.then(function (resolvedResult) { | ||
plog('promise expression assignment resolved'); | ||
self.context[symbol] = resolvedResult; | ||
if (matches[1].trim() === 'var') | ||
resolvedResult = undefined; | ||
executeScriptDeferred.resolve(resolvedResult); | ||
}); | ||
} | ||
} | ||
} | ||
catch (err) { | ||
plog('script execution failed'); | ||
console.error(chalk.bold.red(err)); | ||
executeScriptDeferred.resolve(null); | ||
return executeScriptDeferred.promise; | ||
} | ||
} | ||
function onEchoInput(line) { | ||
if (line === '') | ||
line = '\n'; | ||
if (!echoInputToOutput) | ||
return new Q(line); | ||
return promiseOutput(line); | ||
catch (e) { | ||
var err; | ||
if (isRecoverableError(e)) { | ||
plog('recoverable error in compilation'); | ||
if (isScopedVar) { | ||
text = util.format('var %s = %s', symbol, text); | ||
} | ||
self.bufferedInput = text; | ||
executeScriptDeferred.resolve(undefined); | ||
} | ||
else { | ||
plog('nonrecoverable error in compilation'); | ||
err = e; | ||
executeScriptDeferred.reject(err); | ||
} | ||
} | ||
function newSession() { | ||
var replSessionDeferred = Q.defer(); | ||
return executeScriptDeferred.promise; | ||
}; | ||
var lineReader = new LineReader(options.input, replSessionDeferred); | ||
self.printResult = function(result) { | ||
if (result===null && self.lineReader.isDone()) | ||
return new Q(null); | ||
function onScriptInput() { | ||
if (lineReader.isDone()) { | ||
if (options.output != process.stdout) { | ||
options.output.end(); | ||
} | ||
return; | ||
} | ||
if (result === null || result === undefined) { | ||
return new Q(result); | ||
} | ||
else { | ||
var resultAsString = self.options.writer(result); | ||
return self.promiseOutput(resultAsString + '\n'); | ||
} | ||
}; | ||
promptForInput() | ||
.then(lineReader.readLine) | ||
.then(onEchoInput) | ||
.then(compileAndExecuteScript) | ||
.then(options.transformResult) | ||
.then(printResult) | ||
.catch(function(err) { console.error(chalk.bold.red(err)); }) | ||
.done(onScriptInput); | ||
} | ||
self.onEchoInput = function(line) { | ||
if (line===null && self.lineReader.isDone()) | ||
return new Q(null); | ||
if (!_.isString(line)) | ||
throw new Error('LineReader.readLine return null!'); | ||
if (line === '') | ||
line = '\n'; | ||
if (!self.echoInputToOutput) | ||
return new Q(line); | ||
return self.promiseOutput(line); | ||
}; | ||
function sigintHandler() { | ||
options.output.write('\n'); | ||
if (bufferedInput === '') { | ||
process.exit(); | ||
} | ||
else { | ||
bufferedInput = ''; | ||
} | ||
} | ||
self.newSession = function() { | ||
var replSessionDeferred = Q.defer(); | ||
process.on('SIGINT', sigintHandler); | ||
var lineReader = self.lineReader = new LineReader(self.options.input); | ||
options.input.on('end', function() { | ||
process.removeListener('SIGINT', sigintHandler); | ||
replSessionDeferred.resolve("end-of-input"); | ||
}); | ||
options.input.on('error', function(err) { | ||
process.removeListener('SIGINT', sigintHandler); | ||
replSessionDeferred.reject(err); | ||
}); | ||
function onScriptInput() { | ||
if (lineReader.isDone()) { | ||
if (self.options.output != process.stdout) { | ||
plog('lineReader is done, closing output'); | ||
self.options.output.end(); | ||
} | ||
replSessionDeferred.resolve(null); | ||
return; | ||
} | ||
onScriptInput(); | ||
return replSessionDeferred.promise; | ||
self.promptForInput() | ||
.then(lineReader.readLine) | ||
.then(self.onEchoInput) | ||
.then(self.compileAndExecuteScript) | ||
.then(self.printResult) | ||
.catch(function(err) { console.error(chalk.bold.red(err)); }) | ||
.delay(1) | ||
.done(onScriptInput); | ||
} | ||
var replServer = { | ||
context: sandbox, | ||
endOfSessionPromise: newSession() | ||
}; | ||
onScriptInput(); | ||
return replServer; | ||
return replSessionDeferred.promise; | ||
}; | ||
} | ||
module.exports = { start: start }; | ||
module.exports = { | ||
start: function(options, _initialContext) { | ||
var replServer = new ReplServer(); | ||
return replServer.init(options, _initialContext); | ||
} | ||
}; |
{ | ||
"name": "repl-promise", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "An alternative REPL implementation using Promises.", | ||
@@ -5,0 +5,0 @@ "bin": { |
@@ -23,7 +23,6 @@ // # Transcript-test.js | ||
prompt: 'node > ', | ||
input: fs.createReadStream('test/data/'+name+'.txt'), | ||
input: 'test/data/'+name+'.txt', | ||
output: output, | ||
}; | ||
var initialContext = { timers: require('timers') }; | ||
@@ -30,0 +29,0 @@ var replServer = repl.start(options, initialContext); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18069
14
367
4
1