Reading user input is a big aspect of this project because we will be asking for a question or an answer for the CLI to work with.
Node has some internal options for parsing user input. We will check out the readline
module and how it parses input. We will also see the promise driven version of the module and use a top level await to ask questions in the CLI.
This package has some limitations in how you present questions to your user though so if presentation is important to you, you will want to consider using a 3rd party library.
Kevin Cunningham: [0:00] For this project, the ability to be able to ask a user a question and respond to that question is going to be super important. We can do that with using Node internals or by leaning on a third-party library. Let's look at Node internals first. [0:14] Node internals has a library called readline. I can import it like that, import readline from 'readline', or I can prefix it with node: . This is true for any of the Node internal libraries, a mixture that you're always going to get, the Node library and not another package that was name-squatting. Let's look at two ways to be able to deal with this asking a question.
[0:40] For both of these approaches, we'll create an interface. An interface is an example of a duplex stream, one that has both an input and an output. We want to parse in a few options. We want to tell our interface that we are using a terminal, so we'll set that to true. We want to tell our interface that the input we're expecting is going to be from stdin.
[1:00] We want to tell our interface that the output is in stdout. Then, we'll ask our question. We set a variable to capture the input. This interface has an EventListener for the input. I'm going to listen to the input for every key pressed. That gives me an event.
[1:14] It also gives me some more data, a readline data. Let's console.log into those and see what we get. What is your name? It's waiting. If I type in K, I get K because of the stdout. I get K because that's the event. I get this object because that's rl. That's what I'm getting from the readline interface.
[1:33] I'm getting the sequence, the name of the character, whether or not Ctrl was held on, whether or not the method key was held on, whether or not the Shift was held on. I hold on Shift, then K, Shift is true, if I don't, then Shift is false. If I hold on Ctrl and then K, the sequence I get is \x0b, which is what Ctrl-K looks like, true and K.
[1:57] We don't want to just console.log these. We want to check whether or not the return key has been pressed. I pressed the return. We can see it's got a name of return. I want to check if rl.name === "return". Then, I want to console.log("Your name is"), and then that input that we've been collecting. Then, maybe we'll have the next question. "Where do you live?"
[2:20] If it's not returned, all I want to do is to append to that input whatever the event was. Let's try it now. What's your name? Kevin. Your name is Kevin. Where do you live? We can add custom logic to ask the next question, clear out the input collected, and to keep going.
[2:38] If, like me, you're using Node 18, which is the current LTS -- actually, if you're using Node 17, this is true for you as well -- you can use the promises version of readline. To do that, all I need to do is to rename this import from readline to readline/promises. We create the interface in the same way but now we can await the only answer.
[2:59] Instead of having to do with any of this, and instead of logging the question, we can instead get the answer and with readline, a question, "What is your name?" Because we're in module mode, in the SM mode, that we've got top-level await, we do need to wrap that the function.
[3:17] We can now console.log and say, "Your name is." Then we're finished, we can call close(), so we're finished for interface. Let's try that. What's your name? Kevin. Your name is Kevin. We don't call close. We're still waiting for some input. We could have a second question here.
[3:39] We could have, answer2 = await rl.question("Where do you live?"). Then console.log( "You live in ${answer2}." ). Try that. What's your name? Kevin. Where do you live? Northern Ireland. I live in Northern Ireland. Then I need to call rl.close, when I'm finished asking and answering this question. What's your name? Kevin. Where do you live? Northern Ireland.
[4:01] We get quite far here just by using readline. Lots of functionality that we could see we could build here, but there are things like using color on the command line, having multiple choice questions. Lots of different ways we could imagine user input being passed in on the command line.
[4:19] This method, while functional, isn't this beautiful for our user. This might be the point where you think, is there a third-party package that can do this work for me? I want to focus on the core functionality of my tool and dealing with multiple terminals and operating system, field beyond the scope of what I'm doing.