Home

Awesome

Table of Contents

Command

Command execution for command line interfaces, a component of the toolkit.

This module is a binding of the define and argparse modules to provide option parsing and command execution for more complex programs. If your requirements are not very complex you may prefer to use argparse by itself.

Install

npm install cli-command

Test

Tests are not included in the package, clone the repository and install dependencies to run the test suite:

npm test

Examples

The lipsum program is the canonical example for this module, it is a fully functional program that demonstrates much of the help configuration flexibility.

More useful real-world programs are mdp (markdown partial processor) which was used to generate this document and manpage a tool that can generate man pages from any program created using this module.

More example programs are in the ebin directory, there are also a ton of examples in the test directory.

Configuration

Invoke the configure() method with an object to override the default configuration.

var stash = {};
cli.configure({stash: stash})

Help

var cli = require('cli-command');
cli
  .help()
  // ...
  .parse();

The help method adds a flag to the program which by default is mapped to --help.

Help Configuration

The help configuration object supports the following properties:

Help Styles

Help Sections

Help output sections are named the same as man pages, the keys are:

var sections = [
  'name',
  'description',
  'synopsis',
  'commands',
  'options',
  'environment',
  'files',
  'examples',
  'exit',
  'history',
  'author',
  'bugs',
  'copyright',
  'see'
];

Disable output for a section with false:

cli.configure({help:{sections: {description: false}}});

Set a section to a string value to include the string literal:

cli.configure({help:{sections: {examples: 'Some example section text'}}});

Or specify an array of objects with name and description properties:

cli.configure({
  help:{
    sections: {
      examples: [
        {
          name: 'fu --bar',
          description: 'Example of using fu with --bar'
        }
      ]
    }
  }
});

Help Sort

// TODO.

Help Environment

All help configuration properties (with the exception of complex objects such as sections, messages, titles etc.) may be configured using environment variables.

This enables you to quickly inspect help output under different configurations and allows users of your program to set a preference for how help output should be displayed. For example:

export cli_toolkit_help_align=line;
export cli_toolkit_help_maximum=120;

Use unset to clear set variables:

unset cli_toolkit_help_*;           // bash
unset -m 'cli_toolkit_help_*';      // zsh requires -m option and quotes

You may disable this behaviour when invoking the help middleware, for example:

cli.help(null, null, null, false);

Help Manual

Help output can be converted into the following formats by setting environment variables:

Plain

The default GNU style help output is designed to be compatible with help2man.

JSON

To print help as JSON set the CLI_TOOLKIT_HELP_JSON variable. By default the output is compact, however you can pretty print the JSON by setting CLI_TOOLKIT_HELP_JSON_INDENT to a valid integer.

Version

var cli = require('cli-command');
cli
  .version()
  // ...
  .parse();

The version method adds a flag to the program which by default is mapped to --version. If you wish to customize the version output pass a function to the help method, this can be useful if you want to include version information for external programs you depend upon or just to include more useful information.

var cli = require('cli-command');
var version = cli.version;
cli()
  .version(function() {
    // invoke the default version action
    // and pass true so it does not exit the process
    version.call(this, true);
    // add additional version information here
    process.exit(0);
  })
  .parse();
<p align="center"> <img src="https://raw.github.com/freeformsystems/cli-command/master/img/version.png" /> </p>

Source: version/defaults and version/custom.

Types

A flexible, extensible and intuitive type system allows coercion between the argument string values and javascript types.

Essentially the type coercion system is just a function that gets passed the string value of the argument, which allows simple coercion with parseInt etc.

var cli = require('cli-command')();
function range(val) {
  return val.split('..').map(Number);
}
function list(val) {
  return val.split(',');
}
cli
  .option('-i, --integer <n>', 'an integer argument', parseInt)
  .option('-f, --float <n>', 'a float argument', parseFloat)
  .option('-r, --range <a>..<b>', 'a range', range)
  .option('-l, --list <items>', 'a list', list)
// ...

Source: test/spec/coerce.

The coercion function (referred to as a converter) may be more complex, the signature is:

function(value, arg, index)

Where value is the argument string value, arg is the option definition and index is the position in an array (only for options that are repeatable). Functions are executed in the scope of the program so you can access all it's properties (this.name() is very useful).

Native functions are good if you are willing to accept NaN as a possible value; for those cases where you must have a valid number you should use one of the pre-defined type coercion functions that will throw an error if the value is NaN. The type error will then be emitted as an error event (ETYPE). If there is no listener for error and etype a useful error message is printed and the program will exit, otherwise you are free to handle the error as you like.

Source test/spec/types.

var cli = require('cli-command');
var types = cli.types;
var program = cli()
  .option('-f, --float <n>', 'a float argument', types.float);
// ...

Type List

Type Map

As a convenience common native types are mapped from the constructor to the coercion function:

{
  Array: types.array,
  Boolean: types.boolean,
  Date: types.date,
  JSON: types.json,
  Number: types.number,
  String: types.string
}

Such that you can map types with:

cli.option('-n, --number <n>', 'a number argument', Number)

The JSON type is an exception as it is not a constructor, however, it is supported as a shortcut for types.json.

Multiple Types

It is also possible to declare an option as being one of a list of types by specifying an array of functions:

cli.option('-d, --date <d>', 'a date or string', [Date, String])

When an array is used coercion will be attempted for each listed type function, the first to succeed will become the option's value, if all type coercions fail then an ETYPE error event is emitted.

Custom Types

Declare a function to create your own custom type:

var ArgumentTypeError = require('cli-command').error.type;
function mime(value, arg, index) {
  // validate the value is a recognized mime type
  // and return it if valid
  throw new ArgumentTypeError('invalid mime type for %s, got %s',
    arg.toString(null), value);
}
// ...
cli.option('-m, --mime-type <mime>', 'a mime type', mime)
// ...

If you throw Error rather than ArgumentTypeError that is fine, it will be wrapped in an ArgumentTypeError. You can utilize ArgumentTypeError for it's message parameter support.

Complex Types

Complex types differ in that the type function must be invoked when declaring the option and it returns a closure that is the converter.

Enum

Validates that a value exists in a list of acceptable values.

types.enum(Array)
var list = ['css', 'scss', 'less'];
cli.option('-c, --css <value>',
    'css preprocessor', types.enum(list))

File

It is useful to be able to test options that are files to be of a particular type. You may use the file type with a series of file test expressions:

types.file(String, [Boolean])

By default files are resolved according to the rules for the path type, you may specify the second argument as false to disable this behaviour.

To test that an option's value is a regular file and exists use:

cli.option('-f, --file <file>',
    'file to process', types.file('f'))

See the fs documentation for the list of file expressions and some caveats. Note that multiple expressions can be specified, so to test a file is readable and has it's executable bit set you could use rx.

List

Splits a value into an array based on a string or regexp delimiter.

types.list(String|RegExp)
cli.option('-l, --list <list>',
    'a comma-delimited list argument', types.list(/\s*,\s*/))

Object

Coalesces related options into an object. Note that the conflicts logic applies to object property names when the stash configuration property is not set.

types.object(String)
cli
  .option('-s, --scheme <scheme>',
    'transport scheme', types.object('server'))
  .option('-h, --host <host>',
    'server hostname', types.object('server'))
  .option('-p, --port <n>',
    'server port', types.object('server'))

Unparsed Types

It is possible to coerce or validate the unparsed options by specifying a converter on the program:

var cli = require('cli-command');
var types = cli.types;
var program = cli()
  .converter(types.integer)
  .parse();

Note that because the unparsed arguments list is always an array specifying the Array type will result in a multi-dimensional array of strings.

Commands

var path = require('path');
require('ttycolor')().defaults();
// use existing meta data (package.json)
var cli = require('../..')(
  path.join(__dirname, '..', 'package.json'));
cli
  .option('-f --file <file...>', 'files to copy')
  .option('-v --verbose', 'print more information')
  .version()
  .help();
cli.command('cp')
  .description('copy files')
  .action(function(cmd, options, raw) {
    // execute copy logic here, scope is the program instance (cli)
    console.log('files: %j', this.file);
  });
cli.parse();  // defaults to process.argv.slice(2)

Source: command.

Executable Commands

If you wish to structure your program as a series of executables for each command (git style) use the alternative syntax:

#!/usr/bin/env node

require('ttycolor')().defaults();
var cli = require('../..')();
cli
  .configure({command: {exec: true}})
  .version()
  .help()
  .on('empty', function(help, version) {
    help.call(this, true);
    console.error(this.name() + ': command required');
  })
  .command('install', 'install packages')
  .on('complete', function(req) {
    // access the child process via req.process
  })
cli.parse();   // execute pkg-install(1) upon install command

Source: pkg.

Errors

Handling errors in any program is important but doing it elegantly in a command line program can be tricky, so the error module has been integrated to make error handling consistent and robust.

The pre-defined error conditions are in en.json. The error module intentionally starts incrementing exit status codes from 128 so as not to conflict with low exit status codes, for example, node uses exit code 8 to indicate an uncaught exception. The command module uses exit codes from 64-127 and you are encouraged to start your exit codes from 128.

Error conditions encountered by the module are treated in an idiomatic manner, they are dispatched as an error event from the program. However, you may just want some default error handling, so if you have not registered an error listener on the program by the time parse() is called then the default error handling will be used.

The default error handling prints useful messages to stderr with information about the error except for an uncaught exception which will also print the stack trace.

var path = require('path');
require('ttycolor')().defaults();
var pkg = path.normalize(
  path.join(__dirname, '..', '..', 'package.json'));
var cli = require('../..')(pkg, 'error/custom');
cli
  .on('error', function(e) {
    // map of error definitions is `this.errors`
    if(e.code == this.errors.EUNCAUGHT.code) {
      e.error(false); // omit stack trace
      e.exit();       // use error definition exit code
    }
    // pass other errors through to the default handler
    this.error(e);
  })
  .option('-n, --number [n]', 'a numeric value', Number)
  .version()
  .help()
  .parse();
throw new Error('a custom error message');

Source: error/custom.

If you are only interested in a particular error you can listen for the error event by error definition key (note the event name is lowercase). When you listen for a particular error the generic error event is not dispatched for that error condition.

var path = require('path');
require('ttycolor')().defaults();
var pkg = path.normalize(
  path.join(__dirname, '..', '..', 'package.json'));
var cli = require('../..')(pkg, 'error/event');
cli
  .on('etype', function(e) {
    console.error(this.name() + ': %s', 'etype listener fired');
    // NOTE: if we did not invoke the default handler
    // NOTE: which exits the process on error by default
    // NOTE: then the default uncaught exception handling
    // NOTE: would also be triggered
    this.error(e);
  })
  .option('-n, --number [n]', 'a numeric value', Number)
  .version()
  .help()
  .parse();
throw new Error('an euncaught listener error message');

Source: error/event.

API

The define module is thoroughly documented so you should check that out to learn more about defining program options, if you want to dig under the hood a little also read the argparse documentation.

Program

Methods

help([name], [description], [action])
cli.help()
cli.help('--info', 'print help information', function(){})
cli.help(function(){})

Adds a help flag to the program, scope for the action callback is the program instance.

Returns the program for chaining.

version([version], [name], [description], [action])
cli.version()
cli.version('1.0.0')
cli.version('1.0.0', '--version', 'print version', function(){})
cli.version(function(){})

Adds a version flag to the program, scope for the action callback is the program instance. Configured version number is available via after setting the flag option by invoking with zero arguments.

Returns the program for chaining or the version string if a version flag exists and zero arguments are passed.

Conflicts

By default the module will set parsed options as properties of the program. This makes for very convenient access to option values, it is just this.option (or program.option if the scope is not the program).

However, there may be times when an argument key conflicts with an internal property or method. To prevent this you can either rename the option or set the configuration property stash to an object that will contain the option values, for example:

var cli = require('..');
var stash = {};
cli.configure({stash: stash});
// ...
cli.parse();

If a stash has not been configured and your program declares an option that would cause a conflict, the program will scream at you, literally scream.

<p align="center"> <img src="https://raw.github.com/freeformsystems/cli-command/master/img/conflict.png" /> </p>

Source: conflict.

Enumerate

The program class has been designed such that enumeration will only show properties that derived from the argument parsing. This provides a convenient way to iterate over the argument keys and values for options that were specified on the command line.

The only drawback to this design is that the console.dir() method no longer allows inspection of properties and methods.

At times you may wish to inspect the internal structure of the program using console.dir(), to enable this functionality set the CLI_TOOLKIT_DEBUG environment variable before all require() statements. This forces all properties and methods to be enumerable and console.dir() will work as expected.

See the enumerate/defaults and enumerate/debug examples.

Interface

It is important to decouple your program configuration from the binary file that user's will run so that you can easily test your program and generate code coverage. The interface module is a tiny library that complements this command module to encourage decoupling.

Reserved Keywords

_action, _commands, _conf, _configure, _converter, _description, _detail, _emitter, _events, _exec, _extra, _key, _last, _maxListeners, _middlecache, _middleware, _name, _names, _options, _package, _parent, _request, _sections, _usage, _version, action, addListener, assign, command, commands, configure, converter, createCommand, createFlag, createOption, description, detail, domain, emit, error, errors, extra, finder, flag, getFullName, getLongName, getOptionString, getParents, getShortName, hasCommands, hasOptions, help, isArgument, isCommand, isFlag, isOption, isProgram, key, last, listeners, name, names, on, once, option, options, package, parent, parse, raise, removeAllListeners, removeListener, request, reset, sections, setMaxListeners, toObject, toString, usage, use, version, wrap.

Credits

Chainable program definition inspired by commander, type conversion on native type constructors lifted from nopt and middleware concept thanks to express.

License

Everything is MIT. Read the license if you feel inclined.

Generated by mdp(1).