Revert "Don't use a separate context for the repl."
authorisaacs <i@izs.me>
Thu, 20 Oct 2011 15:54:50 +0000 (08:54 -0700)
committerisaacs <i@izs.me>
Fri, 21 Oct 2011 20:00:37 +0000 (13:00 -0700)
This reverts commit b70fed48a7fb0ac884e6574253a89db6158b2f60.

doc/api/repl.markdown
lib/repl.js
test/common.js

index 7bce1f7..c0b9789 100644 (file)
@@ -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.
 
index 3feb651..3da7b61 100644 (file)
@@ -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 <name>() 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;
+};
index a8ebb87..a906217 100644 (file)
@@ -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;