From caf70f5e9419c307bc9f35c3062e9af8b7bf48ea Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 20 Oct 2011 08:54:50 -0700 Subject: [PATCH] Revert "Don't use a separate context for the repl." This reverts commit b70fed48a7fb0ac884e6574253a89db6158b2f60. --- doc/api/repl.markdown | 18 ++++++- lib/repl.js | 116 +++++++++++++++++++++++++++++++----------- test/common.js | 11 ---- 3 files changed, 104 insertions(+), 41 deletions(-) diff --git a/doc/api/repl.markdown b/doc/api/repl.markdown index 7bce1f7e50..c0b9789e1b 100644 --- a/doc/api/repl.markdown +++ b/doc/api/repl.markdown @@ -83,12 +83,28 @@ The special variable `_` (underscore) contains the result of the last expression > _ += 1 4 -The REPL provides access to any variables in the global scope. +The REPL provides access to any variables in the global scope. You can expose +a variable to the REPL explicitly by assigning it to the `context` object +associated with each `REPLServer`. For example: + + // repl_test.js + var repl = require("repl"), + msg = "message"; + + repl.start().context.m = msg; + +Things in the `context` object appear as local within the REPL: + + mjr:~$ node repl_test.js + > m + 'message' There are a few special REPL commands: - `.break` - While inputting a multi-line expression, sometimes you get lost or just don't care about completing it. `.break` will start over. + - `.clear` - Resets the `context` object to an empty object and clears any + multi-line expression. - `.exit` - Close the I/O stream, which will cause the REPL to exit. - `.help` - Show this list of special commands. diff --git a/lib/repl.js b/lib/repl.js index 3feb651fa8..3da7b61e4f 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -46,10 +46,6 @@ var path = require('path'); var fs = require('fs'); var rl = require('readline'); -global.module = module; -global.exports = exports; -global.require = require; - // If obj.hasOwnProperty has been overridden, then calling // obj.hasOwnProperty(prop) will break. // See: https://github.com/joyent/node/issues/1707 @@ -57,6 +53,9 @@ function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } + +var context; + exports.disableColors = process.env.NODE_DISABLE_COLORS ? true : false; // hack for require.resolve("./relative") to work properly. @@ -72,27 +71,16 @@ exports.writer = util.inspect; function REPLServer(prompt, stream, eval) { var self = this; - var contextWarning; - Object.defineProperty(this, 'context', { - get: function() { - if (!contextWarning) { - contextWarning = 'repl.context is deprecated.'; - console.error(contextWarning); - } - return global; - } - }); - - - self.eval = eval || function(code, file, cb) { + self.eval = eval || function(code, context, file, cb) { try { - var err, result = vm.runInThisContext(code, file); + var err, result = vm.runInContext(code, context, file); } catch (e) { err = e; } cb(err, result); }; + self.resetContext(); self.bufferedCommand = ''; if (stream) { @@ -185,13 +173,14 @@ function REPLServer(prompt, stream, eval) { // First we attempt to eval as expression with parens. // This catches '{a : 1}' properly. self.eval('(' + evalCmd + ')', + self.context, 'repl', function(e, ret) { if (e && !isSyntaxError(e)) return finish(e); if (typeof ret === 'function' || e) { // Now as statement without parens. - self.eval(evalCmd, 'repl', finish); + self.eval(evalCmd, self.context, 'repl', finish); } else { finish(null, ret); } @@ -228,8 +217,8 @@ function REPLServer(prompt, stream, eval) { self.bufferedCommand = ''; // If we got any output - print it (if no error) - if (!e) { - global._ = ret; + if (!e && ret !== undefined) { + self.context._ = ret; self.outputStream.write(exports.writer(ret) + '\n'); } @@ -256,12 +245,25 @@ exports.start = function(prompt, source, eval) { }; -var resetWarning; +REPLServer.prototype.createContext = function() { + var context = vm.createContext(); + + for (var i in global) context[i] = global[i]; + context.module = module; + context.require = require; + context.global = context; + context.global.global = context; + + return context; +}; + REPLServer.prototype.resetContext = function(force) { - if (!resetWarning) { - resetWarning = 'REPLServer.resetContext is deprecated.'; - console.error(resetWarning); + if (!context || force) { + context = this.createContext(); + for (var i in require.cache) delete require.cache[i]; } + + this.context = context; }; REPLServer.prototype.displayPrompt = function() { @@ -411,9 +413,26 @@ REPLServer.prototype.complete = function(line, callback) { if (!expr) { // If context is instance of vm.ScriptContext // Get global vars synchronously - completionGroups.push(Object.getOwnPropertyNames(global)); - addStandardGlobals(); - completionGroupsLoaded(); + if (this.context.constructor.name === 'Context') { + completionGroups.push(Object.getOwnPropertyNames(this.context)); + addStandardGlobals(); + completionGroupsLoaded(); + } else { + this.eval('.scope', this.context, 'repl', function(err, globals) { + if (err || !globals) { + addStandardGlobals(); + } else if (Array.isArray(globals[0])) { + // Add grouped globals + globals.forEach(function(group) { + completionGroups.push(group); + }); + } else { + completionGroups.push(globals); + addStandardGlobals(); + } + completionGroupsLoaded(); + }); + } function addStandardGlobals() { // Global object properties @@ -438,7 +457,7 @@ REPLServer.prototype.complete = function(line, callback) { } } else { - this.eval(expr, 'repl', function(e, obj) { + this.eval(expr, this.context, 'repl', function(e, obj) { // if (e) console.log(e); if (obj != null) { @@ -565,6 +584,16 @@ function defineDefaultCommands(repl) { } }); + repl.defineCommand('clear', { + help: 'Break, and also clear the local context', + action: function() { + this.outputStream.write('Clearing context...\n'); + this.bufferedCommand = ''; + this.resetContext(true); + this.displayPrompt(); + } + }); + repl.defineCommand('exit', { help: 'Exit the repl', action: function() { @@ -599,3 +628,32 @@ function trimWhitespace(cmd) { function regexpEscape(s) { return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } + + +/** + * Converts commands that use var and function () to use the + * local exports.context when evaled. This provides a local context + * on the REPL. + * + * @param {String} cmd The cmd to convert. + * @return {String} The converted command. + */ +REPLServer.prototype.convertToContext = function(cmd) { + var self = this, matches, + scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m, + scopeFunc = /^\s*function\s*([_\w\$]+)/; + + // Replaces: var foo = "bar"; with: self.context.foo = bar; + matches = scopeVar.exec(cmd); + if (matches && matches.length === 3) { + return 'self.context.' + matches[1] + matches[2]; + } + + // Replaces: function foo() {}; with: foo = function foo() {}; + matches = scopeFunc.exec(self.bufferedCommand); + if (matches && matches.length === 2) { + return matches[1] + ' = ' + self.bufferedCommand; + } + + return cmd; +}; diff --git a/test/common.js b/test/common.js index a8ebb871f9..a906217823 100644 --- a/test/common.js +++ b/test/common.js @@ -123,17 +123,6 @@ process.on('exit', function() { knownGlobals.push(DataView); } - // repl pollution - if (global.hasOwnProperty('module')) { - knownGlobals.push(global.module); - } - if (global.hasOwnProperty('require')) { - knownGlobals.push(global.require); - } - if (global.hasOwnProperty('exports')) { - knownGlobals.push(global.exports); - } - for (var x in global) { var found = false;