From 3284f6af18e7993762ab659fb94cf8e424c4b0d7 Mon Sep 17 00:00:00 2001 From: lihan Date: Wed, 31 Oct 2012 00:09:59 -0400 Subject: [PATCH 01/11] tests for the repl: ignoring comments, output in inspect mode, variable saving --- test/repl.coffee | 41 ++++++++++++++++++++++++++++++++++++++++- test/test.html | 1 - 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/test/repl.coffee b/test/repl.coffee index c683b32a..a6b74a76 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -1,4 +1,43 @@ # REPL # ---- -# TODO: add tests +# TODO: add more tests +{spawn} = require 'child_process' +PROMPT = 'coffee> ' + +testOutput = (expected, actual) -> + eq expected, actual.slice(0, expected.length) + actual.substr expected.length + +testCommands = (input, expectedOutput) -> + input = [input] if typeof input is 'string' + expectedOutput = [expectedOutput] if typeof expectedOutput is 'string' + output = '' + coffee = spawn 'bin/coffee' + input.push 'process.exit()' + + coffee.stdout.on 'data', (data) -> + output += data.toString().replace(/\u001b\[\d{0,2}m/g, '') + coffee.stdin.write "#{input.shift()}\n" + + coffee.on 'exit', -> + output = testOutput PROMPT, output + while expectedOutput.length > 0 + output = testOutput "#{expectedOutput.shift()}\n#{PROMPT}", output + eq '', output + +test "comments are ignored", -> + testCommands "1 + 1 #foo", "2" + +test "output in inspect mode", -> + testCommands '"1 + 1\\n"', "'1 + 1\\n'" + +test "variables are saved", -> + input = [ + "foo = 'foo'" + 'foobar = "#{foo}bar"' + ] + testCommands input, [ + "'foo'" + "'foobar'" + ] diff --git a/test/test.html b/test/test.html index 256b2988..a09692c9 100644 --- a/test/test.html +++ b/test/test.html @@ -106,7 +106,6 @@ 'option_parser' 'ranges' 'regexps' - 'repl' 'scope' 'slicing_and_splicing' 'soaks' From 627b921bca55fc9983c9c5463b9a60973d9c65c3 Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Mon, 14 Jan 2013 21:17:48 -0800 Subject: [PATCH 02/11] Initial commit of REPL based on node's REPLServer --- lib/coffee-script/command.js | 4 +- lib/coffee-script/repl.js | 277 +++-------------------------------- src/command.coffee | 4 +- src/repl.coffee | 208 ++------------------------ 4 files changed, 37 insertions(+), 456 deletions(-) diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index dfefacd5..2624e8da 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -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); diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index c14b8d1b..8a60ce24 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -1,276 +1,37 @@ // 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, merge, repl, replDefaults, vm; - stdin = process.openStdin(); + vm = require('vm'); - stdout = process.stdout; + repl = 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(code, context, file, cb) { try { - obj = Script.runInThisContext(obj); - } catch (e) { - return; + code = CoffeeScript.compile(code, { + filename: file, + bare: true + }); + return cb(null, vm.runInContext(code, context, file)); + } catch (err) { + return 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]; } }; - 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); - } + module.exports = { + start: function(opts) { + if (opts == null) { + opts = {}; } - completions = getCompletions(free, candidates); - return [completions, free]; + return repl.start(merge(replDefaults, opts)); } }; - 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() {} - }; - stdin.on('data', function(chunk) { - var line, lines, _i, _len, _ref; - pipedInput += chunk; - if (!/\n/.test(pipedInput)) { - 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; - } - stdout.write("" + line + "\n"); - run(line); - } - }); - 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); - } - 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); - }); - } else { - repl = readline.createInterface(stdin, stdout, autocomplete); - } - } - - 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); diff --git a/src/command.coffee b/src/command.coffee index 25cbd84d..fcb28596 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -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' diff --git a/src/repl.coffee b/src/repl.coffee index d94c63eb..f8b64120 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -1,197 +1,17 @@ -# 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' +repl = 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: (code, context, file, cb) -> + try + code = CoffeeScript.compile(code, {filename: file, bare: true}) + cb(null, vm.runInContext(code, context, file)) + catch err + cb(err) -# Config -REPL_PROMPT = 'coffee> ' -REPL_PROMPT_MULTILINE = '------> ' -REPL_PROMPT_CONTINUATION = '......> ' -enableColours = no -unless process.platform is 'win32' - enableColours = not process.env.NODE_DISABLE_COLORS - -# Log an error. -error = (err) -> - stdout.write (err.stack or err.toString()) + '\n' - -## 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() - return - if !buffer.toString().trim() and !backlog - repl.prompt() - 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 = {}) -> + repl.start merge(replDefaults, opts) From d84cd9466e517fc2d25e02ce7d5bea1ef15621aa Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Mon, 14 Jan 2013 22:22:28 -0800 Subject: [PATCH 03/11] empty command evaluates to undefined --- lib/coffee-script/repl.js | 3 +++ src/repl.coffee | 1 + test/repl.coffee | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 8a60ce24..0e1b2432 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -14,6 +14,9 @@ prompt: "coffee> ", "eval": function(code, context, file, cb) { try { + if (/^\(\s+\)$/.test(code)) { + code = ''; + } code = CoffeeScript.compile(code, { filename: file, bare: true diff --git a/src/repl.coffee b/src/repl.coffee index f8b64120..459274df 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -7,6 +7,7 @@ replDefaults = prompt: "coffee> ", eval: (code, context, file, cb) -> try + code = '' if /^\(\s+\)$/.test code # Empty command won't parse code = CoffeeScript.compile(code, {filename: file, bare: true}) cb(null, vm.runInContext(code, context, file)) catch err diff --git a/test/repl.coffee b/test/repl.coffee index a6b74a76..d5051d8b 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -11,7 +11,7 @@ testOutput = (expected, actual) -> testCommands = (input, expectedOutput) -> input = [input] if typeof input is 'string' - expectedOutput = [expectedOutput] if typeof expectedOutput is 'string' + expectedOutput = [expectedOutput] if typeof expectedOutput in ['string', 'undefined'] output = '' coffee = spawn 'bin/coffee' input.push 'process.exit()' @@ -41,3 +41,6 @@ test "variables are saved", -> "'foo'" "'foobar'" ] + +test "empty command evaluates to undefined", -> + testCommands "", undefined From 69d80e2dede6a4e226f32cd35cad9c7246b905ae Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Wed, 16 Jan 2013 11:48:50 -0800 Subject: [PATCH 04/11] Single quotes --- src/repl.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repl.coffee b/src/repl.coffee index 459274df..393aac28 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -4,7 +4,7 @@ CoffeeScript = require './coffee-script' {merge} = require './helpers' replDefaults = - prompt: "coffee> ", + prompt: 'coffee> ', eval: (code, context, file, cb) -> try code = '' if /^\(\s+\)$/.test code # Empty command won't parse From 4a52814a795e234afe401723fd38f14a2ae52e10 Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Tue, 15 Jan 2013 23:31:40 -0800 Subject: [PATCH 05/11] Multiline WIP --- lib/coffee-script/repl.js | 54 +++++++++++++++++++++++++++++++++++---- src/repl.coffee | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 0e1b2432..2b39ec01 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -1,22 +1,23 @@ // Generated by CoffeeScript 1.5.0-pre (function() { - var CoffeeScript, merge, repl, replDefaults, vm; + var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm; vm = require('vm'); - repl = require('repl'); + nodeREPL = require('repl'); CoffeeScript = require('./coffee-script'); merge = require('./helpers').merge; replDefaults = { - prompt: "coffee> ", + prompt: 'coffee> ', "eval": function(code, context, file, cb) { try { if (/^\(\s+\)$/.test(code)) { - code = ''; + return cb(null); } + code = code.replace(/子/mg, '\n'); code = CoffeeScript.compile(code, { filename: file, bare: true @@ -28,12 +29,55 @@ } }; + addMultilineHandler = function(repl) { + var inputStream, multiline, nodeLineListener, outputStream, rli; + rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream; + multiline = { + enabled: false, + prompt: new Array(repl.prompt.length).join('.') + ' ', + buffer: '' + }; + nodeLineListener = rli.listeners('line')[0]; + rli.removeListener('line', nodeLineListener); + rli.on('line', function(cmd) { + if (multiline.enabled === true) { + multiline.buffer += "" + cmd + "\n"; + return rli.prompt(true); + } else { + return nodeLineListener(cmd); + } + }); + return inputStream.on('keypress', function(char, key) { + if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) { + return; + } + multiline.enabled = !multiline.enabled; + if (multiline.enabled === false) { + if (!multiline.buffer.match(/\n/)) { + rli.setPrompt(repl.prompt); + rli.prompt(true); + return; + } + multiline.buffer = multiline.buffer.replace(/\n/mg, '子'); + rli.emit('line', multiline.buffer); + return multiline.buffer = ''; + } else { + rli.setPrompt(multiline.prompt); + return rli.prompt(true); + } + }); + }; + module.exports = { start: function(opts) { + var repl; if (opts == null) { opts = {}; } - return repl.start(merge(replDefaults, opts)); + opts = merge(replDefaults, opts); + repl = nodeREPL.start(opts); + addMultilineHandler(repl); + return repl; } }; diff --git a/src/repl.coffee b/src/repl.coffee index 393aac28..7a853caf 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -1,5 +1,5 @@ vm = require 'vm' -repl = require 'repl' +nodeREPL = require 'repl' CoffeeScript = require './coffee-script' {merge} = require './helpers' @@ -7,12 +7,52 @@ replDefaults = prompt: 'coffee> ', eval: (code, context, file, cb) -> try - code = '' if /^\(\s+\)$/.test code # Empty command won't parse + return cb(null) if /^\(\s+\)$/.test code # Empty command + code = code.replace /子/mg, '\n' # Temporary hack, see TODO below code = CoffeeScript.compile(code, {filename: file, bare: true}) cb(null, vm.runInContext(code, context, file)) catch err cb(err) +# TODO: how to test? +addMultilineHandler = (repl) -> + {rli, inputStream, outputStream} = repl + + multiline = + enabled: off + prompt: new Array(repl.prompt.length).join('.') + ' ' + buffer: '' + + # Proxy node's line listener + nodeLineListener = rli.listeners('line')[0] + rli.removeListener 'line', nodeLineListener + rli.on 'line', (cmd) -> + if multiline.enabled is on + multiline.buffer += "#{cmd}\n" + rli.prompt true + else + nodeLineListener(cmd) + + # 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' + multiline.enabled = !multiline.enabled + if multiline.enabled is off + unless multiline.buffer.match /\n/ + rli.setPrompt repl.prompt + rli.prompt true + return + # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval? + multiline.buffer = multiline.buffer.replace /\n/mg, '子' + rli.emit 'line', multiline.buffer + multiline.buffer = '' + else + rli.setPrompt multiline.prompt + rli.prompt true + module.exports = start: (opts = {}) -> - repl.start merge(replDefaults, opts) + opts = merge(replDefaults, opts) + repl = nodeREPL.start opts + addMultilineHandler(repl) + repl \ No newline at end of file From 041033a51af086b48156ff2a9dafb4675b88d99c Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Mon, 21 Jan 2013 00:43:08 -0600 Subject: [PATCH 06/11] finish multiline support for REPL rewrite --- lib/coffee-script/repl.js | 45 ++++++++++++++++++++++++------------- src/repl.coffee | 47 +++++++++++++++++++++++++-------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 2b39ec01..8bb78b04 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -12,17 +12,17 @@ replDefaults = { prompt: 'coffee> ', - "eval": function(code, context, file, cb) { + "eval": function(input, context, filename, cb) { + var js; try { - if (/^\(\s+\)$/.test(code)) { + if (/^\(\s+\)$/.test(input)) { return cb(null); } - code = code.replace(/子/mg, '\n'); - code = CoffeeScript.compile(code, { - filename: file, + js = CoffeeScript.compile(input, { + filename: filename, bare: true }); - return cb(null, vm.runInContext(code, context, file)); + return cb(null, vm.runInContext(js, context, filename)); } catch (err) { return cb(err); } @@ -34,36 +34,46 @@ rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream; multiline = { enabled: false, - prompt: new Array(repl.prompt.length).join('.') + ' ', + initialPrompt: '------> ', + prompt: '......> ', buffer: '' }; nodeLineListener = rli.listeners('line')[0]; rli.removeListener('line', nodeLineListener); rli.on('line', function(cmd) { - if (multiline.enabled === true) { + if (multiline.enabled) { multiline.buffer += "" + cmd + "\n"; - return rli.prompt(true); + rli.setPrompt(multiline.prompt); + rli.prompt(true); } else { - return nodeLineListener(cmd); + nodeLineListener(cmd); } }); return inputStream.on('keypress', function(char, key) { if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) { return; } - multiline.enabled = !multiline.enabled; - if (multiline.enabled === false) { + if (multiline.enabled) { if (!multiline.buffer.match(/\n/)) { + multiline.enabled = !multiline.enabled; rli.setPrompt(repl.prompt); rli.prompt(true); return; } - multiline.buffer = multiline.buffer.replace(/\n/mg, '子'); + if (!rli.line.match(/^\s*$/)) { + return; + } + multiline.enabled = !multiline.enabled; + rli.line = ''; + rli.cursor = 0; + rli.output.cursorTo(0); + rli.output.clearLine(1); rli.emit('line', multiline.buffer); - return multiline.buffer = ''; + multiline.buffer = ''; } else { - rli.setPrompt(multiline.prompt); - return rli.prompt(true); + multiline.enabled = !multiline.enabled; + rli.setPrompt(multiline.initialPrompt); + rli.prompt(true); } }); }; @@ -76,6 +86,9 @@ } opts = merge(replDefaults, opts); repl = nodeREPL.start(opts); + repl.on('exit', function() { + return repl.outputStream.write('\n'); + }); addMultilineHandler(repl); return repl; } diff --git a/src/repl.coffee b/src/repl.coffee index 7a853caf..db361c0a 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -5,54 +5,67 @@ CoffeeScript = require './coffee-script' replDefaults = prompt: 'coffee> ', - eval: (code, context, file, cb) -> + eval: (input, context, filename, cb) -> try - return cb(null) if /^\(\s+\)$/.test code # Empty command - code = code.replace /子/mg, '\n' # Temporary hack, see TODO below - code = CoffeeScript.compile(code, {filename: file, bare: true}) - cb(null, vm.runInContext(code, context, file)) + return cb null if /^\(\s+\)$/.test input # Empty command + # TODO: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations + js = CoffeeScript.compile input, {filename, bare: yes} + cb null, vm.runInContext js, context, filename catch err - cb(err) + cb err # TODO: how to test? addMultilineHandler = (repl) -> {rli, inputStream, outputStream} = repl - multiline = + multiline = enabled: off - prompt: new Array(repl.prompt.length).join('.') + ' ' + initialPrompt: '------> ' + prompt: '......> ' buffer: '' # Proxy node's line listener nodeLineListener = rli.listeners('line')[0] rli.removeListener 'line', nodeLineListener rli.on 'line', (cmd) -> - if multiline.enabled is on + if multiline.enabled multiline.buffer += "#{cmd}\n" + rli.setPrompt multiline.prompt rli.prompt true else nodeLineListener(cmd) + return # 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' - multiline.enabled = !multiline.enabled - if multiline.enabled is off + 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 - # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval? - multiline.buffer = multiline.buffer.replace /\n/mg, '子' + # no-op unless the current line is empty + return unless 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 rli.emit 'line', multiline.buffer multiline.buffer = '' else - rli.setPrompt multiline.prompt + multiline.enabled = not multiline.enabled + rli.setPrompt multiline.initialPrompt rli.prompt true + return module.exports = start: (opts = {}) -> - opts = merge(replDefaults, opts) + opts = merge replDefaults, opts repl = nodeREPL.start opts - addMultilineHandler(repl) - repl \ No newline at end of file + repl.on 'exit', -> repl.outputStream.write '\n' + addMultilineHandler repl + repl From 537c5f4b7094b284bd3f7ccd9c18bc71f3000501 Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Mon, 21 Jan 2013 10:27:52 -0600 Subject: [PATCH 07/11] fix some issues pointed out in 041033a51af086b48156ff2a9dafb4675b88d99c --- lib/coffee-script/repl.js | 16 ++++++++++++---- src/repl.coffee | 19 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 8bb78b04..2d6d5cd8 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -15,10 +15,13 @@ "eval": function(input, context, filename, cb) { var js; try { - if (/^\(\s+\)$/.test(input)) { + input = input.replace(/\uFF00/g, '\n'); + input = input.slice(1, -1); + input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'); + if (/^\s*$/.test(input)) { return cb(null); } - js = CoffeeScript.compile(input, { + js = CoffeeScript.compile("[]=(" + input + "\n)", { filename: filename, bare: true }); @@ -34,8 +37,12 @@ rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream; multiline = { enabled: false, - initialPrompt: '------> ', - prompt: '......> ', + initialPrompt: repl.prompt.replace(/^[^> ]*/, function(x) { + return x.replace(/./g, '-'); + }), + prompt: repl.prompt.replace(/^[^> ]*>?/, function(x) { + return x.replace(/./g, '.'); + }), buffer: '' }; nodeLineListener = rli.listeners('line')[0]; @@ -68,6 +75,7 @@ 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 { diff --git a/src/repl.coffee b/src/repl.coffee index db361c0a..52c7628f 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -7,9 +7,16 @@ replDefaults = prompt: 'coffee> ', eval: (input, context, filename, cb) -> try - return cb null if /^\(\s+\)$/.test input # Empty command - # TODO: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations - js = CoffeeScript.compile input, {filename, bare: yes} + # XXX: multiline hack + input = input.replace /\uFF00/g, '\n' + # strip node-added parens + input = input[1...-1] + # 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 + js = CoffeeScript.compile "[]=(#{input}\n)", {filename, bare: yes} cb null, vm.runInContext js, context, filename catch err cb err @@ -20,8 +27,8 @@ addMultilineHandler = (repl) -> multiline = enabled: off - initialPrompt: '------> ' - prompt: '......> ' + initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-') + prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.') buffer: '' # Proxy node's line listener @@ -54,6 +61,8 @@ addMultilineHandler = (repl) -> 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 From 47bd05e9a00a293ed69e6ea099b68bb86d97cf6e Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Mon, 21 Jan 2013 10:02:04 -0800 Subject: [PATCH 08/11] REPL tests based on direct interaction with input and output stream. Includes multiline tests. --- Cakefile | 1 + lib/coffee-script/repl.js | 2 +- src/repl.coffee | 2 +- test/repl.coffee | 106 +++++++++++++++++++++++++------------- 4 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Cakefile b/Cakefile index b148ee54..3f3020ef 100644 --- a/Cakefile +++ b/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) -> diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 2d6d5cd8..f3057827 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -67,7 +67,7 @@ rli.prompt(true); return; } - if (!rli.line.match(/^\s*$/)) { + if (!((rli.line == null) || rli.line.match(/^\s*$/))) { return; } multiline.enabled = !multiline.enabled; diff --git a/src/repl.coffee b/src/repl.coffee index 52c7628f..0b3766eb 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -54,7 +54,7 @@ addMultilineHandler = (repl) -> rli.prompt true return # no-op unless the current line is empty - return unless rli.line.match /^\s*$/ + return unless not rli.line? or rli.line.match /^\s*$/ # eval, print, loop multiline.enabled = not multiline.enabled rli.line = '' diff --git a/test/repl.coffee b/test/repl.coffee index d5051d8b..940651aa 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -1,46 +1,82 @@ # REPL # ---- +Stream = require 'stream' -# TODO: add more tests -{spawn} = require 'child_process' -PROMPT = 'coffee> ' +class MockInputStream extends Stream + constructor: () -> + @readable = true -testOutput = (expected, actual) -> - eq expected, actual.slice(0, expected.length) - actual.substr expected.length + resume: -> -testCommands = (input, expectedOutput) -> - input = [input] if typeof input is 'string' - expectedOutput = [expectedOutput] if typeof expectedOutput in ['string', 'undefined'] - output = '' - coffee = spawn 'bin/coffee' - input.push 'process.exit()' + emitLine: (val) -> + @emit 'data', new Buffer("#{val}\n") - coffee.stdout.on 'data', (data) -> - output += data.toString().replace(/\u001b\[\d{0,2}m/g, '') - coffee.stdin.write "#{input.shift()}\n" +class MockOutputStream extends Stream + constructor: () -> + @writable = true + @written = [] - coffee.on 'exit', -> - output = testOutput PROMPT, output - while expectedOutput.length > 0 - output = testOutput "#{expectedOutput.shift()}\n#{PROMPT}", output - eq '', output + write: (data) -> + #console.log 'output write', arguments + @written.push data -test "comments are ignored", -> - testCommands "1 + 1 #foo", "2" + lastWrite: (fromEnd=-1) -> + @written[@written.length - 1 + fromEnd].replace /\n$/, '' -test "output in inspect mode", -> - testCommands '"1 + 1\\n"', "'1 + 1\\n'" +testRepl = (desc, fn) -> + input = new MockInputStream() + output = new MockOutputStream() + Repl.start {input, output} + fn input, output -test "variables are saved", -> - input = [ - "foo = 'foo'" - 'foobar = "#{foo}bar"' - ] - testCommands input, [ - "'foo'" - "'foobar'" - ] +assertEqual = (expected, value) -> + eq expected, value, "Expected '#{value}' to equal '#{expected}'" -test "empty command evaluates to undefined", -> - testCommands "", undefined + +testRepl "starts with coffee prompt", (input, output) -> + assertEqual 'coffee> ', output.lastWrite(0) + +testRepl "writes eval to output", (input, output) -> + input.emitLine '1+1' + assertEqual '2', output.lastWrite() + +testRepl "comments are ignored", (input, output) -> + input.emitLine '1 + 1 #foo' + assertEqual '2', output.lastWrite() + +testRepl "output in inspect mode", (input, output) -> + input.emitLine '"1 + 1\\n"' + assertEqual "'1 + 1\\n'", output.lastWrite() + +testRepl "variables are saved", (input, output) -> + input.emitLine "foo = 'foo'" + input.emitLine 'foobar = "#{foo}bar"' + assertEqual "'foobar'", output.lastWrite() + +testRepl "empty command evaluates to undefined", (input, output) -> + input.emitLine '' + assertEqual 'undefined', output.lastWrite() + +ctrlV = { ctrl: true, name: 'v'} +testRepl "ctrl-v toggles multiline prompt", (input, output) -> + input.emit 'keypress', null, ctrlV + assertEqual '------> ', output.lastWrite(0) + input.emit 'keypress', null, ctrlV + assertEqual 'coffee> ', output.lastWrite(0) + +testRepl "multiline continuation changes prompt", (input, output) -> + input.emit 'keypress', null, ctrlV + input.emitLine '' + assertEqual '....... ', 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 '(->' + input.emitLine ' 1 + 1' + input.emitLine ')()' + input.emit 'keypress', null, ctrlV + assertEqual '2', output.lastWrite() From 702071553f2f74d3ca6130a5519b3ee4209d92ac Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Fri, 25 Jan 2013 21:53:35 -0800 Subject: [PATCH 09/11] Remove testing TODO --- src/repl.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/repl.coffee b/src/repl.coffee index 0b3766eb..df400426 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -21,7 +21,6 @@ replDefaults = catch err cb err -# TODO: how to test? addMultilineHandler = (repl) -> {rli, inputStream, outputStream} = repl From be9707f8d28e18ada4b2e90ef457184d2360e2d1 Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Sat, 26 Jan 2013 02:07:56 -0600 Subject: [PATCH 10/11] final tweaks to REPL rewrite We can still use some more extensive tests, but it's already much better tested than the current REPL. --- Cakefile | 4 ++-- lib/coffee-script/repl.js | 19 ++++++++--------- src/repl.coffee | 24 ++++++++++----------- test/repl.coffee | 44 +++++++++++++++++++-------------------- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/Cakefile b/Cakefile index 3f3020ef..31aa6586 100644 --- a/Cakefile +++ b/Cakefile @@ -200,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 eqaul #{b}" + global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg ? "Expected #{a} to deep eqaul #{b}" # When all the tests have run, collect and print errors. # If a stacktrace is available, output the compiled function source. diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index f3057827..06f7f321 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -14,21 +14,20 @@ 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 { - input = input.replace(/\uFF00/g, '\n'); - input = input.slice(1, -1); - input = input.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'); - if (/^\s*$/.test(input)) { - return cb(null); - } - js = CoffeeScript.compile("[]=(" + input + "\n)", { + js = CoffeeScript.compile("_=(" + input + "\n)", { filename: filename, bare: true }); - return cb(null, vm.runInContext(js, context, filename)); } catch (err) { - return cb(err); + cb(err); } + return cb(null, vm.runInContext(js, context, filename)); } }; @@ -67,7 +66,7 @@ rli.prompt(true); return; } - if (!((rli.line == null) || rli.line.match(/^\s*$/))) { + if ((rli.line != null) && !rli.line.match(/^\s*$/)) { return; } multiline.enabled = !multiline.enabled; diff --git a/src/repl.coffee b/src/repl.coffee index df400426..c0855b43 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -6,20 +6,18 @@ CoffeeScript = require './coffee-script' 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 - # XXX: multiline hack - input = input.replace /\uFF00/g, '\n' - # strip node-added parens - input = input[1...-1] - # 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 - js = CoffeeScript.compile "[]=(#{input}\n)", {filename, bare: yes} - cb null, vm.runInContext js, context, filename + js = CoffeeScript.compile "_=(#{input}\n)", {filename, bare: yes} catch err cb err + cb null, vm.runInContext(js, context, filename) addMultilineHandler = (repl) -> {rli, inputStream, outputStream} = repl @@ -39,7 +37,7 @@ addMultilineHandler = (repl) -> rli.setPrompt multiline.prompt rli.prompt true else - nodeLineListener(cmd) + nodeLineListener cmd return # Handle Ctrl-v @@ -53,7 +51,7 @@ addMultilineHandler = (repl) -> rli.prompt true return # no-op unless the current line is empty - return unless not rli.line? or rli.line.match /^\s*$/ + return if rli.line? and not rli.line.match /^\s*$/ # eval, print, loop multiline.enabled = not multiline.enabled rli.line = '' diff --git a/test/repl.coffee b/test/repl.coffee index 940651aa..c08dc6c0 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -3,7 +3,7 @@ Stream = require 'stream' class MockInputStream extends Stream - constructor: () -> + constructor: -> @readable = true resume: -> @@ -12,7 +12,7 @@ class MockInputStream extends Stream @emit 'data', new Buffer("#{val}\n") class MockOutputStream extends Stream - constructor: () -> + constructor: -> @writable = true @written = [] @@ -20,54 +20,53 @@ class MockOutputStream extends Stream #console.log 'output write', arguments @written.push data - lastWrite: (fromEnd=-1) -> + lastWrite: (fromEnd = -1) -> @written[@written.length - 1 + fromEnd].replace /\n$/, '' -testRepl = (desc, fn) -> - input = new MockInputStream() - output = new MockOutputStream() - Repl.start {input, output} - fn input, output -assertEqual = (expected, value) -> - eq expected, value, "Expected '#{value}' to equal '#{expected}'" +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) -> - assertEqual 'coffee> ', output.lastWrite(0) + eq 'coffee> ', output.lastWrite(0) testRepl "writes eval to output", (input, output) -> input.emitLine '1+1' - assertEqual '2', output.lastWrite() + eq '2', output.lastWrite() testRepl "comments are ignored", (input, output) -> input.emitLine '1 + 1 #foo' - assertEqual '2', output.lastWrite() + eq '2', output.lastWrite() testRepl "output in inspect mode", (input, output) -> input.emitLine '"1 + 1\\n"' - assertEqual "'1 + 1\\n'", output.lastWrite() + eq "'1 + 1\\n'", output.lastWrite() testRepl "variables are saved", (input, output) -> input.emitLine "foo = 'foo'" input.emitLine 'foobar = "#{foo}bar"' - assertEqual "'foobar'", output.lastWrite() + eq "'foobar'", output.lastWrite() testRepl "empty command evaluates to undefined", (input, output) -> input.emitLine '' - assertEqual 'undefined', output.lastWrite() + eq 'undefined', output.lastWrite() -ctrlV = { ctrl: true, name: 'v'} testRepl "ctrl-v toggles multiline prompt", (input, output) -> input.emit 'keypress', null, ctrlV - assertEqual '------> ', output.lastWrite(0) + eq '------> ', output.lastWrite(0) input.emit 'keypress', null, ctrlV - assertEqual 'coffee> ', output.lastWrite(0) + eq 'coffee> ', output.lastWrite(0) testRepl "multiline continuation changes prompt", (input, output) -> input.emit 'keypress', null, ctrlV input.emitLine '' - assertEqual '....... ', output.lastWrite(0) + eq '....... ', output.lastWrite(0) testRepl "evaluates multiline", (input, output) -> # Stubs. Could assert on their use. @@ -75,8 +74,7 @@ testRepl "evaluates multiline", (input, output) -> output.clearLine = -> input.emit 'keypress', null, ctrlV - input.emitLine '(->' + input.emitLine 'do ->' input.emitLine ' 1 + 1' - input.emitLine ')()' input.emit 'keypress', null, ctrlV - assertEqual '2', output.lastWrite() + eq '2', output.lastWrite() From b1300bdd79b19bb78d3b281c145fb4e16c865c9e Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Sat, 26 Jan 2013 02:29:16 -0600 Subject: [PATCH 11/11] fix typo; thanks @Nami-Doc --- Cakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cakefile b/Cakefile index 31aa6586..0ffa9762 100644 --- a/Cakefile +++ b/Cakefile @@ -200,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 ? "Expected #{a} to eqaul #{b}" - global.arrayEq = (a, b, msg) -> ok arrayEgal(a,b), msg ? "Expected #{a} to deep eqaul #{b}" + 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.