commit
a106fb451b
5
Cakefile
5
Cakefile
|
@ -172,6 +172,7 @@ runTests = (CoffeeScript) ->
|
|||
|
||||
# Convenience aliases.
|
||||
global.CoffeeScript = CoffeeScript
|
||||
global.Repl = require './lib/coffee-script/repl'
|
||||
|
||||
# Our test helper function for delimiting different test cases.
|
||||
global.test = (description, fn) ->
|
||||
|
@ -199,8 +200,8 @@ runTests = (CoffeeScript) ->
|
|||
return no for el, idx in a when not arrayEgal el, b[idx]
|
||||
yes
|
||||
|
||||
global.eq = (a, b, msg) -> ok egal(a, b), msg
|
||||
global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg
|
||||
global.eq = (a, b, msg) -> ok egal(a, b), msg ? "Expected #{a} to equal #{b}"
|
||||
global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg ? "Expected #{a} to deep equal #{b}"
|
||||
|
||||
# When all the tests have run, collect and print errors.
|
||||
# If a stacktrace is available, output the compiled function source.
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
loadRequires();
|
||||
}
|
||||
if (opts.interactive) {
|
||||
return require('./repl');
|
||||
return require('./repl').start();
|
||||
}
|
||||
if (opts.watch && !fs.watch) {
|
||||
return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + ".");
|
||||
|
@ -79,7 +79,7 @@
|
|||
return compileScript(null, sources[0]);
|
||||
}
|
||||
if (!sources.length) {
|
||||
return require('./repl');
|
||||
return require('./repl').start();
|
||||
}
|
||||
literals = opts.run ? sources.splice(1) : [];
|
||||
process.argv = process.argv.slice(0, 2).concat(literals);
|
||||
|
|
|
@ -1,276 +1,104 @@
|
|||
// Generated by CoffeeScript 1.5.0-pre
|
||||
(function() {
|
||||
var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout,
|
||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm;
|
||||
|
||||
stdin = process.openStdin();
|
||||
vm = require('vm');
|
||||
|
||||
stdout = process.stdout;
|
||||
nodeREPL = require('repl');
|
||||
|
||||
CoffeeScript = require('./coffee-script');
|
||||
|
||||
readline = require('readline');
|
||||
merge = require('./helpers').merge;
|
||||
|
||||
inspect = require('util').inspect;
|
||||
|
||||
Script = require('vm').Script;
|
||||
|
||||
Module = require('module');
|
||||
|
||||
REPL_PROMPT = 'coffee> ';
|
||||
|
||||
REPL_PROMPT_MULTILINE = '------> ';
|
||||
|
||||
REPL_PROMPT_CONTINUATION = '......> ';
|
||||
|
||||
enableColours = false;
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
enableColours = !process.env.NODE_DISABLE_COLORS;
|
||||
}
|
||||
|
||||
error = function(err) {
|
||||
return stdout.write((err.stack || err.toString()) + '\n');
|
||||
};
|
||||
|
||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/;
|
||||
|
||||
SIMPLEVAR = /(\w+)$/i;
|
||||
|
||||
autocomplete = function(text) {
|
||||
return completeAttribute(text) || completeVariable(text) || [[], text];
|
||||
};
|
||||
|
||||
completeAttribute = function(text) {
|
||||
var all, candidates, completions, key, match, obj, prefix, _i, _len, _ref;
|
||||
if (match = text.match(ACCESSOR)) {
|
||||
all = match[0], obj = match[1], prefix = match[2];
|
||||
replDefaults = {
|
||||
prompt: 'coffee> ',
|
||||
"eval": function(input, context, filename, cb) {
|
||||
var js;
|
||||
input = input.replace(/\uFF00/g, '\n');
|
||||
input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3');
|
||||
if (/^\s*$/.test(input)) {
|
||||
return cb(null);
|
||||
}
|
||||
try {
|
||||
obj = Script.runInThisContext(obj);
|
||||
} catch (e) {
|
||||
return;
|
||||
js = CoffeeScript.compile("_=(" + input + "\n)", {
|
||||
filename: filename,
|
||||
bare: true
|
||||
});
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
obj = Object(obj);
|
||||
candidates = Object.getOwnPropertyNames(obj);
|
||||
while (obj = Object.getPrototypeOf(obj)) {
|
||||
_ref = Object.getOwnPropertyNames(obj);
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
key = _ref[_i];
|
||||
if (__indexOf.call(candidates, key) < 0) {
|
||||
candidates.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
completions = getCompletions(prefix, candidates);
|
||||
return [completions, prefix];
|
||||
return cb(null, vm.runInContext(js, context, filename));
|
||||
}
|
||||
};
|
||||
|
||||
completeVariable = function(text) {
|
||||
var candidates, completions, free, key, keywords, r, vars, _i, _len, _ref;
|
||||
free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0;
|
||||
if (text === "") {
|
||||
free = "";
|
||||
}
|
||||
if (free != null) {
|
||||
vars = Script.runInThisContext('Object.getOwnPropertyNames(Object(this))');
|
||||
keywords = (function() {
|
||||
var _i, _len, _ref1, _results;
|
||||
_ref1 = CoffeeScript.RESERVED;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
||||
r = _ref1[_i];
|
||||
if (r.slice(0, 2) !== '__') {
|
||||
_results.push(r);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
candidates = vars;
|
||||
for (_i = 0, _len = keywords.length; _i < _len; _i++) {
|
||||
key = keywords[_i];
|
||||
if (__indexOf.call(candidates, key) < 0) {
|
||||
candidates.push(key);
|
||||
}
|
||||
}
|
||||
completions = getCompletions(free, candidates);
|
||||
return [completions, free];
|
||||
}
|
||||
};
|
||||
|
||||
getCompletions = function(prefix, candidates) {
|
||||
var el, _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = candidates.length; _i < _len; _i++) {
|
||||
el = candidates[_i];
|
||||
if (0 === el.indexOf(prefix)) {
|
||||
_results.push(el);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
|
||||
process.on('uncaughtException', error);
|
||||
|
||||
backlog = '';
|
||||
|
||||
run = function(buffer) {
|
||||
var code, returnValue, _;
|
||||
buffer = buffer.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3");
|
||||
buffer = buffer.replace(/[\r\n]+$/, "");
|
||||
if (multilineMode) {
|
||||
backlog += "" + buffer + "\n";
|
||||
repl.setPrompt(REPL_PROMPT_CONTINUATION);
|
||||
repl.prompt();
|
||||
return;
|
||||
}
|
||||
if (!buffer.toString().trim() && !backlog) {
|
||||
repl.prompt();
|
||||
return;
|
||||
}
|
||||
code = backlog += buffer;
|
||||
if (code[code.length - 1] === '\\') {
|
||||
backlog = "" + backlog.slice(0, -1) + "\n";
|
||||
repl.setPrompt(REPL_PROMPT_CONTINUATION);
|
||||
repl.prompt();
|
||||
return;
|
||||
}
|
||||
repl.setPrompt(REPL_PROMPT);
|
||||
backlog = '';
|
||||
try {
|
||||
_ = global._;
|
||||
returnValue = CoffeeScript["eval"]("_=(" + code + "\n)", {
|
||||
filename: 'repl',
|
||||
modulename: 'repl'
|
||||
});
|
||||
if (returnValue === void 0) {
|
||||
global._ = _;
|
||||
}
|
||||
repl.output.write("" + (inspect(returnValue, false, 2, enableColours)) + "\n");
|
||||
} catch (err) {
|
||||
error(err);
|
||||
}
|
||||
return repl.prompt();
|
||||
};
|
||||
|
||||
if (stdin.readable && stdin.isRaw) {
|
||||
pipedInput = '';
|
||||
repl = {
|
||||
prompt: function() {
|
||||
return stdout.write(this._prompt);
|
||||
},
|
||||
setPrompt: function(p) {
|
||||
return this._prompt = p;
|
||||
},
|
||||
input: stdin,
|
||||
output: stdout,
|
||||
on: function() {}
|
||||
addMultilineHandler = function(repl) {
|
||||
var inputStream, multiline, nodeLineListener, outputStream, rli;
|
||||
rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream;
|
||||
multiline = {
|
||||
enabled: false,
|
||||
initialPrompt: repl.prompt.replace(/^[^> ]*/, function(x) {
|
||||
return x.replace(/./g, '-');
|
||||
}),
|
||||
prompt: repl.prompt.replace(/^[^> ]*>?/, function(x) {
|
||||
return x.replace(/./g, '.');
|
||||
}),
|
||||
buffer: ''
|
||||
};
|
||||
stdin.on('data', function(chunk) {
|
||||
var line, lines, _i, _len, _ref;
|
||||
pipedInput += chunk;
|
||||
if (!/\n/.test(pipedInput)) {
|
||||
nodeLineListener = rli.listeners('line')[0];
|
||||
rli.removeListener('line', nodeLineListener);
|
||||
rli.on('line', function(cmd) {
|
||||
if (multiline.enabled) {
|
||||
multiline.buffer += "" + cmd + "\n";
|
||||
rli.setPrompt(multiline.prompt);
|
||||
rli.prompt(true);
|
||||
} else {
|
||||
nodeLineListener(cmd);
|
||||
}
|
||||
});
|
||||
return inputStream.on('keypress', function(char, key) {
|
||||
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
|
||||
return;
|
||||
}
|
||||
lines = pipedInput.split("\n");
|
||||
pipedInput = lines[lines.length - 1];
|
||||
_ref = lines.slice(0, -1);
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
line = _ref[_i];
|
||||
if (!(line)) {
|
||||
continue;
|
||||
if (multiline.enabled) {
|
||||
if (!multiline.buffer.match(/\n/)) {
|
||||
multiline.enabled = !multiline.enabled;
|
||||
rli.setPrompt(repl.prompt);
|
||||
rli.prompt(true);
|
||||
return;
|
||||
}
|
||||
stdout.write("" + line + "\n");
|
||||
run(line);
|
||||
if ((rli.line != null) && !rli.line.match(/^\s*$/)) {
|
||||
return;
|
||||
}
|
||||
multiline.enabled = !multiline.enabled;
|
||||
rli.line = '';
|
||||
rli.cursor = 0;
|
||||
rli.output.cursorTo(0);
|
||||
rli.output.clearLine(1);
|
||||
multiline.buffer = multiline.buffer.replace(/\n/g, '\uFF00');
|
||||
rli.emit('line', multiline.buffer);
|
||||
multiline.buffer = '';
|
||||
} else {
|
||||
multiline.enabled = !multiline.enabled;
|
||||
rli.setPrompt(multiline.initialPrompt);
|
||||
rli.prompt(true);
|
||||
}
|
||||
});
|
||||
stdin.on('end', function() {
|
||||
var line, _i, _len, _ref;
|
||||
_ref = pipedInput.trim().split("\n");
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
line = _ref[_i];
|
||||
if (!(line)) {
|
||||
continue;
|
||||
}
|
||||
stdout.write("" + line + "\n");
|
||||
run(line);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start: function(opts) {
|
||||
var repl;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
stdout.write('\n');
|
||||
return process.exit(0);
|
||||
});
|
||||
} else {
|
||||
if (readline.createInterface.length < 3) {
|
||||
repl = readline.createInterface(stdin, autocomplete);
|
||||
stdin.on('data', function(buffer) {
|
||||
return repl.write(buffer);
|
||||
opts = merge(replDefaults, opts);
|
||||
repl = nodeREPL.start(opts);
|
||||
repl.on('exit', function() {
|
||||
return repl.outputStream.write('\n');
|
||||
});
|
||||
} else {
|
||||
repl = readline.createInterface(stdin, stdout, autocomplete);
|
||||
addMultilineHandler(repl);
|
||||
return repl;
|
||||
}
|
||||
}
|
||||
|
||||
multilineMode = false;
|
||||
|
||||
repl.input.on('keypress', function(char, key) {
|
||||
var cursorPos, newPrompt;
|
||||
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
|
||||
return;
|
||||
}
|
||||
cursorPos = repl.cursor;
|
||||
repl.output.cursorTo(0);
|
||||
repl.output.clearLine(1);
|
||||
multilineMode = !multilineMode;
|
||||
if (!multilineMode && backlog) {
|
||||
repl._line();
|
||||
}
|
||||
backlog = '';
|
||||
repl.setPrompt((newPrompt = multilineMode ? REPL_PROMPT_MULTILINE : REPL_PROMPT));
|
||||
repl.prompt();
|
||||
return repl.output.cursorTo(newPrompt.length + (repl.cursor = cursorPos));
|
||||
});
|
||||
|
||||
repl.input.on('keypress', function(char, key) {
|
||||
if (!(multilineMode && repl.line)) {
|
||||
return;
|
||||
}
|
||||
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'd')) {
|
||||
return;
|
||||
}
|
||||
multilineMode = false;
|
||||
return repl._line();
|
||||
});
|
||||
|
||||
repl.on('attemptClose', function() {
|
||||
if (multilineMode) {
|
||||
multilineMode = false;
|
||||
repl.output.cursorTo(0);
|
||||
repl.output.clearLine(1);
|
||||
repl._onLine(repl.line);
|
||||
return;
|
||||
}
|
||||
if (backlog || repl.line) {
|
||||
backlog = '';
|
||||
repl.historyIndex = -1;
|
||||
repl.setPrompt(REPL_PROMPT);
|
||||
repl.output.write('\n(^C again to quit)');
|
||||
return repl._line((repl.line = ''));
|
||||
} else {
|
||||
return repl.close();
|
||||
}
|
||||
});
|
||||
|
||||
repl.on('close', function() {
|
||||
repl.output.write('\n');
|
||||
return repl.input.destroy();
|
||||
});
|
||||
|
||||
repl.on('line', run);
|
||||
|
||||
repl.setPrompt(REPL_PROMPT);
|
||||
|
||||
repl.prompt();
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -68,12 +68,12 @@ exports.run = ->
|
|||
return usage() if opts.help
|
||||
return version() if opts.version
|
||||
loadRequires() if opts.require
|
||||
return require './repl' if opts.interactive
|
||||
return require('./repl').start() if opts.interactive
|
||||
if opts.watch and !fs.watch
|
||||
return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
|
||||
return compileStdio() if opts.stdio
|
||||
return compileScript null, sources[0] if opts.eval
|
||||
return require './repl' unless sources.length
|
||||
return require('./repl').start() unless sources.length
|
||||
literals = if opts.run then sources.splice 1 else []
|
||||
process.argv = process.argv[0..1].concat literals
|
||||
process.argv[0] = 'coffee'
|
||||
|
|
258
src/repl.coffee
258
src/repl.coffee
|
@ -1,197 +1,77 @@
|
|||
# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript
|
||||
# and evaluates it. Good for simple tests, or poking around the **Node.js** API.
|
||||
# Using it looks like this:
|
||||
#
|
||||
# coffee> console.log "#{num} bottles of beer" for num in [99..1]
|
||||
|
||||
# Start by opening up `stdin` and `stdout`.
|
||||
stdin = process.openStdin()
|
||||
stdout = process.stdout
|
||||
|
||||
# Require the **coffee-script** module to get access to the compiler.
|
||||
vm = require 'vm'
|
||||
nodeREPL = require 'repl'
|
||||
CoffeeScript = require './coffee-script'
|
||||
readline = require 'readline'
|
||||
{inspect} = require 'util'
|
||||
{Script} = require 'vm'
|
||||
Module = require 'module'
|
||||
{merge} = require './helpers'
|
||||
|
||||
# REPL Setup
|
||||
replDefaults =
|
||||
prompt: 'coffee> ',
|
||||
eval: (input, context, filename, cb) ->
|
||||
# XXX: multiline hack
|
||||
input = input.replace /\uFF00/g, '\n'
|
||||
# strip single-line comments
|
||||
input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
|
||||
# empty command
|
||||
return cb null if /^\s*$/.test input
|
||||
# TODO: fix #1829: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
|
||||
try
|
||||
js = CoffeeScript.compile "_=(#{input}\n)", {filename, bare: yes}
|
||||
catch err
|
||||
cb err
|
||||
cb null, vm.runInContext(js, context, filename)
|
||||
|
||||
# Config
|
||||
REPL_PROMPT = 'coffee> '
|
||||
REPL_PROMPT_MULTILINE = '------> '
|
||||
REPL_PROMPT_CONTINUATION = '......> '
|
||||
enableColours = no
|
||||
unless process.platform is 'win32'
|
||||
enableColours = not process.env.NODE_DISABLE_COLORS
|
||||
addMultilineHandler = (repl) ->
|
||||
{rli, inputStream, outputStream} = repl
|
||||
|
||||
# Log an error.
|
||||
error = (err) ->
|
||||
stdout.write (err.stack or err.toString()) + '\n'
|
||||
multiline =
|
||||
enabled: off
|
||||
initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-')
|
||||
prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.')
|
||||
buffer: ''
|
||||
|
||||
## Autocompletion
|
||||
|
||||
# Regexes to match complete-able bits of text.
|
||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/
|
||||
SIMPLEVAR = /(\w+)$/i
|
||||
|
||||
# Returns a list of completions, and the completed text.
|
||||
autocomplete = (text) ->
|
||||
completeAttribute(text) or completeVariable(text) or [[], text]
|
||||
|
||||
# Attempt to autocomplete a chained dotted attribute: `one.two.three`.
|
||||
completeAttribute = (text) ->
|
||||
if match = text.match ACCESSOR
|
||||
[all, obj, prefix] = match
|
||||
try obj = Script.runInThisContext obj
|
||||
catch e
|
||||
return
|
||||
return unless obj?
|
||||
obj = Object obj
|
||||
candidates = Object.getOwnPropertyNames obj
|
||||
while obj = Object.getPrototypeOf obj
|
||||
for key in Object.getOwnPropertyNames obj when key not in candidates
|
||||
candidates.push key
|
||||
completions = getCompletions prefix, candidates
|
||||
[completions, prefix]
|
||||
|
||||
# Attempt to autocomplete an in-scope free variable: `one`.
|
||||
completeVariable = (text) ->
|
||||
free = text.match(SIMPLEVAR)?[1]
|
||||
free = "" if text is ""
|
||||
if free?
|
||||
vars = Script.runInThisContext 'Object.getOwnPropertyNames(Object(this))'
|
||||
keywords = (r for r in CoffeeScript.RESERVED when r[..1] isnt '__')
|
||||
candidates = vars
|
||||
for key in keywords when key not in candidates
|
||||
candidates.push key
|
||||
completions = getCompletions free, candidates
|
||||
[completions, free]
|
||||
|
||||
# Return elements of candidates for which `prefix` is a prefix.
|
||||
getCompletions = (prefix, candidates) ->
|
||||
el for el in candidates when 0 is el.indexOf prefix
|
||||
|
||||
# Make sure that uncaught exceptions don't kill the REPL.
|
||||
process.on 'uncaughtException', error
|
||||
|
||||
# The current backlog of multi-line code.
|
||||
backlog = ''
|
||||
|
||||
# The main REPL function. **run** is called every time a line of code is entered.
|
||||
# Attempt to evaluate the command. If there's an exception, print it out instead
|
||||
# of exiting.
|
||||
run = (buffer) ->
|
||||
# remove single-line comments
|
||||
buffer = buffer.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3"
|
||||
# remove trailing newlines
|
||||
buffer = buffer.replace /[\r\n]+$/, ""
|
||||
if multilineMode
|
||||
backlog += "#{buffer}\n"
|
||||
repl.setPrompt REPL_PROMPT_CONTINUATION
|
||||
repl.prompt()
|
||||
# Proxy node's line listener
|
||||
nodeLineListener = rli.listeners('line')[0]
|
||||
rli.removeListener 'line', nodeLineListener
|
||||
rli.on 'line', (cmd) ->
|
||||
if multiline.enabled
|
||||
multiline.buffer += "#{cmd}\n"
|
||||
rli.setPrompt multiline.prompt
|
||||
rli.prompt true
|
||||
else
|
||||
nodeLineListener cmd
|
||||
return
|
||||
if !buffer.toString().trim() and !backlog
|
||||
repl.prompt()
|
||||
|
||||
# Handle Ctrl-v
|
||||
inputStream.on 'keypress', (char, key) ->
|
||||
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
|
||||
if multiline.enabled
|
||||
# allow arbitrarily switching between modes any time before multiple lines are entered
|
||||
unless multiline.buffer.match /\n/
|
||||
multiline.enabled = not multiline.enabled
|
||||
rli.setPrompt repl.prompt
|
||||
rli.prompt true
|
||||
return
|
||||
# no-op unless the current line is empty
|
||||
return if rli.line? and not rli.line.match /^\s*$/
|
||||
# eval, print, loop
|
||||
multiline.enabled = not multiline.enabled
|
||||
rli.line = ''
|
||||
rli.cursor = 0
|
||||
rli.output.cursorTo 0
|
||||
rli.output.clearLine 1
|
||||
# XXX: multiline hack
|
||||
multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
|
||||
rli.emit 'line', multiline.buffer
|
||||
multiline.buffer = ''
|
||||
else
|
||||
multiline.enabled = not multiline.enabled
|
||||
rli.setPrompt multiline.initialPrompt
|
||||
rli.prompt true
|
||||
return
|
||||
code = backlog += buffer
|
||||
if code[code.length - 1] is '\\'
|
||||
backlog = "#{backlog[...-1]}\n"
|
||||
repl.setPrompt REPL_PROMPT_CONTINUATION
|
||||
repl.prompt()
|
||||
return
|
||||
repl.setPrompt REPL_PROMPT
|
||||
backlog = ''
|
||||
try
|
||||
_ = global._
|
||||
returnValue = CoffeeScript.eval "_=(#{code}\n)", {
|
||||
filename: 'repl'
|
||||
modulename: 'repl'
|
||||
}
|
||||
if returnValue is undefined
|
||||
global._ = _
|
||||
repl.output.write "#{inspect returnValue, no, 2, enableColours}\n"
|
||||
catch err
|
||||
error err
|
||||
repl.prompt()
|
||||
|
||||
if stdin.readable and stdin.isRaw
|
||||
# handle piped input
|
||||
pipedInput = ''
|
||||
repl =
|
||||
prompt: -> stdout.write @_prompt
|
||||
setPrompt: (p) -> @_prompt = p
|
||||
input: stdin
|
||||
output: stdout
|
||||
on: ->
|
||||
stdin.on 'data', (chunk) ->
|
||||
pipedInput += chunk
|
||||
return unless /\n/.test pipedInput
|
||||
lines = pipedInput.split "\n"
|
||||
pipedInput = lines[lines.length - 1]
|
||||
for line in lines[...-1] when line
|
||||
stdout.write "#{line}\n"
|
||||
run line
|
||||
return
|
||||
stdin.on 'end', ->
|
||||
for line in pipedInput.trim().split "\n" when line
|
||||
stdout.write "#{line}\n"
|
||||
run line
|
||||
stdout.write '\n'
|
||||
process.exit 0
|
||||
else
|
||||
# Create the REPL by listening to **stdin**.
|
||||
if readline.createInterface.length < 3
|
||||
repl = readline.createInterface stdin, autocomplete
|
||||
stdin.on 'data', (buffer) -> repl.write buffer
|
||||
else
|
||||
repl = readline.createInterface stdin, stdout, autocomplete
|
||||
|
||||
multilineMode = off
|
||||
|
||||
# Handle multi-line mode switch
|
||||
repl.input.on 'keypress', (char, key) ->
|
||||
# test for Ctrl-v
|
||||
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
|
||||
cursorPos = repl.cursor
|
||||
repl.output.cursorTo 0
|
||||
repl.output.clearLine 1
|
||||
multilineMode = not multilineMode
|
||||
repl._line() if not multilineMode and backlog
|
||||
backlog = ''
|
||||
repl.setPrompt (newPrompt = if multilineMode then REPL_PROMPT_MULTILINE else REPL_PROMPT)
|
||||
repl.prompt()
|
||||
repl.output.cursorTo newPrompt.length + (repl.cursor = cursorPos)
|
||||
|
||||
# Handle Ctrl-d press at end of last line in multiline mode
|
||||
repl.input.on 'keypress', (char, key) ->
|
||||
return unless multilineMode and repl.line
|
||||
# test for Ctrl-d
|
||||
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'd'
|
||||
multilineMode = off
|
||||
repl._line()
|
||||
|
||||
repl.on 'attemptClose', ->
|
||||
if multilineMode
|
||||
multilineMode = off
|
||||
repl.output.cursorTo 0
|
||||
repl.output.clearLine 1
|
||||
repl._onLine repl.line
|
||||
return
|
||||
if backlog or repl.line
|
||||
backlog = ''
|
||||
repl.historyIndex = -1
|
||||
repl.setPrompt REPL_PROMPT
|
||||
repl.output.write '\n(^C again to quit)'
|
||||
repl._line (repl.line = '')
|
||||
else
|
||||
repl.close()
|
||||
|
||||
repl.on 'close', ->
|
||||
repl.output.write '\n'
|
||||
repl.input.destroy()
|
||||
|
||||
repl.on 'line', run
|
||||
|
||||
repl.setPrompt REPL_PROMPT
|
||||
repl.prompt()
|
||||
module.exports =
|
||||
start: (opts = {}) ->
|
||||
opts = merge replDefaults, opts
|
||||
repl = nodeREPL.start opts
|
||||
repl.on 'exit', -> repl.outputStream.write '\n'
|
||||
addMultilineHandler repl
|
||||
repl
|
||||
|
|
|
@ -1,4 +1,80 @@
|
|||
# REPL
|
||||
# ----
|
||||
Stream = require 'stream'
|
||||
|
||||
# TODO: add tests
|
||||
class MockInputStream extends Stream
|
||||
constructor: ->
|
||||
@readable = true
|
||||
|
||||
resume: ->
|
||||
|
||||
emitLine: (val) ->
|
||||
@emit 'data', new Buffer("#{val}\n")
|
||||
|
||||
class MockOutputStream extends Stream
|
||||
constructor: ->
|
||||
@writable = true
|
||||
@written = []
|
||||
|
||||
write: (data) ->
|
||||
#console.log 'output write', arguments
|
||||
@written.push data
|
||||
|
||||
lastWrite: (fromEnd = -1) ->
|
||||
@written[@written.length - 1 + fromEnd].replace /\n$/, ''
|
||||
|
||||
|
||||
testRepl = (desc, fn) ->
|
||||
input = new MockInputStream
|
||||
output = new MockOutputStream
|
||||
Repl.start {input, output}
|
||||
test desc, -> fn input, output
|
||||
|
||||
ctrlV = { ctrl: true, name: 'v'}
|
||||
|
||||
|
||||
testRepl "starts with coffee prompt", (input, output) ->
|
||||
eq 'coffee> ', output.lastWrite(0)
|
||||
|
||||
testRepl "writes eval to output", (input, output) ->
|
||||
input.emitLine '1+1'
|
||||
eq '2', output.lastWrite()
|
||||
|
||||
testRepl "comments are ignored", (input, output) ->
|
||||
input.emitLine '1 + 1 #foo'
|
||||
eq '2', output.lastWrite()
|
||||
|
||||
testRepl "output in inspect mode", (input, output) ->
|
||||
input.emitLine '"1 + 1\\n"'
|
||||
eq "'1 + 1\\n'", output.lastWrite()
|
||||
|
||||
testRepl "variables are saved", (input, output) ->
|
||||
input.emitLine "foo = 'foo'"
|
||||
input.emitLine 'foobar = "#{foo}bar"'
|
||||
eq "'foobar'", output.lastWrite()
|
||||
|
||||
testRepl "empty command evaluates to undefined", (input, output) ->
|
||||
input.emitLine ''
|
||||
eq 'undefined', output.lastWrite()
|
||||
|
||||
testRepl "ctrl-v toggles multiline prompt", (input, output) ->
|
||||
input.emit 'keypress', null, ctrlV
|
||||
eq '------> ', output.lastWrite(0)
|
||||
input.emit 'keypress', null, ctrlV
|
||||
eq 'coffee> ', output.lastWrite(0)
|
||||
|
||||
testRepl "multiline continuation changes prompt", (input, output) ->
|
||||
input.emit 'keypress', null, ctrlV
|
||||
input.emitLine ''
|
||||
eq '....... ', output.lastWrite(0)
|
||||
|
||||
testRepl "evaluates multiline", (input, output) ->
|
||||
# Stubs. Could assert on their use.
|
||||
output.cursorTo = (pos) ->
|
||||
output.clearLine = ->
|
||||
|
||||
input.emit 'keypress', null, ctrlV
|
||||
input.emitLine 'do ->'
|
||||
input.emitLine ' 1 + 1'
|
||||
input.emit 'keypress', null, ctrlV
|
||||
eq '2', output.lastWrite()
|
||||
|
|
|
@ -106,7 +106,6 @@
|
|||
'option_parser'
|
||||
'ranges'
|
||||
'regexps'
|
||||
'repl'
|
||||
'scope'
|
||||
'slicing_and_splicing'
|
||||
'soaks'
|
||||
|
|
Loading…
Reference in New Issue