mirror of https://github.com/lukechilds/node.git
Matt Ranney
15 years ago
committed by
Ryan Dahl
2 changed files with 241 additions and 88 deletions
@ -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; |
|||
|
|||
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; |
|||
|
|||
var stdin; |
|||
// 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); |
|||
}; |
|||
|
|||
exports.start = function (prompt) { |
|||
if (prompt !== undefined) { |
|||
exports.prompt = prompt; |
|||
} |
|||
REPLServer.prototype.displayPrompt = function () { |
|||
var self = this; |
|||
self.stream.write(self.buffered_cmd.length ? '... ' : self.prompt); |
|||
}; |
|||
|
|||
stdin = process.openStdin(); |
|||
stdin.setEncoding('utf8'); |
|||
stdin.addListener("data", readline); |
|||
displayPrompt(); |
|||
} |
|||
|
|||
/** |
|||
* The main REPL function. This is called everytime the user enters |
|||
* data on the command line. |
|||
*/ |
|||
function readline (cmd) { |
|||
cmd = trimWhitespace(cmd); |
|||
// 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; |
|||
} |
|||
}; |
|||
|
@ -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…
Reference in new issue