REPL can be run from multiple different streams.
e.g. from UNIX sockets with socat.
This commit is contained in:
parent
cc9d5ab546
commit
b7441040f8
191
lib/repl.js
191
lib/repl.js
@ -1,153 +1,168 @@
|
||||
// A repl library that you can include in your own code to get a runtime
|
||||
// interface to your program. Just require("/repl.js").
|
||||
// interface to your program.
|
||||
//
|
||||
// var repl = require("/repl.js");
|
||||
// repl.start("prompt> "); // start repl on stdin
|
||||
// net.createServer(function (socket) { // listen for unix socket connections and start repl on them
|
||||
// repl.start("node via Unix socket> ", socket);
|
||||
// }).listen("/tmp/node-repl-sock");
|
||||
// net.createServer(function (socket) { // listen for TCP socket connections and start repl on them
|
||||
// repl.start("node via TCP socket> ", socket);
|
||||
// }).listen(5001);
|
||||
|
||||
// repl.start("node > ").scope.foo = "stdin is fun"; // expose foo to repl scope
|
||||
|
||||
var sys = require('sys');
|
||||
|
||||
var buffered_cmd = '';
|
||||
var trimmer = /^\s*(.+)\s*$/m;
|
||||
var scopedVar = /^\s*var\s*([_\w\$]+)(.*)$/m;
|
||||
var scopeFunc = /^\s*function\s*([_\w\$]+)/;
|
||||
|
||||
exports.scope = {};
|
||||
exports.prompt = "node> ";
|
||||
// Can overridden with custom print functions, such as `probe` or `eyes.js`
|
||||
exports.writer = sys.p;
|
||||
exports.writer = sys.inspect;
|
||||
|
||||
var stdin;
|
||||
|
||||
exports.start = function (prompt) {
|
||||
if (prompt !== undefined) {
|
||||
exports.prompt = prompt;
|
||||
}
|
||||
|
||||
stdin = process.openStdin();
|
||||
stdin.setEncoding('utf8');
|
||||
stdin.addListener("data", readline);
|
||||
displayPrompt();
|
||||
function REPLServer(prompt, stream) {
|
||||
var self = this;
|
||||
|
||||
self.scope = {};
|
||||
self.buffered_cmd = '';
|
||||
self.prompt = prompt || "node> ";
|
||||
self.stream = stream || process.openStdin();
|
||||
self.stream.setEncoding('utf8');
|
||||
self.stream.addListener("data", function (chunk) {
|
||||
self.readline.call(self, chunk);
|
||||
});
|
||||
self.displayPrompt();
|
||||
}
|
||||
exports.REPLServer = REPLServer;
|
||||
|
||||
/**
|
||||
* The main REPL function. This is called everytime the user enters
|
||||
* data on the command line.
|
||||
*/
|
||||
function readline (cmd) {
|
||||
cmd = trimWhitespace(cmd);
|
||||
// prompt is a string to print on each line for the prompt,
|
||||
// source is a stream to use for I/O, defaulting to stdin/stdout.
|
||||
exports.start = function (prompt, source) {
|
||||
return new REPLServer(prompt, source);
|
||||
};
|
||||
|
||||
REPLServer.prototype.displayPrompt = function () {
|
||||
var self = this;
|
||||
self.stream.write(self.buffered_cmd.length ? '... ' : self.prompt);
|
||||
};
|
||||
|
||||
// read a line from the stream, then eval it
|
||||
REPLServer.prototype.readline = function (cmd) {
|
||||
var self = this;
|
||||
|
||||
cmd = self.trimWhitespace(cmd);
|
||||
|
||||
// Check to see if a REPL keyword was used. If it returns true,
|
||||
// display next prompt and return.
|
||||
if (parseREPLKeyword(cmd) === true) {
|
||||
if (self.parseREPLKeyword(cmd) === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// The catchall for errors
|
||||
try {
|
||||
buffered_cmd += "\n" + cmd;
|
||||
self.buffered_cmd += cmd;
|
||||
// This try is for determining if the command is complete, or should
|
||||
// continue onto the next line.
|
||||
try {
|
||||
buffered_cmd = convertToScope(buffered_cmd);
|
||||
|
||||
// Scope the readline with exports.scope to provide "local" vars
|
||||
with (exports.scope) {
|
||||
var ret = eval(buffered_cmd);
|
||||
self.buffered_cmd = self.convertToScope(self.buffered_cmd);
|
||||
|
||||
// Scope the readline with self.scope to provide "local" vars and make Douglas Crockford cry
|
||||
with (self.scope) {
|
||||
var ret = eval(self.buffered_cmd);
|
||||
if (ret !== undefined) {
|
||||
exports.scope['_'] = ret;
|
||||
exports.writer(ret);
|
||||
self.scope['_'] = ret;
|
||||
self.stream.write(exports.writer(ret) + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
buffered_cmd = '';
|
||||
|
||||
self.buffered_cmd = '';
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError)) throw e;
|
||||
}
|
||||
} catch (e) {
|
||||
// On error: Print the error and clear the buffer
|
||||
if (e.stack) {
|
||||
sys.puts(e.stack);
|
||||
self.stream.write(e.stack + "\n");
|
||||
} else {
|
||||
sys.puts(e.toString());
|
||||
self.stream.write(e.toString() + "\n");
|
||||
}
|
||||
buffered_cmd = '';
|
||||
self.buffered_cmd = '';
|
||||
}
|
||||
|
||||
displayPrompt();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to display the prompt.
|
||||
*/
|
||||
function displayPrompt () {
|
||||
sys.print(buffered_cmd.length ? '... ' : exports.prompt);
|
||||
}
|
||||
|
||||
self.displayPrompt();
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to parse and execute the Node REPL commands.
|
||||
*
|
||||
*
|
||||
* @param {cmd} cmd The command entered to check
|
||||
* @returns {Boolean} If true it means don't continue parsing the command
|
||||
* @returns {Boolean} If true it means don't continue parsing the command
|
||||
*/
|
||||
function parseREPLKeyword (cmd) {
|
||||
|
||||
REPLServer.prototype.parseREPLKeyword = function (cmd) {
|
||||
var self = this;
|
||||
|
||||
switch (cmd) {
|
||||
case ".break":
|
||||
buffered_cmd = '';
|
||||
displayPrompt();
|
||||
self.buffered_cmd = '';
|
||||
self.displayPrompt();
|
||||
return true;
|
||||
case ".clear":
|
||||
sys.puts("Clearing Scope...");
|
||||
buffered_cmd = '';
|
||||
exports.scope = {};
|
||||
displayPrompt();
|
||||
self.stream.write("Clearing Scope...\n");
|
||||
self.buffered_cmd = '';
|
||||
self.scope = {};
|
||||
self.displayPrompt();
|
||||
return true;
|
||||
case ".exit":
|
||||
stdin.close();
|
||||
self.stream.close();
|
||||
return true;
|
||||
case ".help":
|
||||
sys.puts(".break\tSometimes you get stuck in a place you can't get out... This will get you out.");
|
||||
sys.puts(".clear\tBreak, and also clear the local scope.");
|
||||
sys.puts(".exit\tExit the prompt");
|
||||
sys.puts(".help\tShow repl options");
|
||||
displayPrompt();
|
||||
self.stream.write(".break\tSometimes you get stuck in a place you can't get out... This will get you out.\n");
|
||||
self.stream.write(".clear\tBreak, and also clear the local scope.\n");
|
||||
self.stream.write(".exit\tExit the prompt\n");
|
||||
self.stream.write(".help\tShow repl options\n");
|
||||
self.displayPrompt();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Trims Whitespace from a line.
|
||||
*
|
||||
*
|
||||
* @param {String} cmd The string to trim the whitespace from
|
||||
* @returns {String} The trimmed string
|
||||
* @returns {String} The trimmed string
|
||||
*/
|
||||
function trimWhitespace (cmd) {
|
||||
var matches = trimmer.exec(cmd);
|
||||
if (matches && matches.length == 2) {
|
||||
REPLServer.prototype.trimWhitespace = function (cmd) {
|
||||
var trimmer = /^\s*(.+)\s*$/m,
|
||||
matches = trimmer.exec(cmd);
|
||||
|
||||
if (matches && matches.length === 2) {
|
||||
return matches[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts commands that use var and function <name>() to use the
|
||||
* local exports.scope when evaled. This provides a local scope
|
||||
* on the REPL.
|
||||
*
|
||||
*
|
||||
* @param {String} cmd The cmd to convert
|
||||
* @returns {String} The converted command
|
||||
*/
|
||||
function convertToScope (cmd) {
|
||||
var matches;
|
||||
|
||||
// Replaces: var foo = "bar"; with: exports.scope.foo = bar;
|
||||
matches = scopedVar.exec(cmd);
|
||||
if (matches && matches.length == 3) {
|
||||
return "exports.scope." + matches[1] + matches[2];
|
||||
REPLServer.prototype.convertToScope = function (cmd) {
|
||||
var self = this, matches,
|
||||
scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m,
|
||||
scopeFunc = /^\s*function\s*([_\w\$]+)/;
|
||||
|
||||
// Replaces: var foo = "bar"; with: self.scope.foo = bar;
|
||||
matches = scopeVar.exec(cmd);
|
||||
if (matches && matches.length === 3) {
|
||||
return "self.scope." + matches[1] + matches[2];
|
||||
}
|
||||
|
||||
|
||||
// Replaces: function foo() {}; with: foo = function foo() {};
|
||||
matches = scopeFunc.exec(buffered_cmd);
|
||||
if (matches && matches.length == 2) {
|
||||
return matches[1] + " = " + buffered_cmd;
|
||||
matches = scopeFunc.exec(self.buffered_cmd);
|
||||
if (matches && matches.length === 2) {
|
||||
return matches[1] + " = " + self.buffered_cmd;
|
||||
}
|
||||
|
||||
|
||||
return cmd;
|
||||
}
|
||||
};
|
||||
|
138
test/simple/test-repl.js
Normal file
138
test/simple/test-repl.js
Normal file
@ -0,0 +1,138 @@
|
||||
require("../common");
|
||||
var sys = require("sys"),
|
||||
net = require("net"),
|
||||
repl = require("repl"),
|
||||
message = "Read, Eval, Print Loop",
|
||||
unix_socket_path = "/tmp/node-repl-sock",
|
||||
prompt_unix = "node via Unix socket> ",
|
||||
prompt_tcp = "node via TCP socket> ",
|
||||
server_tcp, server_unix, client_tcp, client_unix, timer;
|
||||
|
||||
debug('repl test');
|
||||
|
||||
// function for REPL to run
|
||||
invoke_me = function (arg) {
|
||||
return "invoked " + arg;
|
||||
};
|
||||
|
||||
function send_expect(list) {
|
||||
if (list.length > 0) {
|
||||
var cur = list.shift();
|
||||
|
||||
cur.client.expect = cur.expect;
|
||||
cur.client.list = list;
|
||||
if (cur.send.length > 0) {
|
||||
cur.client.write(cur.send);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tcp_test() {
|
||||
server_tcp = net.createServer(function (socket) {
|
||||
assert.strictEqual(server_tcp, socket.server);
|
||||
assert.strictEqual(server_tcp.type, 'tcp4');
|
||||
|
||||
socket.addListener("end", function () {
|
||||
socket.end();
|
||||
});
|
||||
|
||||
repl.start(prompt_tcp, socket);
|
||||
});
|
||||
|
||||
server_tcp.addListener('listening', function () {
|
||||
client_tcp = net.createConnection(PORT);
|
||||
|
||||
client_tcp.addListener('connect', function () {
|
||||
assert.equal(true, client_tcp.readable);
|
||||
assert.equal(true, client_tcp.writable);
|
||||
|
||||
send_expect([
|
||||
{ client: client_tcp, send: "", expect: prompt_tcp },
|
||||
{ client: client_tcp, send: "invoke_me(333)", expect: ('\'' + "invoked 333" + '\'\n' + prompt_tcp) },
|
||||
{ client: client_tcp, send: "a += 1", expect: ("12346" + '\n' + prompt_tcp) }
|
||||
]);
|
||||
});
|
||||
|
||||
client_tcp.addListener('data', function (data) {
|
||||
var data_str = data.asciiSlice(0, data.length);
|
||||
sys.puts("TCP data: " + data_str + ", compare to " + client_tcp.expect);
|
||||
assert.strictEqual(client_tcp.expect, data_str);
|
||||
if (client_tcp.list && client_tcp.list.length > 0) {
|
||||
send_expect(client_tcp.list);
|
||||
}
|
||||
else {
|
||||
sys.puts("End of TCP test.");
|
||||
client_tcp.end();
|
||||
client_unix.end();
|
||||
clearTimeout(timer);
|
||||
}
|
||||
});
|
||||
|
||||
client_tcp.addListener("error", function (e) {
|
||||
throw e;
|
||||
});
|
||||
|
||||
client_tcp.addListener("close", function () {
|
||||
server_tcp.close();
|
||||
});
|
||||
});
|
||||
|
||||
server_tcp.listen(PORT);
|
||||
}
|
||||
|
||||
function unix_test() {
|
||||
server_unix = net.createServer(function (socket) {
|
||||
assert.strictEqual(server_unix, socket.server);
|
||||
assert.strictEqual(server_unix.type, 'unix');
|
||||
|
||||
socket.addListener("end", function () {
|
||||
socket.end();
|
||||
});
|
||||
|
||||
repl.start(prompt_unix, socket).scope.message = message;
|
||||
});
|
||||
|
||||
server_unix.addListener('listening', function () {
|
||||
client_unix = net.createConnection(unix_socket_path);
|
||||
|
||||
client_unix.addListener('connect', function () {
|
||||
assert.equal(true, client_unix.readable);
|
||||
assert.equal(true, client_unix.writable);
|
||||
|
||||
send_expect([
|
||||
{ client: client_unix, send: "", expect: prompt_unix },
|
||||
{ client: client_unix, send: "message", expect: ('\'' + message + '\'\n' + prompt_unix) },
|
||||
{ client: client_unix, send: "invoke_me(987)", expect: ('\'' + "invoked 987" + '\'\n' + prompt_unix) },
|
||||
{ client: client_unix, send: "a = 12345", expect: ("12345" + '\n' + prompt_unix) }
|
||||
]);
|
||||
});
|
||||
|
||||
client_unix.addListener('data', function (data) {
|
||||
var data_str = data.asciiSlice(0, data.length);
|
||||
sys.puts("Unix data: " + data_str + ", compare to " + client_unix.expect);
|
||||
assert.strictEqual(client_unix.expect, data_str);
|
||||
if (client_unix.list && client_unix.list.length > 0) {
|
||||
send_expect(client_unix.list);
|
||||
}
|
||||
else {
|
||||
sys.puts("End of Unix test, running TCP test.");
|
||||
tcp_test();
|
||||
}
|
||||
});
|
||||
|
||||
client_unix.addListener("error", function (e) {
|
||||
throw e;
|
||||
});
|
||||
|
||||
client_unix.addListener("close", function () {
|
||||
server_unix.close();
|
||||
});
|
||||
});
|
||||
|
||||
server_unix.listen(unix_socket_path);
|
||||
}
|
||||
|
||||
unix_test();
|
||||
timer = setTimeout(function () {
|
||||
assert.fail("Timeout");
|
||||
}, 1000);
|
Loading…
x
Reference in New Issue
Block a user