Skip to content

Instantly share code, notes, and snippets.

@goliatone
Forked from princejwesley/await-babel-repl.js
Last active April 4, 2023 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save goliatone/e8f38b75aa05b2d189f68a92c61af110 to your computer and use it in GitHub Desktop.
Save goliatone/e8f38b75aa05b2d189f68a92c61af110 to your computer and use it in GitHub Desktop.
[Node REPL] Examples of node REPL #repl #nodejs #node

Node.js REPL Examples

Different examples of Node.js REPLs

  • Top level async/await
  • Using custom VM
  • Using atocomplete, custom commands, histroy, etc
const repl = require('repl');
const babel = require('babel-core');
function preprocess(input) {
const awaitMatcher = /^(?:\s*(?:(?:let|var|const)\s)?\s*([^=]+)=\s*|^\s*)(await\s[\s\S]*)/;
const asyncWrapper = (code, binder) => {
let assign = binder ? `global.${binder} = ` : '';
return `(function(){ async function _wrap() { return ${assign}${code} } return _wrap();})()`;
};
// match & transform
const match = input.match(awaitMatcher);
if (match) {
input = `${asyncWrapper(match[2], match[1])}`;
}
return input;
}
function myEval(cmd, context, filename, callback) {
const code = babel.transform(preprocess(cmd), {
presets: ['es2015', 'stage-3'],
plugins: [
["transform-runtime", {
"regenerator": true
}]
]
}).code;
_eval(code, context, filename, callback);
}
const replInstance = repl.start({ prompt: '> ' });
const _eval = replInstance.eval;
replInstance.eval = myEval;
/*
* A "simple" NodeJS REPL for a custom CLI app using repl (https://nodejs.org/api/repl.html)
* with custom commands, autocomplete for custom commands, persistent command history &
* "command history compression" (won't keep multiple instances in a row of the same command in the history)
*
* Author: l3l_aze <at> Yahoo!! (dot) com
*/
'use strict';
const fs = require('fs');
const path = require('path');
const name = 'test-repl';
const debug = require('debug')(name);
// Custom command setup
const fullCommands = {
'clear': 'clear the screen',
'quit': 'Exit the REPL',
'exit': 'Exit the REPL'
};
const commands = Object.keys(fullCommands);
// Create REPL
const repl = require('repl');
const server = repl.start({
prompt: 'test-repl>',
terminal: true, // Set to true to enable command history
ignoreUndefined: true, // Ignore 'undefined' when running a command that does not return a value
replMode: 'strict' // 'use strict'
});
// Setup REPL
function init() {
process.stdout.write('\n'); // So The next debug line doesn't split into two lines, and doesn't start on the prompt line
debug('Intializing %s', name);
const replHistoryFile = path.join('./', '.node_repl_history');
server.eval = customEval;
server.completer = customAutocomplete;
// Save command history when exiting
server.on('exit', () => {
debug('Closing %s...saving history', name);
// server.lines = commands used in current session
let data = server.lines;
// Do not try to save history when there have been no commands run
if (data.length > 0) {
fs.appendFileSync(replHistoryFile, data.join('\n') + '\n');
}
process.exit();
})
// Obviously we don't want to try to load non-existent history.
if (fs.existsSync(replHistoryFile)) {
loadHistory(replHistoryFile);
}
}
// Custom 'eval' for REPL, to handle custom commands
const customEval = function customEval(cmd, callback) {
let result;
cmd = cmd.trim();
// Calling eval with an empty line below in the default case will cause it to be saved in command history.
if (cmd === '') {
return undefined;
}
switch (cmd) {
case 'clear':
process.stdout.cursorTo(0, 0); // Move to top line of terminal
process.stdout.clearLine();
process.stdout.clearScreenDown();
server.lines.push(cmd); // Save known command in history
break;
case 'quit':
case 'exit':
server.lines.push(cmd); // Save command in history when successful
server.emit('exit'); // Rather than process.exit, because that will just quit the program immediately.
default:
// Wrapped in try/catch to prevent errors from stopping the REPL
try {
result = eval(cmd);
// Print result of mathematical formulas, etc
if (result !== undefined) {
console.info(result);
}
server.lines.push(cmd); // Save command in history when successful
} catch (err) {
// Single-line error messages like 'ReferenceError: ls is not defined'
console.error(err.constructor.name + ': ' + err.message);
}
}
return result;
}
// Autocomplete-with-tab setup
const customAutocomplete = function customAutocomplete(line, callback) { // non-async version crashes when used.
const hits = commands.filter(c => c.indexOf(line) === 0);
callback(null, [hits.length > 0 ? hits : commands, line]);
};
const loadHistory = function loadHistory(file) {
let data;
let line;
let lastLine;
data = ('' + fs.readFileSync(file))
.split('\n')
.filter(line => line.trim());
debug('Entries: %s', data.length);
// "Command History Compression"
// Remove multiple instances in a row of the exact same command line while loading
// and re-write the history file with the new, slightly better, compressed version.
data = data.filter(line => {
if (lastLine === undefined) {
lastLine = line;
return true;
} else if (line !== lastLine) {
lastLine = line;
return true;
}
lastLine = undefined;
return false;
})
fs.writeFileSync(file, data.join('\n') + '\n');
data.reverse().map(line => server.history.push(line));
debug('Loaded command history: %s entries', data.length);
// Otherwise the last debug call there leaves the user at a non-prompt which will change when arrow up/down is pressed.
if (process.env.DEBUG) {
process.stdout.write(server._prompt);
}
};
init();
#!/usr/bin/env node
var repl = require('repl'),
fs = require('fs'),
path = require('path'),
vm = require('vm');
var myrepl = repl.start('> ');
var stup = path.join(process.env.HOME, '.noderc');
vm.runInContext(fs.readFileSync(stup).toString(), myrepl.context);
process.argv.slice(2).forEach(function(filename) {
vm.runInContext(fs.readFileSync(filename).toString(), myrepl.context);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment