summaryrefslogtreecommitdiff
path: root/lib/internal/repl/history.js
diff options
context:
space:
mode:
authorLance Ball <lball@redhat.com>2019-02-01 12:49:16 -0500
committerLance Ball <lball@redhat.com>2019-02-11 14:30:26 -0500
commit0aa74443d8bdea3c3840dbc3d4bd700b05ca7a4c (patch)
tree962e4c785ba03f01bf6ba5ad444f14e416c485ec /lib/internal/repl/history.js
parent902c71a9d09bce6b1da4258d1775375f6539bbec (diff)
downloadandroid-node-v8-0aa74443d8bdea3c3840dbc3d4bd700b05ca7a4c.tar.gz
android-node-v8-0aa74443d8bdea3c3840dbc3d4bd700b05ca7a4c.tar.bz2
android-node-v8-0aa74443d8bdea3c3840dbc3d4bd700b05ca7a4c.zip
repl: add repl.setupHistory for programmatic repl
Adds a `repl.setupHistory()` instance method so that programmatic REPLs can also write history to a file. This change also refactors all of the history file management to `lib/internal/repl/history.js`, cleaning up and simplifying `lib/internal/repl.js`. PR-URL: https://github.com/nodejs/node/pull/25895 Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Diffstat (limited to 'lib/internal/repl/history.js')
-rw-r--r--lib/internal/repl/history.js153
1 files changed, 153 insertions, 0 deletions
diff --git a/lib/internal/repl/history.js b/lib/internal/repl/history.js
new file mode 100644
index 0000000000..a0ae07441e
--- /dev/null
+++ b/lib/internal/repl/history.js
@@ -0,0 +1,153 @@
+'use strict';
+
+const { Interface } = require('readline');
+const path = require('path');
+const fs = require('fs');
+const os = require('os');
+const util = require('util');
+const debug = util.debuglog('repl');
+
+// XXX(chrisdickinson): The 15ms debounce value is somewhat arbitrary.
+// The debounce is to guard against code pasted into the REPL.
+const kDebounceHistoryMS = 15;
+
+module.exports = setupHistory;
+
+function _writeToOutput(repl, message) {
+ repl._writeToOutput(message);
+ repl._refreshLine();
+}
+
+function setupHistory(repl, historyPath, ready) {
+ // Empty string disables persistent history
+ if (typeof historyPath === 'string')
+ historyPath = historyPath.trim();
+
+ if (historyPath === '') {
+ repl._historyPrev = _replHistoryMessage;
+ return ready(null, repl);
+ }
+
+ if (!historyPath) {
+ try {
+ historyPath = path.join(os.homedir(), '.node_repl_history');
+ } catch (err) {
+ _writeToOutput(repl, '\nError: Could not get the home directory.\n' +
+ 'REPL session history will not be persisted.\n');
+
+ debug(err.stack);
+ repl._historyPrev = _replHistoryMessage;
+ return ready(null, repl);
+ }
+ }
+
+ var timer = null;
+ var writing = false;
+ var pending = false;
+ repl.pause();
+ // History files are conventionally not readable by others:
+ // https://github.com/nodejs/node/issues/3392
+ // https://github.com/nodejs/node/pull/3394
+ fs.open(historyPath, 'a+', 0o0600, oninit);
+
+ function oninit(err, hnd) {
+ if (err) {
+ // Cannot open history file.
+ // Don't crash, just don't persist history.
+ _writeToOutput(repl, '\nError: Could not open history file.\n' +
+ 'REPL session history will not be persisted.\n');
+ debug(err.stack);
+
+ repl._historyPrev = _replHistoryMessage;
+ repl.resume();
+ return ready(null, repl);
+ }
+ fs.close(hnd, onclose);
+ }
+
+ function onclose(err) {
+ if (err) {
+ return ready(err);
+ }
+ fs.readFile(historyPath, 'utf8', onread);
+ }
+
+ function onread(err, data) {
+ if (err) {
+ return ready(err);
+ }
+
+ if (data) {
+ repl.history = data.split(/[\n\r]+/, repl.historySize);
+ } else {
+ repl.history = [];
+ }
+
+ fs.open(historyPath, 'r+', onhandle);
+ }
+
+ function onhandle(err, hnd) {
+ if (err) {
+ return ready(err);
+ }
+ fs.ftruncate(hnd, 0, (err) => {
+ repl._historyHandle = hnd;
+ repl.on('line', online);
+
+ // Reading the file data out erases it
+ repl.once('flushHistory', function() {
+ repl.resume();
+ ready(null, repl);
+ });
+ flushHistory();
+ });
+ }
+
+ // ------ history listeners ------
+ function online(line) {
+ repl._flushing = true;
+
+ if (timer) {
+ clearTimeout(timer);
+ }
+
+ timer = setTimeout(flushHistory, kDebounceHistoryMS);
+ }
+
+ function flushHistory() {
+ timer = null;
+ if (writing) {
+ pending = true;
+ return;
+ }
+ writing = true;
+ const historyData = repl.history.join(os.EOL);
+ fs.write(repl._historyHandle, historyData, 0, 'utf8', onwritten);
+ }
+
+ function onwritten(err, data) {
+ writing = false;
+ if (pending) {
+ pending = false;
+ online();
+ } else {
+ repl._flushing = Boolean(timer);
+ if (!repl._flushing) {
+ repl.emit('flushHistory');
+ }
+ }
+ }
+}
+
+function _replHistoryMessage() {
+ if (this.history.length === 0) {
+ _writeToOutput(
+ this,
+ '\nPersistent history support disabled. ' +
+ 'Set the NODE_REPL_HISTORY environment\nvariable to ' +
+ 'a valid, user-writable path to enable.\n'
+ );
+ }
+ this._historyPrev = Interface.prototype._historyPrev;
+ return this._historyPrev();
+}