mirror of https://github.com/lukechilds/node.git
Browse Source
this creates a new internal module responsible for providing the repl created via "iojs" or "iojs -i," and adds the following options to the readline and repl subsystems: * "repl mode" - determine whether a repl is strict mode, sloppy mode, or auto-detect mode. * historySize - determine the maximum number of lines a repl will store as history. The built-in repl gains persistent history support when the NODE_REPL_HISTORY_FILE environment variable is set. This functionality is not exposed to userland repl instances. PR-URL: https://github.com/iojs/io.js/pull/1513 Reviewed-By: Fedor Indutny <fedor@indutny.com>v2.0.2
10 changed files with 394 additions and 61 deletions
@ -0,0 +1,168 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
module.exports = {createRepl: createRepl}; |
||||
|
|
||||
|
const Interface = require('readline').Interface; |
||||
|
const REPL = require('repl'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary.
|
||||
|
// The debounce is to guard against code pasted into the REPL.
|
||||
|
const kDebounceHistoryMS = 15; |
||||
|
|
||||
|
try { |
||||
|
// hack for require.resolve("./relative") to work properly.
|
||||
|
module.filename = path.resolve('repl'); |
||||
|
} catch (e) { |
||||
|
// path.resolve('repl') fails when the current working directory has been
|
||||
|
// deleted. Fall back to the directory name of the (absolute) executable
|
||||
|
// path. It's not really correct but what are the alternatives?
|
||||
|
const dirname = path.dirname(process.execPath); |
||||
|
module.filename = path.resolve(dirname, 'repl'); |
||||
|
} |
||||
|
|
||||
|
// hack for repl require to work properly with node_modules folders
|
||||
|
module.paths = require('module')._nodeModulePaths(module.filename); |
||||
|
|
||||
|
function createRepl(env, cb) { |
||||
|
const opts = { |
||||
|
useGlobal: true, |
||||
|
ignoreUndefined: false |
||||
|
}; |
||||
|
|
||||
|
if (parseInt(env.NODE_NO_READLINE)) { |
||||
|
opts.terminal = false; |
||||
|
} |
||||
|
if (parseInt(env.NODE_DISABLE_COLORS)) { |
||||
|
opts.useColors = false; |
||||
|
} |
||||
|
|
||||
|
opts.replMode = { |
||||
|
'strict': REPL.REPL_MODE_STRICT, |
||||
|
'sloppy': REPL.REPL_MODE_SLOPPY, |
||||
|
'magic': REPL.REPL_MODE_MAGIC |
||||
|
}[String(env.NODE_REPL_MODE).toLowerCase().trim()]; |
||||
|
|
||||
|
if (opts.replMode === undefined) { |
||||
|
opts.replMode = REPL.REPL_MODE_MAGIC; |
||||
|
} |
||||
|
|
||||
|
const historySize = Number(env.NODE_REPL_HISTORY_SIZE); |
||||
|
if (!isNaN(historySize) && historySize > 0) { |
||||
|
opts.historySize = historySize; |
||||
|
} else { |
||||
|
// XXX(chrisdickinson): set here to avoid affecting existing applications
|
||||
|
// using repl instances.
|
||||
|
opts.historySize = 1000; |
||||
|
} |
||||
|
|
||||
|
const repl = REPL.start(opts); |
||||
|
if (env.NODE_REPL_HISTORY_PATH) { |
||||
|
return setupHistory(repl, env.NODE_REPL_HISTORY_PATH, cb); |
||||
|
} |
||||
|
repl._historyPrev = _replHistoryMessage; |
||||
|
cb(null, repl); |
||||
|
} |
||||
|
|
||||
|
function setupHistory(repl, historyPath, ready) { |
||||
|
const fs = require('fs'); |
||||
|
var timer = null; |
||||
|
var writing = false; |
||||
|
var pending = false; |
||||
|
repl.pause(); |
||||
|
fs.open(historyPath, 'a+', oninit); |
||||
|
|
||||
|
function oninit(err, hnd) { |
||||
|
if (err) { |
||||
|
return ready(err); |
||||
|
} |
||||
|
fs.close(hnd, onclose); |
||||
|
} |
||||
|
|
||||
|
function onclose(err) { |
||||
|
if (err) { |
||||
|
return ready(err); |
||||
|
} |
||||
|
fs.readFile(historyPath, 'utf8', onread); |
||||
|
} |
||||
|
|
||||
|
function onread(err, data) { |
||||
|
if (err) { |
||||
|
return ready(err); |
||||
|
} |
||||
|
|
||||
|
if (data) { |
||||
|
try { |
||||
|
repl.history = JSON.parse(data); |
||||
|
if (!Array.isArray(repl.history)) { |
||||
|
throw new Error('Expected array, got ' + typeof repl.history); |
||||
|
} |
||||
|
repl.history.slice(-repl.historySize); |
||||
|
} catch (err) { |
||||
|
return ready( |
||||
|
new Error(`Could not parse history data in ${historyPath}.`)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fs.open(historyPath, 'w', onhandle); |
||||
|
} |
||||
|
|
||||
|
function onhandle(err, hnd) { |
||||
|
if (err) { |
||||
|
return ready(err); |
||||
|
} |
||||
|
repl._historyHandle = hnd; |
||||
|
repl.on('line', online); |
||||
|
repl.resume(); |
||||
|
return ready(null, repl); |
||||
|
} |
||||
|
|
||||
|
// ------ history listeners ------
|
||||
|
function online() { |
||||
|
repl._flushing = true; |
||||
|
|
||||
|
if (timer) { |
||||
|
clearTimeout(timer); |
||||
|
} |
||||
|
|
||||
|
timer = setTimeout(flushHistory, kDebounceHistoryMS); |
||||
|
} |
||||
|
|
||||
|
function flushHistory() { |
||||
|
timer = null; |
||||
|
if (writing) { |
||||
|
pending = true; |
||||
|
return; |
||||
|
} |
||||
|
writing = true; |
||||
|
const historyData = JSON.stringify(repl.history, null, 2); |
||||
|
fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten); |
||||
|
} |
||||
|
|
||||
|
function onwritten(err, data) { |
||||
|
writing = false; |
||||
|
if (pending) { |
||||
|
pending = false; |
||||
|
online(); |
||||
|
} else { |
||||
|
repl._flushing = Boolean(timer); |
||||
|
if (!repl._flushing) { |
||||
|
repl.emit('flushHistory'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
function _replHistoryMessage() { |
||||
|
if (this.history.length === 0) { |
||||
|
this._writeToOutput( |
||||
|
'\nPersistent history support disabled. ' + |
||||
|
'Set the NODE_REPL_HISTORY_PATH environment variable to ' + |
||||
|
'a valid, user-writable path to enable.\n' |
||||
|
); |
||||
|
this._refreshLine(); |
||||
|
} |
||||
|
this._historyPrev = Interface.prototype._historyPrev; |
||||
|
return this._historyPrev(); |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
var common = require('../common'); |
||||
|
var assert = require('assert'); |
||||
|
var Stream = require('stream'); |
||||
|
var repl = require('repl'); |
||||
|
|
||||
|
common.globalCheck = false; |
||||
|
|
||||
|
var tests = [ |
||||
|
testSloppyMode, |
||||
|
testStrictMode, |
||||
|
testAutoMode |
||||
|
]; |
||||
|
|
||||
|
tests.forEach(function(test) { |
||||
|
test(); |
||||
|
}); |
||||
|
|
||||
|
function testSloppyMode() { |
||||
|
var cli = initRepl(repl.REPL_MODE_SLOPPY); |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
x = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.equal(cli.output.accumulator.join(''), '> 3\n> ') |
||||
|
cli.output.accumulator.length = 0; |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
let y = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.ok(/SyntaxError: Block-scoped/.test( |
||||
|
cli.output.accumulator.join(''))); |
||||
|
} |
||||
|
|
||||
|
function testStrictMode() { |
||||
|
var cli = initRepl(repl.REPL_MODE_STRICT); |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
x = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.ok(/ReferenceError: x is not defined/.test( |
||||
|
cli.output.accumulator.join(''))); |
||||
|
cli.output.accumulator.length = 0; |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
let y = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.equal(cli.output.accumulator.join(''), 'undefined\n> '); |
||||
|
} |
||||
|
|
||||
|
function testAutoMode() { |
||||
|
var cli = initRepl(repl.REPL_MODE_MAGIC); |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
x = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.equal(cli.output.accumulator.join(''), '> 3\n> ') |
||||
|
cli.output.accumulator.length = 0; |
||||
|
|
||||
|
cli.input.emit('data', ` |
||||
|
let y = 3 |
||||
|
`.trim() + '\n');
|
||||
|
assert.equal(cli.output.accumulator.join(''), 'undefined\n> '); |
||||
|
} |
||||
|
|
||||
|
function initRepl(mode) { |
||||
|
var input = new Stream(); |
||||
|
input.write = input.pause = input.resume = function(){}; |
||||
|
input.readable = true; |
||||
|
|
||||
|
var output = new Stream(); |
||||
|
output.write = output.pause = output.resume = function(buf) { |
||||
|
output.accumulator.push(buf); |
||||
|
}; |
||||
|
output.accumulator = []; |
||||
|
output.writable = true; |
||||
|
|
||||
|
return repl.start({ |
||||
|
input: input, |
||||
|
output: output, |
||||
|
useColors: false, |
||||
|
terminal: false, |
||||
|
replMode: mode |
||||
|
}); |
||||
|
} |
Loading…
Reference in new issue