Awesome
Table of Contents
- Command
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})
bin
: A specific directory to use when executing commands as external programs, defaults to the same directory as the parent executable.exit
: Whether the default error handler will exit the process when an error occurs, default istrue
.help
: An object containing properties that control the default help output, see help.middleware
: An object containing booleans that allows subtractive configuration of the default middleware.stash
: An object to receive parsed options as properties, default is the program instance.strict
: Do not allow any unparsed options, defaultfalse
.trace
: A boolean that forces the default error handler to always print stack traces, default isfalse
.unknown
: Whether unknown option error handling is enabled, defaulttrue
.
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:
align
: A string indicating the alignment style, possible values arecolumn|line|flex|wrap
, default iscolumn
.assignment
: A string delimiter to use for options that accept values, default is=
.collapse
: A boolean indicating that whitespace should not be printed between sections, default isfalse
.copyright
: A string describing the program copyright, default isundefined
.delimiter
: A string delimiter to use between option names, default is,
.exit
: A boolean that forces inclusion of anEXIT
section generated from the program error definitions, default isfalse
.indent
: An integer indicating the number of spaces to indent, default is1
.maximum
: An integer of the column used to wrap long descriptions, default is80
.messages
: An object containing strings for miscellaneous help messages.pedantic
: A boolean indicating that description should be title case and terminated with a period, default istrue
.sections
: An object containing booleans, strings or arrays that control which help sections are printed, default isundefined
.sort
: Whether commands and options are sorted, default isfalse
, may be a boolean, a custom sort function or one of the recognized sort values, see help sort.style
: A string indicating the style of help output, default isgnu
, see help styles.titles
: Map of custom section titles.vanilla
: Never use parameter replacement when printing help output, default isfalse
. This is useful if you are using the ttycolor module but would prefer commands and options not to be highlighted.width
: The character width of the left column, default is20
, only applies whenalign
is set tocolumn
.
Help Styles
gnu
: Prints the defaultGNU
style help output.json
: Prints theJSON
document used to create help output.synopsis
: A minimal style that just prints the synopsis (usage).
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 text help designed to be help2man compatible.
- JSON text used as an intermediary format for other converters.
- TODO: man page format.
- TODO: markdown format.
- TODO: markdown+pandoc format.
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
array
: Argument value must be coerced to anarray
, useful if you wish to ensure that a non-repeatable option becomes an array, for repeatable options it will always be anarray
. This method does not throw an error.boolean
: Coerce the value to aboolean
. Accepts the string valuestrue
andfalse
(case insensitive) and converts integers using the javascript notion of truthy, otherwise any positive length string is treated astrue
. The method does not throw an error.date
: Parse the value as aDate
and throw an error if the value could not be parsed.float
: Parse the value as a floating point number and throw an error if the result isNaN
.integer
: Parse the value as an integer and throw an error if the result isNaN
.json
: Parse the value as aJSON
string and throw an error if the value is malformed.number
: Parse the value as anumber
and throw an error if the result isNaN
.path
: Parse the value as a file system path, relative paths are resolved relative to the current working directory and tilde expansion is performed to resolve paths relative to the user's home directory. This method does not throw an error.string
: Strictly speaking a noop, however it is declared if you wish to allow multiple types for an argument and fallback tostring
. This method does not throw an error.url
: Parse the value to an object containingURL
information, this method will throw an error if nohost
could be determined from the value.
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.
name
: A specific name for the help flag, default is--help
.description
: A specific description for the option, overrides the default.action
: A callback to invoke when the help option is encountered.
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.
version
: A specific version for the program, overrides any version extracted frompackage.json
.name
: A specific name for the version option flags, default is--version
.description
: A specific description for the option, overrides the default.action
: A callback to invoke when the version option is encountered.
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.
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).