mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-14 01:07:55 -05:00
* Bump version to 2.0.0; bump dependencies versions * Make v2 docs the primary docs; jettison the v1 docs’ source: whenever the v1 docs need to be rebuild in the future, that can be done on the `1` branch and copied over; simplify folder tree * Updated v1 docs that reflect that v2 is out and have updated paths to reflect that the v2 docs are now the primary docs, and the v1 docs only live under /v1/ * Add Google Analytics; track navigation, editing code and running code * 2.0.0 changelog * Fix link to root docs * No more @next; installing local copy should be --save-dev * Analytics on the browser-based tests page should prove fascinating . . . * Update annotated source * Add note to changelog clarifying scope
258 lines
8.6 KiB
JavaScript
258 lines
8.6 KiB
JavaScript
// Generated by CoffeeScript 2.0.0
|
|
(function() {
|
|
var CoffeeScript, addHistory, addMultilineHandler, fs, getCommandId, merge, nodeREPL, path, replDefaults, runInContext, sawSIGINT, updateSyntaxError, vm;
|
|
|
|
fs = require('fs');
|
|
|
|
path = require('path');
|
|
|
|
vm = require('vm');
|
|
|
|
nodeREPL = require('repl');
|
|
|
|
CoffeeScript = require('./');
|
|
|
|
({merge, updateSyntaxError} = require('./helpers'));
|
|
|
|
sawSIGINT = false;
|
|
|
|
replDefaults = {
|
|
prompt: 'coffee> ',
|
|
historyFile: (function() {
|
|
var historyPath;
|
|
historyPath = process.env.XDG_CACHE_HOME || process.env.HOME;
|
|
if (historyPath) {
|
|
return path.join(historyPath, '.coffee_history');
|
|
}
|
|
})(),
|
|
historyMaxInputSize: 10240,
|
|
eval: function(input, context, filename, cb) {
|
|
var Assign, Block, Call, Code, Literal, Value, ast, err, isAsync, js, referencedVars, result, token, tokens;
|
|
// XXX: multiline hack.
|
|
input = input.replace(/\uFF00/g, '\n');
|
|
// Node's REPL sends the input ending with a newline and then wrapped in
|
|
// parens. Unwrap all that.
|
|
input = input.replace(/^\(([\s\S]*)\n\)$/m, '$1');
|
|
// Node's REPL v6.9.1+ sends the input wrapped in a try/catch statement.
|
|
// Unwrap that too.
|
|
input = input.replace(/^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1');
|
|
// Require AST nodes to do some AST manipulation.
|
|
({Block, Assign, Value, Literal, Call, Code} = require('./nodes'));
|
|
try {
|
|
// Tokenize the clean input.
|
|
tokens = CoffeeScript.tokens(input);
|
|
// Collect referenced variable names just like in `CoffeeScript.compile`.
|
|
referencedVars = (function() {
|
|
var i, len, results;
|
|
results = [];
|
|
for (i = 0, len = tokens.length; i < len; i++) {
|
|
token = tokens[i];
|
|
if (token[0] === 'IDENTIFIER') {
|
|
results.push(token[1]);
|
|
}
|
|
}
|
|
return results;
|
|
})();
|
|
// Generate the AST of the tokens.
|
|
ast = CoffeeScript.nodes(tokens);
|
|
// Add assignment to `__` variable to force the input to be an expression.
|
|
ast = new Block([new Assign(new Value(new Literal('__')), ast, '=')]);
|
|
// Wrap the expression in a closure to support top-level `await`
|
|
ast = new Code([], ast);
|
|
isAsync = ast.isAsync;
|
|
// Invoke the wrapping closure
|
|
ast = new Block([new Call(ast)]);
|
|
js = ast.compile({
|
|
bare: true,
|
|
locals: Object.keys(context),
|
|
referencedVars,
|
|
sharedScope: true
|
|
});
|
|
result = runInContext(js, context, filename);
|
|
// Await an async result, if necessary
|
|
if (isAsync) {
|
|
result.then(function(resolvedResult) {
|
|
if (!sawSIGINT) {
|
|
return cb(null, resolvedResult);
|
|
}
|
|
});
|
|
return sawSIGINT = false;
|
|
} else {
|
|
return cb(null, result);
|
|
}
|
|
} catch (error) {
|
|
err = error;
|
|
// AST's `compile` does not add source code information to syntax errors.
|
|
updateSyntaxError(err, input);
|
|
return cb(err);
|
|
}
|
|
}
|
|
};
|
|
|
|
runInContext = function(js, context, filename) {
|
|
if (context === global) {
|
|
return vm.runInThisContext(js, filename);
|
|
} else {
|
|
return vm.runInContext(js, context, filename);
|
|
}
|
|
};
|
|
|
|
addMultilineHandler = function(repl) {
|
|
var inputStream, multiline, nodeLineListener, origPrompt, outputStream, ref, rli;
|
|
({rli, inputStream, outputStream} = repl);
|
|
// Node 0.11.12 changed API, prompt is now _prompt.
|
|
origPrompt = (ref = repl._prompt) != null ? ref : repl.prompt;
|
|
multiline = {
|
|
enabled: false,
|
|
initialPrompt: origPrompt.replace(/^[^> ]*/, function(x) {
|
|
return x.replace(/./g, '-');
|
|
}),
|
|
prompt: origPrompt.replace(/^[^> ]*>?/, function(x) {
|
|
return x.replace(/./g, '.');
|
|
}),
|
|
buffer: ''
|
|
};
|
|
// Proxy node's line listener
|
|
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 {
|
|
rli.setPrompt(origPrompt);
|
|
nodeLineListener(cmd);
|
|
}
|
|
});
|
|
// Handle Ctrl-v
|
|
return inputStream.on('keypress', function(char, key) {
|
|
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
|
|
return;
|
|
}
|
|
if (multiline.enabled) {
|
|
// allow arbitrarily switching between modes any time before multiple lines are entered
|
|
if (!multiline.buffer.match(/\n/)) {
|
|
multiline.enabled = !multiline.enabled;
|
|
rli.setPrompt(origPrompt);
|
|
rli.prompt(true);
|
|
return;
|
|
}
|
|
// no-op unless the current line is empty
|
|
if ((rli.line != null) && !rli.line.match(/^\s*$/)) {
|
|
return;
|
|
}
|
|
// eval, print, loop
|
|
multiline.enabled = !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 = !multiline.enabled;
|
|
rli.setPrompt(multiline.initialPrompt);
|
|
rli.prompt(true);
|
|
}
|
|
});
|
|
};
|
|
|
|
// Store and load command history from a file
|
|
addHistory = function(repl, filename, maxSize) {
|
|
var buffer, fd, lastLine, readFd, size, stat;
|
|
lastLine = null;
|
|
try {
|
|
// Get file info and at most maxSize of command history
|
|
stat = fs.statSync(filename);
|
|
size = Math.min(maxSize, stat.size);
|
|
// Read last `size` bytes from the file
|
|
readFd = fs.openSync(filename, 'r');
|
|
buffer = Buffer.alloc(size);
|
|
fs.readSync(readFd, buffer, 0, size, stat.size - size);
|
|
fs.closeSync(readFd);
|
|
// Set the history on the interpreter
|
|
repl.rli.history = buffer.toString().split('\n').reverse();
|
|
if (stat.size > maxSize) {
|
|
// If the history file was truncated we should pop off a potential partial line
|
|
repl.rli.history.pop();
|
|
}
|
|
if (repl.rli.history[0] === '') {
|
|
// Shift off the final blank newline
|
|
repl.rli.history.shift();
|
|
}
|
|
repl.rli.historyIndex = -1;
|
|
lastLine = repl.rli.history[0];
|
|
} catch (error) {}
|
|
fd = fs.openSync(filename, 'a');
|
|
repl.rli.addListener('line', function(code) {
|
|
if (code && code.length && code !== '.history' && code !== '.exit' && lastLine !== code) {
|
|
// Save the latest command in the file
|
|
fs.writeSync(fd, `${code}\n`);
|
|
return lastLine = code;
|
|
}
|
|
});
|
|
// XXX: The SIGINT event from REPLServer is undocumented, so this is a bit fragile
|
|
repl.on('SIGINT', function() {
|
|
return sawSIGINT = true;
|
|
});
|
|
repl.on('exit', function() {
|
|
return fs.closeSync(fd);
|
|
});
|
|
// Add a command to show the history stack
|
|
return repl.commands[getCommandId(repl, 'history')] = {
|
|
help: 'Show command history',
|
|
action: function() {
|
|
repl.outputStream.write(`${repl.rli.history.slice(0).reverse().join('\n')}\n`);
|
|
return repl.displayPrompt();
|
|
}
|
|
};
|
|
};
|
|
|
|
getCommandId = function(repl, commandName) {
|
|
var commandsHaveLeadingDot;
|
|
// Node 0.11 changed API, a command such as '.help' is now stored as 'help'
|
|
commandsHaveLeadingDot = repl.commands['.help'] != null;
|
|
if (commandsHaveLeadingDot) {
|
|
return `.${commandName}`;
|
|
} else {
|
|
return commandName;
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
start: function(opts = {}) {
|
|
var build, major, minor, repl;
|
|
[major, minor, build] = process.versions.node.split('.').map(function(n) {
|
|
return parseInt(n, 10);
|
|
});
|
|
if (major < 6) {
|
|
console.warn("Node 6+ required for CoffeeScript REPL");
|
|
process.exit(1);
|
|
}
|
|
CoffeeScript.register();
|
|
process.argv = ['coffee'].concat(process.argv.slice(2));
|
|
opts = merge(replDefaults, opts);
|
|
repl = nodeREPL.start(opts);
|
|
if (opts.prelude) {
|
|
runInContext(opts.prelude, repl.context, 'prelude');
|
|
}
|
|
repl.on('exit', function() {
|
|
if (!repl.rli.closed) {
|
|
return repl.outputStream.write('\n');
|
|
}
|
|
});
|
|
addMultilineHandler(repl);
|
|
if (opts.historyFile) {
|
|
addHistory(repl, opts.historyFile, opts.historyMaxInputSize);
|
|
}
|
|
// Adapt help inherited from the node REPL
|
|
repl.commands[getCommandId(repl, 'load')].help = 'Load code from a file into this REPL session';
|
|
return repl;
|
|
}
|
|
};
|
|
|
|
}).call(this);
|