Skip to content

Instantly share code, notes, and snippets.

@DTrejo
Created April 4, 2011 03:32
Show Gist options
  • Save DTrejo/901104 to your computer and use it in GitHub Desktop.
Save DTrejo/901104 to your computer and use it in GitHub Desktop.
How to Readline - an example and the beginnings of the docs
node_modules/

Readline

To use this module, do require('readline'). Readline allows reading of a stream (such as STDIN) on a line-by-line basis.

Note that once you've invoked this module, your node program will not terminate until you've closed the interface, and the STDIN stream. Here's how to allow your program to gracefully terminate:

var rl = require('readline');

var i = rl.createInterface(process.sdtin, process.stdout, null);
i.question("What do you think of node.js?", function(answer) {
  // TODO: Log the answer in a database
  console.log("Thank you for your valuable feedback.");

  // These two lines together allow the program to terminate. Without
  // them, it would run forever.
  i.close();
  process.stdin.destroy();
});

rl.createInterface(input, output, completer)

Takes two streams and creates a readline interface. The completer function is used for autocompletion. When given a substring, it returns [[substr1, substr2, ...], originalsubstring].

createInterface is commonly used with process.stdin and process.stdout in order to accept user input:

var readline = require('readline'),
  rl = readline.createInterface(process.stdin, process.stdout);

rl.setPrompt(prompt, length)

Sets the prompt, for example when you run node on the command line, you see > , which is node's prompt.

rl.prompt()

Readies readline for input from the user, putting the current setPrompt options on a new line, giving the user a new spot to write.

rl.question(query, callback)

Prepends the prompt with query and invokes callback with the user's response. Displays the query to the user, and then invokes callback with the user's response after it has been typed.

Example usage:

interface.question('What is your favorite food?', function(answer) {
  console.log('Oh, so your favorite food is ' + answer);
});

rl.close()

Closes tty.

rl.pause()

Pauses tty.

rl.resume()

Resumes tty.

rl.write()

Writes to tty.

Event: 'line'

function (line) {}

Emitted whenever the in stream receives a \n, usually received when the user hits enter, or return. This is a good hook to listen for user input.

Example of listening for line:

rl.on('line', function (cmd) {
  console.log('You just typed: '+cmd);
});

Event: 'close'

function () {}

Emitted whenever the in stream receives a ^C or ^D, respectively known as SIGINT and EOT. This is a good way to know the user is finished using your program.

Example of listening for close, and exiting the program afterward:

rl.on('close', function() {
  console.log('goodbye!');
  process.exit(0);
});

Here's an example of how to use all these together to craft a tiny command line interface:

var readline = require('readline'),
  rl = readline.createInterface(process.stdin, process.stdout),
  prefix = 'OHAI> ';

rl.on('line', function(line) {
  switch(line.trim()) {
    case 'hello':
      console.log('world!');
      break;
    default:
      console.log('Say what? I might have heard `' + line.trim() + '`');
      break;
  }
  rl.setPrompt(prefix, prefix.length);
  rl.prompt();
}).on('close', function() {
  console.log('Have a great day!');
  process.exit(0);
});
console.log(prefix + 'Good to see you. Try typing stuff.');
rl.setPrompt(prefix, prefix.length);
rl.prompt();

Take a look at this slightly more complicated example, and http-console for a real-life use case.

var readline = require('readline'),
rl = readline.createInterface(process.stdin, process.stdout),
prefix = 'OHAI> ';
rl.on('line', function(line) {
switch(line.trim()) {
case 'hello':
console.log('world!');
break;
default:
console.log('Say what? I might have heard `' + line.trim() + '`');
break;
}
rl.setPrompt(prefix, prefix.length);
rl.prompt();
}).on('close', function() {
console.log('Have a great day!');
process.exit(0);
});
console.log(prefix + 'Good to see you. Try typing stuff.');
rl.setPrompt(prefix, prefix.length);
rl.prompt();
//
// Based on github.com/cloudhead/http-console
// An attempt at a simplified readline example.
//
var readline = require('readline')
, util = require('util')
, colors = require('colors') // npm install colors
, rl = readline.createInterface(process.stdin, process.stdout, completer)
, help = [ '.help ' + 'display this message.'.grey
, '.error ' + 'display an example error'.grey
, '.q[uit] ' + 'exit console.'.grey
].join('\n')
;
// This should work now, thanks to @josher19
function completer(line) {
var completions = '.help .error .exit .quit .q'.split(' ')
var hits = completions.filter(function(c) {
if (c.indexOf(line) == 0) {
// console.log('bang! ' + c);
return c;
}
});
return [hits && hits.length ? hits : completions, line];
}
function welcome() {
util.puts([ "= readline-demo "
, "= Welcome, enter .help if you're lost."
, "= Try counting from 1 to 5!"
].join('\n').grey);
prompt();
}
function prompt() {
var arrow = '> '
, length = arrow.length
;
rl.setPrompt(arrow.grey, length);
rl.prompt();
}
var state = 1;
function exec(command) {
var num = parseInt(command, 10);
if (1 <= num && num <= 5) {
if (state === num) {
state++;
console.log('WIN'.green);
} else {
console.log(('Try entering a different number, like '
+ state + ' for example').red);
}
if (state === 6) {
console.log('WOW YOU ROCKS A LOT!'.rainbow);
process.exit(0);
}
} else if (command[0] === '.') {
switch (command.slice(1)) {
case 'help':
util.puts(help.yellow);
break;
case 'error':
console.log("Here's what an error might look like");
JSON.parse('{ a: "bad JSON" }');
break;
case 'exit':
case 'quit':
case 'q':
process.exit(0);
break;
}
} else {
// only print if they typed something
if (command !== '') {
console.log(('\'' + command
+ '\' is not a command dude, sorryz').yellow);
}
}
prompt();
}
//
// Set things up
//
rl.on('line', function(cmd) {
exec(cmd.trim());
}).on('close', function() {
// only gets triggered by ^C or ^D
util.puts('goodbye!'.green);
process.exit(0);
});
process.on('uncaughtException', function(e) {
util.puts(e.stack.red);
rl.prompt();
});
welcome();
// Helpful thing I didn't get around to using:
// Make sure the buffer is flushed before
// we display the prompt.
function flush(callback) {
if (process.stdout.write('')) {
callback();
} else {
process.stdout.once('drain', function() {
callback();
});
}
};
@dresende
Copy link

dresende commented Apr 5, 2011

The 3rd argument to readline.createInterface is completer. This should be a function that accepts the current line (from start to cursor) and should return an array where index 0 is another array with the possible completions and index 1 is the current line (I think..).

function completer(line) {
  return [[
    line + "complete1",
    line + "complete2"
  ], line ];
}

@DTrejo
Copy link
Author

DTrejo commented Apr 7, 2011

Thanks dresende, I'll be using this as a starting point as well as the source to finish up this section of the docs. Cheers!

@shinout
Copy link

shinout commented Oct 20, 2011

Hi, is there any way to hide input characters like putting passwords in UNIX?

@dresende
Copy link

http://tjholowaychuk.com/post/9103188408/commander-js-nodejs-command-line-interfaces-made-easy

You have an option to ask for a password, check the code :)

Check tty.setRawMode.

@shinout
Copy link

shinout commented Oct 20, 2011

Thx dresende, here I found process.stdin.on("keypress"). https://github.com/visionmedia/commander.js/blob/master/lib/commander.js#L772
And also I'll try this awesome library.

@josher19
Copy link

josher19 commented Apr 9, 2012

Here is a fixed completer:

// fixed to use filter instead of map
function completer(line) {
  var completions = '.help .error .exit .quit .q'.split(' ')
  var hits = completions.filter(function(c) {
    if (c.indexOf(line) == 0) {
      // console.log('bang! ' + c);
      return c;
    }
  });
  return [hits && hits.length ? hits : completions, line];
}

@DTrejo
Copy link
Author

DTrejo commented Apr 10, 2012 via email

@josher19
Copy link

Looks like it was removed from the node docs.

http://nodejs.org/api/readline.html (for v0.6.14) has a link pointing to this gist.
Current node master branch only mentions completer briefly. Shall I insert this as an example under the text:

...

Which ends up looking something like: [[substr1, substr2, ...], originalsubstring].

Example:

function completer(line) {
  var completions = '.help .error .exit .quit .q'.split(' ')
  var hits = completions.filter(function(c) {
    if (c.indexOf(line) == 0) {
      // console.log('Found: ' + c);
      return c;
    }
  });
  // show all completions if none found
  return [hits.length ? hits : completions, line];
}

@DTrejo
Copy link
Author

DTrejo commented Apr 10, 2012 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment