Unix and Node: Interfaces

2012-04-05 15:00

Unix and Node: Interfaces

by

at 2012-04-05 07:00:00

original http://feedproxy.google.com/~r/dailyjs/~3/8DQwxBbqODw/node-unix-interfaces

Earlier in this series I covered command-line arguments, which are well-supported in Node. There are times when a more interactive interface is required, however.

Fortunately, various node modules give us the tools to create command-line Unix programs with many different console-based interfaces, from Read-Eval-Print-Loops to GUI-like terminal control libraries.

REPL

Node’s Read-Eval-Print-Loop (REPL) is available as a module, and can be used to create interactive JavaScript shells. Node’s documentation has a cool example that uses a TCP server, so clients can connect with telnet.

The documentation is currently slightly inaccurate with regard to REPL.start – the callback method actually takes four arguments and won’t work as advertised. This example should work with the current version of Node 0.6:

var repl = require('repl')
  , vm = require('vm');

repl.start('> ', process, function(code, context, file, callback) {
  var result
    , err;

  try {
    result = vm.runInThisContext(code, file);
  } catch (err) {
    console.error('Error:', err);
  }
  callback(err, result);
});

The process global is passed as the stream argument to make the REPL read and write to stdin and stdout. The callback method can do anything that’s required. For example, you could allow access to your database objects and methods in a web application, or provide an interactive administration interface to a daemon.

Readline

The repl module works well when a JavaScript shell is required, but what about a custom REPL? Node actually includes a Readline module, which is perfect for this:

var readline = require('readline')
  , rl;

rl = readline.createInterface(process.stdin, process.stdout, null);

rl.setPrompt('➜');

rl.on('line', function(cmd) {
  if (cmd === 'quit') {
    rl.question('Are you sure? (y/n) ', function(answer) {
      if (answer === 'y') {
        rl.close();
      } else {
        rl.prompt();
      }
    });
  } else {
    console.log('You typed:', cmd);
    console.log('Type "quit" to exit');
  }

  rl.prompt();
});

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

rl.prompt();

Here I’ve used the readline module to create an interface, then listen for line events which denote a line of text was typed. The question method will display a prompt and invoke the callback with the response.

By using simple string matching, a completely customised command-line interface can be created. The readline module also has some useful built-in features like command history.

ncurses

The ncurses module by Brian White provides bindings to the ncurses library. This is a popular method for creating text-based user interfaces. If your application needs things like windows, menus, and more elaborate widgets such as a calendar, then ncurses is a good solution.

These bindings require a level of familiarisation with the original ncurses API. One freely available resource for learning ncurses is the NCURSES Programming HOWTO – combined with the ncurses-node README it’s possible to work out how to apply these techniques to a Node project.

Brian has also written some reusable widgets that come with the node-ncurses module:

var ncurses = require('ncurses')
  , widgets = require('ncurses/lib/widgets')
  , win = new ncurses.Window();

widgets.InputBox('Enter your name:', {
    pos: 'center',
    style: {
      colors: {
        bg: 'blue',
        input: {
          fg: 'red',
          bg: 'black'
        }
      }
    }
  }, function(input) {
    if (!input) {
      input = 'nothing';
    }
    win.centertext(0, 'You entered: ' + input);
    win.refresh();
    setTimeout(function() { win.close(); }, 1000);
});

I’ve adapted this example from Brian’s code – it should work if you install the relevant module with npm install ncurses. The result looks like this:

node-ncurses screenshot

Alternatives

There are simpler alternatives to ncurses. Libraries we’ve covered before, like Commander.js have prompts and dialogs. Then there’s ansi.js (License: MIT, npm: ansi) which makes working with 256 ANSI colours relatively painless, particularly for web developers who are familiar with hex colours:

cursor.hex('#660000').bold().underline();

TermUI (npm: node-term-ui) by Josh Faul has a chainable event-based API that can move the cursor around and output text with various colours:

TermUI
  .pos(10,20)
  .fg(TermUI.C.w)
  .bg(TermUI.C.w)
  .out('Hello, world!');

There’s even a tab completion module: complete (License: MIT, npm: complete) by hij1nx. It’s actually designed to work with bash completion, and will write to .bashrc or .bash_profile:

# Node Completion - Auto-generated, do not touch.
shopt -s progcomp
for f in $(command ls ~/.node-completion); do
  f="$HOME/.node-completion/$f"
  test -f "$f" && . "$f"
done

There are dozens more interesting command-line UI libraries out there. If you’ve written something that you’d like us to feature in a Node Roundup post, then please get in touch!