Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adding support for .node_history and .noderc.js in $HOME and `pwd` #3178

Open
wants to merge 2 commits into from

14 participants

regality Dirk von Grünigen Alex David Morteza Milani Oleg Efimov Ben Noordhuis isaacs Jeroen Janssen AJ ONeal Nodejs Jenkins David Dias Andrew Shaffer teramako josher19
regality

This adds support for .node_history, which behaves like a .bash_history for the node repl. When an interactive node quits, it writes ~/.node_history and will load it the next time you start the node repl.

This patch also allows a user to create a file called .noderc.js and place it in their home directory, or the directory they start node from.

When node is started with the -i flag or without arguments, it fires up the REPL.
At this point it will check if ~/.noderc.js or <pwd>/.noderc.js exist. If they do, whatever objects they export will be copied into the repl context and be available for use.

A common use case for this will likely be loading debugging tools. For example I have the following as my noderc:

var util = require('util');

function inspect(obj) {
  console.log(util.inspect(obj, true, 10, true));
}

exports.p = inspect;

That way whenever I start the node repl, I can just type p({some:'huge object'}) without having to retype basic boilerplate every time I start node.

Dirk von Grünigen

+1 for that! But, why don't you just omit the .js extensions? Never saw an rc files with a file extension..

regality

Adding the file extension seems like it would alleviate possible confusion, as well as play nice with editors. I'm not 100% sold on leaving the extension there, but is seems like the rational thing to do.

Alex David

I really like this idea. +1

regality

That last commit adds a history feature to the node REPL that behaves similiar to a bash history.

Morteza Milani

+1

Oleg Efimov

That last commit adds a history feature to the node REPL that behaves similiar to a bash history.

Maybe it is better to chnage JSON format for history file for something more standard? grep-ing history should be allowed

src/node.js
((28 lines not shown))
+ fs.readFile(nodeHistory, function(err, data) {
+ if (err) return;
+ try {
+ var history = JSON.parse(data);
+ repl.setHistory(history);
+ } catch (e) { }
+ });
+ }
+
+ function saveReplHistory(repl, callback) {
+ var fs = NativeModule.require('fs');
+ var history = JSON.stringify(repl.getHistory(), false, 1);
+ if (!nodeHistory) return callback();
+
+ fs.writeFile(nodeHistory, history, function(err) {
+ callback();

This swallows errors. Is that intentional?

Yes, if someone doesn't want a history and does a chmod 000 ~/.node_history then we don't want to complain about it. Are there any use cases where we want to display errors when saving the history? I wouldn't mind it being a only displayed with a verbose flag, but I'm not aware of node having verbose logging at this time?

Oleg Efimov
Sannis added a note

We can show warning to user in this case, like it done for npm * usage in repl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/node.js
@@ -123,11 +124,37 @@
if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
opts.useColors = false;
}
+
+ var home = process.env.HOME;

This won't work on Windows.

Fixed in d01da28

Damn wikipedia. Actually fixed in 61a3221.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
regality

@Sannis Agreed, last commit puts each history entry on it's own line in the file. Good on call on supporting grep.

Ben Noordhuis

@regality: Can you add some tests? Thanks.

@piscisaureus: Can you review this PR as well?

Oleg Efimov

Is therу any chance that this will be merged in near future?

regality

@Sannis, I chatted with @isaacs about it a while back and he said we could expect this to be pulled in for v0.9, so probably not too soon, but it's on the todo-list.

Oleg Efimov

Nice!

isaacs
Owner

@Sannis @regality Just to clarify:

I don't recall saying "We'll merge this patch." I believe I said something to the effect of, "we want some kind of nicer experience on the repl, and not having history or setup is kind of painful."

I have not looked into this patch in depth, and we'll have to be very careful about it. A lot of people use the repl, and it's also used by the debugger, and as a utility to administer many production servers.

v0.8 is imminent, probably going to land in the next week or so. We'll discuss the best route forward once 0.8 is forked and we're into 0.9. A discussion on the nodejs-dev mailing list would be worthwhile as well, just to get some input.

regality

@isaacs Sorry about the misunderstanding, I guess I just got a bit too excited.

As for this patch, I can't see it breaking anything, it should be reverse compatible with any existing node setup.

I'll go ahead and start a thread on the mailing list and we can revisit things after v0.8 lands.

Jeroen Janssen

+1

lib/readline.js
@@ -503,6 +507,18 @@ Interface.prototype._line = function() {
};
+Interface.prototype.getHistory = function() {
+ return this.history;
+};
+
+Interface.prototype.setHistory = function(history) {
+ this.history = history;
+ this.historyIndex = -1;
+ if (this.history.length > kHistorySize) {
+ this.history.splice(kHistorySize, Infinity);

Infinity not needed here.

Suggest replace whole function with:

Interface.prototype.setHistory = function(history) {
  this.historyIndex = -1;
  this.history = history.slice(0, kHistorySize);
}

so as to avoid side effects such as:

h = [1,2,3,4];
repl.setHistory(h);
h.unshift(0);
console.assert(repl.getHistory() === 1, "0 instead of 1"); // 0 instead of 1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
teramako teramako commented on the diff
lib/repl.js
@@ -352,6 +352,19 @@ REPLServer.prototype.resetContext = function(force) {
this.context = context;
};
+REPLServer.prototype.loadContext = function(file) {
+ var Module = require('module');
+ var context;
+ context = Module._load(file);
+ if (context && typeof context === 'object') {
+ for (var key in context) {
+ if (context.hasOwnProperty(key)) {
+ this.context[key] = context[key];
+ }
+ }
+ }
+};
teramako
teramako added a note

ensure the file exists and set all exported properties

REPLServer.prototype.loadContext = function(file) {
  var self = this,
      Module = require('module');
  fs.exists(file, function(exists) {
    if (!exists) return;
    var context = Module._load(file);
    if (context && typeof context === 'object') {
      Object.getOwnPropertyNames(context).forEach(function(key) {
        Object.defineProperty(self.context, key, Object.getOwnPropertyDescriptor(context, key));
      });
    }
  });
};
regality
regality added a note

the context file not being found is handled in src/node.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
AJ ONeal

+1

Nodejs Jenkins

Can one of the admins verify this patch?

regality

Just updated this to work with master and got all tests passing. What's the chance of getting this merged in before 0.12?

David Dias
Owner

@regality have you run the tests for this patch? Seems failing on smartOS-x64, or at least it shows that.

regality

I think the failing tests are unrelated tests that are also failing on master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
19 lib/readline.js
View
@@ -25,7 +25,7 @@
// * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
-var kHistorySize = 30;
+var kHistorySize = 100;
var util = require('util');
var inherits = require('util').inherits;
@@ -102,6 +102,9 @@ function Interface(input, output, completer, terminal) {
self._refreshLine();
}
+ this.history = [];
+ this.historyIndex = -1;
+
if (!this.terminal) {
input.on('data', ondata);
input.on('end', onend);
@@ -128,9 +131,6 @@ function Interface(input, output, completer, terminal) {
// Cursor position on the line.
this.cursor = 0;
- this.history = [];
- this.historyIndex = -1;
-
output.on('resize', onresize);
self.once('close', function() {
input.removeListener('keypress', onkeypress);
@@ -507,6 +507,17 @@ Interface.prototype._deleteLineRight = function() {
this._refreshLine();
};
+Interface.prototype.getHistory = function() {
+ return this.history;
+};
+
+Interface.prototype.setHistory = function(history) {
+ this.history = history.concat();
+ this.historyIndex = -1;
+ if (this.history.length > kHistorySize) {
+ this.history.splice(kHistorySize, Infinity);
+ }
+};
Interface.prototype.clearLine = function() {
this._moveCursor(+Infinity);
42 lib/repl.js
View
@@ -386,6 +386,48 @@ REPLServer.prototype.resetContext = function() {
this.emit('reset', this.context);
};
+REPLServer.prototype.loadContext = function(file) {
+ var Module = require('module');
+ var context = Module._load(file);
+ if (context && typeof context === 'object') {
+ for (var key in context) {
+ if (context.hasOwnProperty(key)) {
+ this.context[key] = context[key];
+ }
+ }
+ }
+};
teramako
teramako added a note

ensure the file exists and set all exported properties

REPLServer.prototype.loadContext = function(file) {
  var self = this,
      Module = require('module');
  fs.exists(file, function(exists) {
    if (!exists) return;
    var context = Module._load(file);
    if (context && typeof context === 'object') {
      Object.getOwnPropertyNames(context).forEach(function(key) {
        Object.defineProperty(self.context, key, Object.getOwnPropertyDescriptor(context, key));
      });
    }
  });
};
regality
regality added a note

the context file not being found is handled in src/node.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+REPLServer.prototype.setHistory = function(history) {
+ this.rli.setHistory(history);
+};
+
+REPLServer.prototype.getHistory = function() {
+ return this.rli.getHistory();
+};
+
+REPLServer.prototype.loadHistory = function(file, callback) {
+ var self = this;
+ callback = callback || function() {};
+ if (!file) return callback();
+ fs.readFile(file, function(err, data) {
+ if (err) return callback(err);
+ data = data.toString();
+ if (!data) return callback();
+ var history = data.split('\n').reverse();
+ self.setHistory(history);
+ callback();
+ });
+};
+
+REPLServer.prototype.saveHistory = function(file, callback) {
+ if (!file) return callback();
+ var history = this.getHistory().reverse().join('\n');
+ fs.writeFile(file, history, function(err) {
+ callback(err);
+ });
+};
+
REPLServer.prototype.displayPrompt = function(preserveCursor) {
var prompt = this.prompt;
if (this.bufferedCommand.length) {
30 src/node.js
View
@@ -125,6 +125,7 @@
} else {
var Module = NativeModule.require('module');
+ var path = NativeModule.require('path');
// If -i or --interactive were passed, or stdin is a TTY.
if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
@@ -140,8 +141,35 @@
opts.useColors = false;
}
var repl = Module.requireRepl().start(opts);
+
+ var home = (process.platform === 'win32' ?
+ process.env.HOMEDRIVE + process.env.HOMEPATH :
+ process.env.HOME);
+ if (home) {
+ var noderc = path.join(home, '.noderc.js');
+ var nodeHistory = path.join(home, '.node_history');
+ }
+ var pwd = path.resolve('.');
+ var pwdrc = path.join(pwd, '.noderc.js');
+ [noderc, pwdrc].forEach(function(file) {
+ try {
+ repl.loadContext(file);
+ } catch (e) {
+ if (e.code !== 'MODULE_NOT_FOUND') {
+ // rethrow any errors generated from noderc
+ throw e;
+ }
+ }
+ });
+
+ repl.loadHistory(nodeHistory, function(err) {
+ // if (err) console.log(err.stack);
+ });
repl.on('exit', function() {
- process.exit();
+ repl.saveHistory(nodeHistory, function(err) {
+ // if (err) console.log(err.stack);
+ process.exit();
+ });
});
} else {
22 test/fixtures/context.js
View
@@ -0,0 +1,22 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+exports.foo = 'bar';
34 test/simple/test-repl-load-context.js
View
@@ -0,0 +1,34 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common'),
+ path = require('path'),
+ assert = require('assert'),
+ fs = require('fs'),
+ repl = require('repl').start('');
+
+var nodercFile = path.join(common.fixturesDir, 'context.js');
+
+repl.loadContext(nodercFile);
+
+assert.equal(repl.context.foo, 'bar');
+
+process.exit();
56 test/simple/test-repl-load-save-history.js
View
@@ -0,0 +1,56 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common'),
+ path = require('path'),
+ assert = require('assert'),
+ repl = require('repl').start('');
+
+var history = [
+ 'never gonna give you up',
+ 'never gonna let you down',
+ 'never gonna gonna run around and desert you',
+ 'never gonna make you cry',
+ 'never gonna say goodbye',
+ 'never gonna tell a lie and hurt you'
+];
+
+var historyFile = path.join(common.tmpDir, 'history');
+
+saveHistory();
+
+function saveHistory() {
+ repl.setHistory(history);
+
+ repl.saveHistory(historyFile, function(err) {
+ if (err) throw err;
+ loadHistory();
+ });
+}
+
+function loadHistory() {
+ repl.setHistory([]);
+ repl.loadHistory(historyFile, function(err) {
+ if (err) throw err;
+ assert.deepEqual(repl.rli.history, history);
+ process.exit();
+ });
+}
Something went wrong with that request. Please try again.