Custom Node JS REPL input/output stream - node.js

I need to have custom REPL input/output stream. for example I need to pass a piece of script to the REPL when some event happens and get it's output and do something with it.
To describe it more clear to you, I'm working on a vscode plugin (github: source code) which provides REPL. in my case I have a vscode WebView and from there, I get user input and then I want to pass that input to the node REPL and get its output and show it to user.
So, how would I achieve that? If you need more information please tell me. thanks in advance.
EDIT 1:
const replServer = repl.start({
input: /* what should be here? */,
output: /* what should be here? */
});
Edit 2:
can anyone explain me what is the usage of input/output parameters in the above example?

Here is a solution that worked for me.
const {
PassThrough
} = require('stream')
const repl = require('repl')
const input = new PassThrough()
const output = new PassThrough()
output.setEncoding('utf-8')
const _repl = repl.start({
prompt: 'awesomeRepl> ',
input,
output
})
_repl.on('exit', function() {
// Do something when REPL exit
console.log('Exited REPL...')
})
function evaluate(code) {
let evaluatedCode = ''
output.on('data', (chunk) => {
evaluatedCode += chunk.toString()
console.log(evaluatedCode)
})
input.write(`${code}\n`)
return result
}
evaluate('2 + 2') // should return 4
Notice created the REPL instance outside the evaluate function so we don't create a new instance for every call of evaluate

To create a repl server you just need to do
const repl = require('repl')
repl.start({prompt: "> ", input: input_stream, output: output_stream");
prompt is a string that is the prompt, stream is the input. input_stream needs to be a readable stream, output_stream needs to be a writable one. you can read more about streams here. Once the streams are working you can do
output_stream.on('data', (chunk) => {
14 //whatever you do with the data
15 });

Related

How to read data from nodejs spawn as it is received

I have a small problem with a nodejs program. I'm trying to use the child_process module but the following code will only trigger the data event once I remove my card from a nfc card reader.
The problem is that the output I need is available before the card is removed.
For example if I put my card on the reader it will take half a second to print some lines inculding the card UID.
Then if I don't release the card, the program nfc-poll will still be working but won't output anything. As soon as I remove my card from the reader, it will output something and then close the buffer. This is when the event data is emitted.
What I'd like is to be able to read every byte as soon as possible to emit the card id as soon as possible.
function NFCReader() {
this.reader = new events.EventEmitter()
this.start_process()
}
NFCReader.prototype = {
start_process: function () {
this._process = cp.spawn('nfc-poll', [], {})
this._process.on('close', this.restart_process.bind(this))
//this._process.stdout.on('data', this.handle_data.bind(this))
this._process.stdout.readableFlowing = true
this._process.stdout.on('data', this.handle_data.bind(this))
this._process.stderr.on('data', this.handle_error.bind(this))
},
handle_data: function (data) {
var _data = data.toString()
var uid_lines = _data
.split('\n')
.filter(function (line) {return line.search('UID') >= 0})
if (uid_lines.length != 1) {
this.reader.emit('error', 'Multiple UID found')
return
}
var card_id = uid_lines[0]
.trim()
.split(':')[1].trim()
.replace(/[ ]+/g, ':')
this.reader.emit('card', card_id)
},
}
I tried to use pipe but it doesn't seem to help.
this is a runStream function from my release tools repo:
const stdin = input ? "pipe" : "ignore";
const out = new StringStream();
const child = execspawn(cmd, Object.assign(options, {
stdio: [stdin, "pipe", 2]
}));
child.on("error", e => out.emit("error", e));
if (input) {
input.pipe(child.stdin);
}
return child.stdout.pipe(out);
You can reproduce it or if you'd care to wait a couple days I could release it as a separate module. I guess there may already be something like that though...

Block for stdin in Node.js

Short explanation:
I'm attempting to write a simple game in Node.js that needs to wait for user input every turn. How do I avoid callback hell (e.g. messy code) internal to a turn loop where each turn loop iteration needs to block and wait for input from stdin?
Long explanation:
All the explanations I have read on StackOverflow when someone asks about blocking for stdin input seem to be "that's not what Node.js is about!"
I understand that Node.js is designed to be non-blocking and I also understand why. However I feel that it has me stuck between a rock and a hard place on how to solve this. I feel like I have three options:
Find a way to block for stdin and retain my while loop
Ditch the while loop and instead recursively call a method (like nextTurn) whenever the previous turn ends.
Ditch the while loop and instead use setTimeout(0, ...) or something similar to call a method (like nextTurn) whenever a turn ends.
With option (1) I am going against Node.js principles of non-blocking IO.
With option (2) I will eventually reach a stack overflow as each call adds another turn to the call stack.
With option (3) my code ends up being a mess to follow.
Internal to Node.js there are default functions that are marked **Sync (e.g. see the fs library or the sleep function) and I'm wondering why there is no Sync method for getting user input? And if I were to write something similar to fs.readSync how would I go about doing it and still follow best practices?
Just found this:
https://www.npmjs.com/package/readline-sync
Example code (after doing an npm install readline-sync)
var readlineSync = require('readline-sync');
while(true) {
var yn = readlineSync.question("Do you like having tools that let you code how you want, rather than how their authors wanted?");
if(yn === 'y') {
console.log("Hooray!");
} else {
console.log("Back to callback world, I guess...");
process.exit();
}
}
Only problem so far is the wailing of the "That's not how node is meant to be used!" chorus, but I have earplugs :)
I agree with the comment about moving towards an event based system and would ditch the loops. I've thrown together a quick example of text based processing which can be used for simple text games.
var fs = require('fs'),
es = require('event-stream');
process.stdin
.pipe(es.split())
.on('data', parseCommand);
var actionHandlers = {};
function parseCommand(command) {
var words = command.split(' '),
action = '';
if(words.length > 1) {
action = words.shift();
}
if(actionHandlers[action]) {
actionHandlers[action](words);
} else {
invalidAction(action);
}
}
function invalidAction(action) {
console.log('Unknown Action:', action);
}
actionHandlers['move'] = function(words) {
console.log('You move', words);
}
actionHandlers['attack'] = function(words) {
console.log('You attack', words);
}
You can now break up your actions into discrete functions which you can register with a central actionHandlers variable. This makes adding new commands almost trivial. If you can add some details on why the above approach wouldn't work well for you, let me know and I'll revise the answer.
ArtHare's solution, at least for my use case, blocks background execution, including those started by a promise. While this code isn't elegant, it did block execution of the current function, until the read from stdin completed.
While this code must run from inside an async function, keep in mind that running an async function from a top-level context (directly from a script, not contained within any other function) will block that function until it completes.
Below is a full .js script demonstrating usage, tested with node v8.12.0:
const readline = require('readline');
const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));
async function blockReadLine() {
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let result = undefined;
rl.on('line', function(line){
result = line;
})
while(!result) await sleep(100);
return result;
}
async function run() {
new Promise(async () => {
while(true) {
console.log("Won't be silenced! Won't be censored!");
await sleep(1000);
}
});
let result = await blockReadLine();
console.log("The result was:" + result);
process.exit(0);
}
run();

Cannot trigger 'end' event using CTRL D when reading from stdin

In the following code
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
process.stdout.write('data: ' + chunk);
});
process.stdin.on('end', function() {
process.stdout.write('end');
});
i can't trigger the 'end' event using ctrl+D, and ctrl+C just exit without triggering it.
hello
data: hello
data
data: data
foo
data: foo
^F
data: ♠
^N
data: ♫
^D
data: ♦
^D^D
data: ♦♦
I'd change this (key combo Ctrl+D):
process.stdin.on('end', function() {
process.stdout.write('end');
});
To this (key combo Ctrl+C):
process.on('SIGINT', function(){
process.stdout.write('\n end \n');
process.exit();
});
Further resources: process docs
I too came upon this problem and found the answer here: Github issue
The readline interface that is provided by windows itself (e.g. the one that you are using now) does not support ^D. If you want more unix-y behaviour, use the readline built-in module and set stdin to raw mode. This will make node interpret raw keypresses and ^D will work. See http://nodejs.org/api/readline.html.
If you are on Windows, the readline interface does not support ^D by default. You will need to change that per the linked instructions.
Alternatively
Use an input file containing your test data for e.g. input.txt
Pipe your input.txt to node
cat input.txt | node main.js
If you are doing it in context to Hackerrank codepair tool then this is for you.
The way tool works is that you have to enter some input in the Stdin section and then click on Run which will take you to stdout.
All the lines of input entered in the stdin will be processed by the process.stdin.on("data",function(){}) part of the code and as soon as the input "ends" it will go straight to the process.stdin.on("end", function(){}) part where we can do the processing and use process.stdout.write("") to output the result on the Stdout.
process.stdin.resume();
process.stdin.setEncoding("ascii");
var input = "";
process.stdin.on("data", function (chunk) {
// This is where we should take the inputs and make them ready.
input += (chunk+"\n");
// This function will stop running as soon as we are done with the input in the Stdin
});
process.stdin.on("end", function () {
// When we reach here, we are done with inputting things according to our wish.
// Now, we can do the processing on the input and create a result.
process.stdout.write(input);
});
You can check the flow by pasting he above code on the code window.
I've faced this too while debugging the code from Hackerrank using IntelliJ IDEA on Mac.
Need to say that without IDEA, executing exactly the same command in terminal - everything worked fine.
At first, I've found this: IntelliJ IDEA: send EOF symbol to Java application - and surprisingly, Cmd+D works fine and sends EOF.
And then, diving into IDEA settings, I've found "Other -> Send EOF", which was Cmd+D by default. After adding the second shortcut to this (Ctrl+D) - everything works as I've used to.
You can use redirectionoperator to feed the input from a file
$ node main.js < input.txt
[ only works in Unix-based machine like MacBook, Linux... ]
Alternatively, after typing the input into shell, you can press Ctrl + D to send EOF(end-of-file) to trigger the event handler in process.stdin.on("end", ...)
Ctrl+D(^D) is not supported in Microsoft Windows by default as mentioned by #Mark
you can also try something like this if you want to run programs using node on your windows pc, here i have triggered end using ctrl+d, hope it helps
'use strict';
const { METHODS } = require('http');
const readline = require('readline')
process.stdin.resume();
process.stdin.setEncoding('utf-8');
readline.emitKeypressEvents(process.stdin);
let inputString = '';
let currentLine = 0;
process.stdin.setRawMode(false)
process.stdin.on('data', inputStdin => {
inputString += inputStdin;
});
process.stdin.on('keypress', (str, key) => {
if (key && key.ctrl && key.name == 'd'){
inputString = inputString.trim().split('\n').map(string => {
return string.trim();
})
main();
}
});
function readLine() {
return inputString[currentLine++];
}
function method() {
}
function main() {
const n = parseInt(readLine().trim());
const arr = readLine().replace(/\s+$/g, '').split(' ').map(qTemp =>parseInt(qTemp,10))
method();
}

Node.js synchronous prompt

I'm using the prompt library for Node.js and I have this code:
var fs = require('fs'),
prompt = require('prompt'),
toCreate = toCreate.toLowerCase(),
stats = fs.lstatSync('./' + toCreate);
if(stats.isDirectory()){
prompt.start();
var property = {
name: 'yesno',
message: 'Directory esistente vuoi continuare lo stesso? (y/n)',
validator: /y[es]*|n[o]?/,
warning: 'Must respond yes or no',
default: 'no'
};
prompt.get(property, function(err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
}
});
}
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
If the prompt is show it seems that it doesn't block code execution but the execution continues and the last two messages by the console are shown while I still have to answer the question.
Is there a way to make it blocking?
With flatiron's prompt library, unfortunately, there is no way to have the code blocking. However, I might suggest my own sync-prompt library. Like the name implies, it allows you to synchronously prompt users for input.
With it, you'd simply issue a function call, and get back the user's command line input:
var prompt = require('sync-prompt').prompt;
var name = prompt('What is your name? ');
// User enters "Mike".
console.log('Hello, ' + name + '!');
// -> Hello, Mike!
var hidden = true;
var password = prompt('Password: ', hidden);
// User enters a password, but nothing will be written to the screen.
So give it a try, if you'd like.
Bear in mind: DO NOT use this on web applications. It should only be used on command line applications.
Update: DO NOT USE THIS LIBRARY AT ALL. IT IS A TOTAL JOKE, TO BE PERFECTLY FRANK.
Since Node.js 8, you can do the following using async/await:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function readLineAsync(message) {
return new Promise((resolve, reject) => {
rl.question(message, (answer) => {
resolve(answer);
});
});
}
// Leverages Node.js' awesome async/await functionality
async function demoSynchronousPrompt() {
var promptInput = await readLineAsync("Give me some input >");
console.log("Won't be executed until promptInput is received", promptInput);
rl.close();
}
Since IO in Node doesn't block, you're not going to find an easy way to make something like this synchronous. Instead, you should move the code into the callback:
...
prompt.get(property, function (err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
}
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
});
or else extract it and call the extracted function:
...
prompt.get(property, function (err, result) {
if(result === 'no'){
console.log('Annullato!');
process.exit(0);
} else {
doCreate();
}
});
...
function doCreate() {
console.log("creating ", toCreate);
console.log('\nAll done, exiting'.green.inverse);
}
Old question, I know, but I just found the perfect tool for this. readline-sync gives you a synchronous way to collect user input in a node script.
It's dead simple to use and it doesn't require any dependencies (I couldn't use sync-prompt because of gyp issues).
From the github readme:
var readlineSync = require('readline-sync');
// Wait for user's response.
var userName = readlineSync.question('May I have your name? ');
console.log('Hi ' + userName + '!');
I'm not affiliated with the project in any way, but it just made my day, so I had to share.
I've come across this thread and all the solutions either:
Don't actually provide a syncronous prompt solution
Are outdated and don't work with new versions of node.
And for that reason I have created syncprompt
. Install it with npm i --save syncprompt and then just add:
var prompt = require('syncprompt');
For example, this will allow you to do:
var name = prompt("Please enter your name? ");
It also supports prompting for passwords:
var topSecretPassword = prompt("Please enter password: ", true);
Vorpal.js is a library I made that has just recently been released. It provides synchronous command execution with an interactive prompt, like you are asking. The below code will do what you are asking:
var vorpal = require('vorpal')();
vorpal.command('do sync')
.action(function (args) {
return 'i have done sync';
});
With the above, the prompt will come back after a second is up (only after callback is called).
This is dependency free, synchronous and works on Windows, Linux and OSX:
// Synchronously prompt for input
function prompt(message)
{
// Write message
process.stdout.write(message);
// Work out shell command to prompt for a string and echo it to stdout
let cmd;
let args;
if (os.platform() == "win32")
{
cmd = 'cmd';
args = [ '/V:ON', '/C', 'set /p response= && echo !response!' ];
}
else
{
cmd = 'bash';
args = [ '-c', 'read response; echo "$response"' ];
}
// Pipe stdout back to self so we can read the echoed value
let opts = {
stdio: [ 'inherit', 'pipe', 'inherit' ],
shell: false,
};
// Run it
return child_process.spawnSync(cmd, args, opts).stdout.toString().trim();
}
const buffer = Buffer.alloc(1024);
require("fs").readSync(process.stdin.fd, buffer);
console.log(buffer.toString());
You can use prompt-sync
const prompt = require('prompt-sync')()
const ans = prompt('How many more times? ') // get input from the user.
P.S. prompt-sync acts weird, if prompt message contains new line character, so if you need multiline prompt just use console.log():
const prompt = require('prompt-sync')()
console.log('How many more times?\n')
const ans = prompt('') // get input from the user.

Parse output of spawned node.js child process line by line

I have a PhantomJS/CasperJS script which I'm running from within a node.js script using process.spawn(). Since CasperJS doesn't support require()ing modules, I'm trying to print commands from CasperJS to stdout and then read them in from my node.js script using spawn.stdout.on('data', function(data) {}); in order to do things like add objects to redis/mongoose (convoluted, yes, but seems more straightforward than setting up a web service for this...) The CasperJS script executes a series of commands and creates, say, 20 screenshots which need to be added to my database.
However, I can't figure out how to break the data variable (a Buffer?) into lines... I've tried converting it to a string and then doing a replace, I've tried doing spawn.stdout.setEncoding('utf8'); but nothing seems to work...
Here is what I have right now
var spawn = require('child_process').spawn;
var bin = "casperjs"
//googlelinks.js is the example given at http://casperjs.org/#quickstart
var args = ['scripts/googlelinks.js'];
var cspr = spawn(bin, args);
//cspr.stdout.setEncoding('utf8');
cspr.stdout.on('data', function (data) {
var buff = new Buffer(data);
console.log("foo: " + buff.toString('utf8'));
});
cspr.stderr.on('data', function (data) {
data += '';
console.log(data.replace("\n", "\nstderr: "));
});
cspr.on('exit', function (code) {
console.log('child process exited with code ' + code);
process.exit(code);
});
https://gist.github.com/2131204
Try this:
cspr.stdout.setEncoding('utf8');
cspr.stdout.on('data', function(data) {
var str = data.toString(), lines = str.split(/(\r?\n)/g);
for (var i=0; i<lines.length; i++) {
// Process the line, noting it might be incomplete.
}
});
Note that the "data" event might not necessarily break evenly between lines of output, so a single line might span multiple data events.
I've actually written a Node library for exactly this purpose, it's called stream-splitter and you can find it on Github: samcday/stream-splitter.
The library provides a special Stream you can pipe your casper stdout into, along with a delimiter (in your case, \n), and it will emit neat token events, one for each line it has split out from the input Stream. The internal implementation for this is very simple, and delegates most of the magic to substack/node-buffers which means there's no unnecessary Buffer allocations/copies.
I found a nicer way to do this with just pure node, which seems to work well:
const childProcess = require('child_process');
const readline = require('readline');
const cspr = childProcess.spawn(bin, args);
const rl = readline.createInterface({ input: cspr.stdout });
rl.on('line', line => /* handle line here */)
Adding to maerics' answer, which does not deal properly with cases where only part of a line is fed in a data dump (theirs will give you the first part and the second part of the line individually, as two separate lines.)
var _breakOffFirstLine = /\r?\n/
function filterStdoutDataDumpsToTextLines(callback){ //returns a function that takes chunks of stdin data, aggregates it, and passes lines one by one through to callback, all as soon as it gets them.
var acc = ''
return function(data){
var splitted = data.toString().split(_breakOffFirstLine)
var inTactLines = splitted.slice(0, splitted.length-1)
var inTactLines[0] = acc+inTactLines[0] //if there was a partial, unended line in the previous dump, it is completed by the first section.
acc = splitted[splitted.length-1] //if there is a partial, unended line in this dump, store it to be completed by the next (we assume there will be a terminating newline at some point. This is, generally, a safe assumption.)
for(var i=0; i<inTactLines.length; ++i){
callback(inTactLines[i])
}
}
}
usage:
process.stdout.on('data', filterStdoutDataDumpsToTextLines(function(line){
//each time this inner function is called, you will be getting a single, complete line of the stdout ^^
}) )
You can give this a try. It will ignore any empty lines or empty new line breaks.
cspr.stdout.on('data', (data) => {
data = data.toString().split(/(\r?\n)/g);
data.forEach((item, index) => {
if (data[index] !== '\n' && data[index] !== '') {
console.log(data[index]);
}
});
});
Old stuff but still useful...
I have made a custom stream Transform subclass for this purpose.
See https://stackoverflow.com/a/59400367/4861714
#nyctef's answer uses an official nodejs package.
Here is a link to the documentation: https://nodejs.org/api/readline.html
The node:readline module provides an interface for reading data from a Readable stream (such as process.stdin) one line at a time.
My personal use-case is parsing json output from the "docker watch" command created in a spawned child_process.
const dockerWatchProcess = spawn(...)
...
const rl = readline.createInterface({
input: dockerWatchProcess.stdout,
output: null,
});
rl.on('line', (log: string) => {
console.log('dockerWatchProcess event::', log);
// code to process a change to a docker event
...
});

Resources