Nodejs multiple inputs from console - mix of masked and unmasked outputs - node.js

I am trying to create a small nodejs utility to produce and consume kafka messages using kafkajs. I want to get the kafka user and password input from the console. I tried different combinations to use the stdoutMuted to true and false at various places, but it doesn't work correctly. If it is set to true before the question is asked then I either get undefined or the question doesn't show up
Using some examples on SO, similar to this and this, I tried to create something like this
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl._writeToOutput = stringToWrite => {
if (rl.stdoutMuted)
rl.output.write("*");
else
rl.output.write(stringToWrite);
};
rl.stdoutMuted = true;
rl.question('Password: ', password => {
rl.stdoutMuted = false;
console.log('\nPassword is ' + password);
rl.close();
});
This gives below
* *******
Password is mypass
OR
'use strict'
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const question1 = () => {
return new Promise((resolve, reject) => {
rl.stdoutMuted = false;
rl.question('Userid : ', (answer) => {
console.log(`Thank you for your valuable feedback: ${answer}`)
resolve()
})
})
}
const question2 = () => {
return new Promise((resolve, reject) => {
rl.stdoutMuted = true;
rl.question('Password : ', (answer) => {
console.log(`Thank you for your valuable feedback: ${answer}`)
resolve()
})
})
}
rl._writeToOutput = function _writeToOutput(stringToWrite) {
if (rl.stdoutMuted)
rl.output.write("\x1B[2K\x1B[200D"+rl.query+"["+((rl.line.length%2==1)?"=-":"-=")+"]");
else
rl.output.write(stringToWrite);
};
const main = async () => {
await question1()
await question2()
rl.close()
}
main()
Gives the below
UserId user
Thank you for your valuable feedback: user
undefined[-=]Thank you for your valuable feedback: password
How can I setup the input such that I can request multiple inputs where some of them will be masked and others plaintext, but all the questions / prompts are visible.
Also, how do I set those as variables that can be used in other parts of the application to connect to Kafka.
Thank you

Related

how to test and mocking nodejs readline with jest?

I'm currently working on a method for a simple console game. And I want to test this method through jest mocking. But I'm having a hard time. Any help would be appreciated.
index.js
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
class App {
play() {
rl.question("Input Numbers ", (userInput) => {
const userNumbers = userInput.split("").map(Number);
if (userNumbers.length !== 3) {
throw new Error("[ERROR] You have to input 3 numbers");
return;
}
});
}
}
module.exports = App;
I tried in index.test.js with jest
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const App = require("../src/App");
test("throws an error if user input 4 numbers", () => {
const mockQuestion = jest.fn().mockImplementation((question, callback) => {
callback("1234");
});
rl.question = mockQuestion;
const app = new App();
expect(() => {
app.play();
}).toThrow("[ERROR] You have to input 3 numbers");
});
But I got this fail message
Expected substring: "[ERROR] You have to input 3 numbers"
Received function did not throw
enter image description here

How to read variable number of lines from the terminal?

I need to ask how many inputs the user want to give (one input per line) and read them.
I am trying to keep it as simple as possible without using any libraries or even async. Why? Because I am doing a course on Coursera and I have to upload a JavaScript/Node file as assignment. The one I wrote with async and promise got rejected (for that version look at the end of this post). Somehow input and output is not working with the "Coursera system".
So I am trying to do it without any bells and whistles:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
terminal: false
});
const r2 = readline.createInterface({
input: process.stdin,
terminal: false
});
let x = null;
let y = [];
rl.on('line', line1 => {
if (line1 !== "\n") {
x = parseInt(line1);
while(x > 0){
r2.on('line', line2 => {
y.push(line2);
});
x -= 1;
}
console.log('You entered these colors:', y);
process.exit();
}
});
Output:
3
You entered these colors: []
Expected output:
[input]3
[input]green
[input]blue
[input]red
[output]You entered these colors: ["green", "blue", "red"]
One that worked but rejected buy Coursera "assignment system":
const readline = require('readline');
const readLineAsync = () => {
const rl = readline.createInterface({
input: process.stdin,
terminal: false
});
return new Promise((resolve) => {
rl.prompt();
rl.on('line', (line) => {
rl.close();
resolve(line);
});
});
};
async function start() {
let line = null;
let colors = [];
line = await readLineAsync();
const numColors = parseInt(line);
for(let count = numColors; count > 0; count--) {
line = await readLineAsync();
colors.push(line);
}
console.log('You entered these colors:', colors);
}
start();
Initially, we want to ask how many colors the user intends to enter. We are sure the input is expected only once so r1.once() is used.
But, the number of times we ask the user for input varies according to the previous input. So we use r1.on() which will not close until terminated. We will use a variable, counter, to ensure the input is asked of the user the required number of times.
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
terminal: false
});
process.stdin.setEncoding('utf8');
rl.once('line', line => {
const count = parseInt(line.toString());
const colors = [];
let counter = 0;
rl.on('line', line => {
const color = readLine(line);
colors.push(color);
if(++counter >= count) {
console.log('You have entered these colros:', colors);
process.exit();
}
});
});
function readLine(line) {
return line.toString();
}
output
3
red
green
blue
You have entered these colros: [ 'red', 'green', 'blue' ]

Node.js readline property doesn't ask for input

Node.js readline property doesn't stop for input, instead continues program, causing app to crash. While trying to solve this I found out that apparently node does the whole code simultaneously and doesn't because of that stop for input. I found out ways to run this code but they didn't work for me.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
var token;
var pass;
rl.question('token: ', (tok) => {
token = tok;
rl.close();
});
rl.question('pass: ', (pas) => {
pass = pas;
rl.close();
});
What can I do to solve this?
I hope you are looking something like below:
const readLine = require('readline');
const util = require('util')
const { promisify } = util;
const readline = readLine.createInterface({
input: process.stdin,
output: process.stdout,
});
// Prepare readline.question for promisification
readline.question[util.promisify.custom] = (question) => {
return new Promise((resolve) => {
readline.question(question, resolve);
});
};
let questionPs = promisify(readline.question);
async function askQuestions (questions,readline) {
let answers= [];
for(let i=0;i<2;i++){
let tmp = await questionPs(q[i]);
answers.push(tmp)
}
console.log(answers);
readline.close();
}
// Usage example:
let q = ['token:','pass:']
askQuestions(q,readline)

How to get synchronous readline, or "simulate" it using async, in nodejs?

I am wondering if there is a simple way to get "synchronous" readline or at least get the appearance of synchronous I/O in node.js
I use something like this but it is quite awkward
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
var i = 0;
var s1 = '';
var s2 = '';
rl.on('line', function(line){
if(i==0) { s1 = line; }
else if(i==1) { s2 = line; }
i++;
})
rl.on('close', function() {
//do something with lines
})'
Instead of this I would prefer if it were as easy as something like
var s1 = getline(); // or "await getline()?"
var s2 = getline(); // or "await getline()?"
Helpful conditions:
(a) Prefer not using external modules or /dev/stdio filehandle, I am submitting code to a code submission website and these do not work there
(b) Can use async/await or generators
(c) Should be line based
(d) Should not require reading entire stdin into memory before processing
Just in case someone stumbles upon here in future
Node 11.7 added support for this using async await
const readline = require('readline');
//const fileStream = fs.createReadStream('input.txt');
const rl = readline.createInterface({
input: process.stdin, //or fileStream
output: process.stdout
});
for await (const line of rl) {
console.log(line)
}
Remember to wrap it in async function(){} otherwise you will get a reserved_keyword_error.
const start = async () =>{
for await (const line of rl) {
console.log(line)
}
}
start()
To read an individual line, you can use the async iterator manually
const it = rl[Symbol.asyncIterator]();
const line1 = await it.next();
You can just wrap it in a promise -
const answer = await new Promise(resolve => {
rl.question("What is your name? ", resolve)
})
console.log(answer)
Like readline module, there is another module called readline-sync, which takes synchronous input.
Example:
const reader = require("readline-sync"); //npm install readline-sync
let username = reader.question("Username: ");
const password = reader.question("Password: ",{ hideEchoBack: true });
if (username == "admin" && password == "foobar") {
console.log("Welcome!")
}
I think this is what you want :
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin , output: process.stdout });
const getLine = (function () {
const getLineGen = (async function* () {
for await (const line of rl) {
yield line;
}
})();
return async () => ((await getLineGen.next()).value);
})();
const main = async () => {
let a = Number(await getLine());
let b = Number(await getLine());
console.log(a+b);
process.exit(0);
};
main();
Note: this answer use experimental features and need Node v11.7
Try this. It's still not a perfect replication of a synchronous line reading function -- e.g. async functions still happen later, so some of your calling code may execute out of order, and you can't call it from inside a normal for loop -- but it's a lot easier to read than the typical .on or .question code.
// standard 'readline' boilerplate
const readline = require('readline');
const readlineInterface = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// new function that promises to ask a question and
// resolve to its answer
function ask(questionText) {
return new Promise((resolve, reject) => {
readlineInterface.question(questionText, (input) => resolve(input) );
});
}
// launch your program since `await` only works inside `async` functions
start()
// use promise-based `ask` function to ask several questions
// in a row and assign each answer to a variable
async function start() {
console.log()
let name = await ask("what is your name? ")
let quest = await ask("what is your quest? ")
let color = await ask("what is your favorite color? ")
console.log("Hello " + name + "! " +
"Good luck with " + quest +
"and here is a " + color + " flower for you.");
process.exit()
}
UPDATE: https://www.npmjs.com/package/readline-promise implements it (source code here: https://github.com/bhoriuchi/readline-promise/blob/master/src/index.js#L192 ). It implements several other features as well, but they seem useful too, and not too overengineered, unlike some other NPM packages that purport to do the same thing. Unfortunately, I can't get it to work due to https://github.com/bhoriuchi/readline-promise/issues/5 but I like its implementation of the central function:
function ask(questionText) {
return new Promise((resolve, reject) => {
readlineInterface.question(questionText, resolve);
});
}
Using generators your example would look like this:
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
var i = 0;
var s1 = '';
var s2 = '';
var iter=(function* () {
s1 = yield;
i++;
s2 = yield;
i++;
while (true) {
yield;
i++;
}
})(); iter.next();
rl.on('line', line=>iter.next(line))
rl.on('close', function() {
//do something with lines
})
So yield here acts as if it were a blocking getline() and you can handle lines in the usual sequential fashion.
UPD:
And an async/await version might look like the following:
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
var i = 0;
var s1 = '';
var s2 = '';
var continuation;
var getline = (() => {
var thenable = {
then: resolve => {
continuation = resolve;
}
};
return ()=>thenable;
})();
(async function() {
s1 = await getline();
i++;
s2 = await getline();
i++;
while (true) {
await getline();
i++;
}
})();
rl.on('line', line=>continuation(line))
rl.on('close', function() {
//do something with lines
})
In both of these "synchronous" versions, i is not used for distinguishing lines and only useful for counting the total number of them.
Here's an example but it requires reading entire stdin before giving results however which is not ideal
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
function lineiterator() {
var currLine = 0;
var lines = [];
return new Promise(function(resolve, reject) {
rl.on('line', function (line){
lines.push(line)
})
rl.on('close', function () {
resolve({
next: function() {
return currLine < lines.length ? lines[currLine++]: null;
}
});
})
})
}
Example
lineiterator().then(function(x) {
console.log(x.next())
console.log(x.next())
})
$ echo test$\ntest | node test.js
test
test
The simplest (and preferred) option is available in the docs. https://nodejs.org/api/readline.html#rlquestionquery-options-callback
const util = require('util');
const question = util.promisify(rl.question).bind(rl);
async function questionExample() {
try {
const answer = await question('What is you favorite food? ');
console.log(`Oh, so your favorite food is ${answer}`);
} catch (err) {
console.error('Question rejected', err);
}
}
questionExample();
Since I don't know how many strings you need I put them all in an Array
Don't hesitate to comment if you need a more detailed answer or if my answer is not exact :
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
var i = 0;
var strings = [];
rl.on('line', function(line) {
// 2 lines below are in case you want to stop the interface after 10 lines
// if (i == 9)
// rl.close()
strings[i] = line
i++
}).on('close', function() {
console.log(strings)
})
// this is in case you want to stop the program when you type ctrl + C
process.on('SIGINT', function() {
rl.close()
})
We can use promise and process.stdin events together to simulate a synchronous input system
const { EOL } = require("os");
const getLine = async () =>
(
await new Promise((resolve) => {
process.stdin.on("data", (line) => {
resolve("" + line);
});
})
).split(EOL)[0];
const line = await getLine();
console.log(line);

How to read line by line and detect eof (end of file)?

Followup to a solution to reading a file line by line, as described here: https://stackoverflow.com/a/16013228/570796
var fs = require('fs'),
readline = require('readline'),
instream = fs.createReadStream('/path/to/file');
var rl = readline.createInterface(
{
input: instream,
terminal: false
});
rl.on('line', function(line)
{
console.log(line);
// if(instream.isEnd()) ...
});
How do I detect if I reached the end of the file?
I understand that there is an event on the ReadStream on('end', () => {/*...*/}) But I need a solution where I can check it through an if statement.
Here's a solution:
let ended = false;
instream.on('end', () => { ended = true });
rl.on('line', function(line) {
if (ended) {
//...
However, there's a reasonable chance you don't actually need this, and your application could be structured differently.
I'm not sure whether the line event can even happen after the end event.
Turns out my suspicions were true, so you need to do it this way around.
let lastLine;
rl.on('line', line => { lastLine = line })
instream.on('end', () => {
assert.notStrictEqual(lastLine, undefined, 'There were no lines!');
// ...
});
You can also use the close event.
const fs = require("fs");
const readline = require('readline');
const readInterface = readline.createInterface({
input: fs.createReadStream("path/to/file.txt"),
output: process.stdout,
terminal: false,
})
rl.on("line", function(line){
console.log(line);
}).on("close", function() {
console.log("EOF");
})
The close event will run when the file has no more data left to be read from.
Another (elegant), approach could be implementing a Promise.
You could furthermore add a reject("Error while reading File"), linked to input.on('error') while reading. But it's not 100% required for your problem.
var fs = require('fs');
var input = require('fs').createReadStream('./inputFile.txt')
var promise = new Promise(function(resolve, reject) {
var lineReader = require('readline').createInterface({
input: input
});
input.on('end', () => {
resolve("I reached the end!");
});
lineReader.on('line', (line) => {
// DO STH. WITH EACH LINE (IF DESIRED)
});
});
promise.then((resolveResult) => {
// DO WHATEVER YOU WANT TO DO AFTER
}
For more information about promises, I'd check the following introduction:
https://developers.google.com/web/fundamentals/primers/promises

Resources