Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/trentm/node-cmdln

node.js helper lib for creating CLI tools with subcommands; think `git`, `svn`, `zfs`
https://github.com/trentm/node-cmdln

Last synced: about 1 month ago
JSON representation

node.js helper lib for creating CLI tools with subcommands; think `git`, `svn`, `zfs`

Awesome Lists containing this project

README

        

`node-cmdln` is a node.js helper lib for creating CLI tools with subcommands
(think `git`, `svn`, `zfs`, `brew`, etc.). It is a sister of my earlier
[Python lib for this](https://github.com/trentm/cmdln).

# Usage

You define a subclass of `Cmdln` and subcommands as `do_NAME` methods.
Minimally you could have a "conan.js" as follows:

```javascript
#!/usr/bin/env node
var util = require('util');
var cmdln = require('cmdln');

function Conan() {
cmdln.Cmdln.call(this, {
name: 'conan',
desc: 'What is best in life?'
});
}
util.inherits(Conan, cmdln.Cmdln);

Conan.prototype.do_crush = function do_crush(subcmd, opts, args, cb) {
console.log('Yargh!');
cb();
};
Conan.prototype.do_crush.help = 'Crush your enemies.';

cmdln.main(new Conan()); // mainline
```

With this, you get the following behaviour:

```bash
$ node examples/conan.js
What is best in life?

Usage:
conan [OPTIONS] COMMAND [ARGS...]
conan help COMMAND

Options:
-h, --help Show this help message and exit.

Commands:
help (?) Help on a specific sub-command.
crush Crush your enemies.

$ node examples/conan.js help crush
Crush your enemies.

$ node examples/conan.js crush
Yargh!
```

# Option processing

Option processing (using [dashdash](https://github.com/trentm/node-dashdash))
is integrated. `do_crush` above could be replaced with:

```javascript
Conan.prototype.do_crush = function (subcmd, opts, args, cb) {
if (opts.help) {
this.do_help('help', {}, [subcmd], cb);
return;
}
if (!args.length) {
console.log('No enemies? Yarg!');
} else {
args.forEach(function (enemy) {
console.log('Smite %s with a %s!', enemy, opts.weapon);
});
}
cb();
};
Conan.prototype.do_crush.options = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Show this help.'
},
{
names: ['weapon', 'w'],
helpArg: 'WEAPON',
type: 'string',
default: 'sword',
help: 'Weapon with which to smite.'
}
];
Conan.prototype.do_crush.help = (
'Crush your enemies.\n'
+ '\n'
+ 'Usage:\n'
+ ' {{name}} {{cmd}} [OPTIONS] [ENEMIES...]\n'
+ '\n'
+ '{{options}}'
);
```

Then we get this behaviour:

```bash
$ node examples/conan.js crush Bob
Smite Bob with a sword!

$ node examples/conan.js crush Bob Linda --weapon mattock
Smite Bob with a mattock!
Smite Linda with a mattock!

$ node examples/conan.js crush -h
Crush your enemies.

Usage:
conan crush [OPTIONS] [ENEMIES...]

Options:
-h, --help Show this help.
-w WEAPON, --weapon=WEAPON Weapon with which to smite.
```

See [examples/conan.js](examples/conan.js) for the complete example. Run
`node example/conan.js ...` to try it out.

# Bash completion

One can generate Bash completion code for a `Cmdln` subclass via

cli.bashCompletion()

One possible usage is to add a `completion` subcmd to your CLI:

CLI.prototype.do_completion = function (subcmd, opts, args, cb) {
console.log( this.bashCompletion() );
cb();
};

and get users to use that to setup Bash completion:

$ alias conan="node examples/conan.js"
$ conan completion > conan.completion
$ source conan.completion

$ conan
crush hear help pulverize see smash
$ conan -
--help --verbose --version -h -v -x
$ conan crush --weapon # custom 'weapon' completion type
bow-and-array mattock spear sword
$ conan crush --weapon spear # custom 'enemy' completion type
King-Osric Subotai Thulsa-Doom _mbsetupuser trentm

See the `do_completion` subcommand on "examples/conan.js" for a complete example
of this. See the equivalent in the larger `triton` tool for another example:
.

Another potential usage could be to pre-generate a completion file and
distribute it with your tool.

# Reference

In general, also please read the comments in [the source](./lib/cmdln.js) and
[browse the examples](./examples/).

## `cmdln.Cmdln`

To use this module you create a class that inherits from `cmdln.Cmdln`; add
some methods to that class that define the tool's commands, options, etc.;
then pass an instance to `cmdln.main()`. Roughly like this:

function CLI() {
cmdln.Cmdln.call(this, {});
}
util.inherits(CLI, cmdln.Cmdln);
...
var cli = new CLI();
cmdln.main(cli);

We'll use the `CLI` and `cli` names as used above in the following reference:

- `new Cmdln()` Create a Cmdln subclass instance. See the block comment
in the code for full documentation on the `config` options.

- `CLI.prototype.do_ = function (subcmd, opts, args, cb)` is how a
subcommand is defined. How the subcmd is handled can be customize with some
properties (e.g. `options`, `help`) on the handler function.

- `CLI.prototype.do_ = ;` Instead of a function handler for a
subcommand, a `do_` can be set to another Cmdln subclass to support
sub-subcommands, like `git remote add|remove|rename|...`. See
["examples/fauxgit.js"](./examples/fauxgit.js) for an example.

- `CLI.prototype.do_.aliases = ;` to define one or
more aliases for a command. These aliases are shown in the "Commands:"
section of the generated help output.

- `CLI.prototype.do_.hiddenAliases = ;` to define one
or more aliases for a command **that are not shown in the generated help
output**. This can be useful when renaming a subcommand in a new version of
a tool and still support the old name.

- `CLI.prototype.do_.options = ;` is how to set the options
(in [dashdash](https://github.com/trentm/node-dashdash) format) for that
subcommand.

- `CLI.prototype.do_.synopses = ;`
Set to the synopsis string(s) for this command, i.e. the part typically
in the "SYNOPSIS" section of a man page. See
[examples/conan.js](examples/conan.js). This supports some template variables:

- `{{name}}` becomes `cli.name` (i.e. the tool name).
- `{{cmd}}` becomes the sub-command name.

Setting `synopses` can be used (a) for the `{{usage}}` template var in
subcmd help (see below) and (b) for `errHelp` for `UsageError`s (see below).

- `CLI.prototype.do_.helpOpts = ;` to override
formatting settings for `options` help output for this command. By default
the `helpOpts` passed into the CLI constructor are used. The set of supported
helpOpts are defined by
[dashdash](https://github.com/trentm/node-dashdash#help-config).

- `CLI.prototype.do_.help = ;` to set the help string for a
subcommand. This supports some template variables:

- `{{name}}` becomes `cli.name` (i.e. the tool name).
- `{{cmd}}` becomes the sub-command name.
- `{{usage}}` becomes a "Usage:\n $synopses" block if `synopses` are
defined (see above).
- `{{options}}` becomes a "Options:\n $option-help" block if `options`
are provided for the subcmd (see above).

- `CLI.prototype.do_.help = function (subcmd, opts, args, cb)` is
an alternate method to handle help for a subcommand. The given function
will be run when `tool help ` is called.

- `CLI.prototype.do_.desc = ;` can be set to a short string
to be used in the `tool help` output to summarize subcmd. If not provided,
then the first line of `do_.help` will be used.

- `CLI.prototype.do_.hidden = ;` Set to false to have
`tool help` output *not* list this subcmd.

- `CLI.prototype.do_.interspersedOptions = ;` Set to
false to have `tool ...` not allow interspersed options
(i.e. options after the first argument).

- `CLI.prototype.do_.allowUnknownOptions = ;` Set to
true to have `tool ...` allow unknown options.

- `CLI.prototype.do_.completionArgtypes = ;` Set to an array
of strings to define the [Bash completion](#bash-completion) type for the
corresponding positional arg. For example, the following:
MyCLI.prototype.do_foo.completionArgtypes = ['fruit', 'file'];
would mean that `mycli foo ` would complete "fruit" (using a
`complete_fruit` bash function, typically provided via the `specExtra`
arg to `.bashCompletion()`) and the second and subsequent positional
args -- `mycli foo banana ` -- would use filename completion.

- `CLI.prototype.init(opts, args, cb)` Hook run after option processing
(`this.opts` is set), but before the subcommand handler is run.

- `CLI.prototype.fini(subcmd, err, cb)` Hook run after the subcommand handler is
run. Here `err` is the error returned by the invocation of the CLI. This allows
a `fini` method to use or deal with that error, if necessary. To just
pass that err on (to the calling `main`) do this:

CLI.prototype.fini = function fini(subcmd, err, cb) {
// Whatever finalization you want to do here (possibly with a
// `finiErr`) ...
cb(finiErr || err, subcmd);
};

(Note: The call signature to `fini` changed in cmdln v3. See the changelog
in CHANGES.md.)

- `CLI.prototype.defaultHandler(subcmd, opts, args, cb)` This is a hook
function to handle an unknown option. By default it will callback with
`UnknownCommandError(subcmd)`. It can be overriden, for example as
follows:

/*
* Provide the `jirash KEY-1` shortcut for `jirash issue get KEY-1`.
*/
JirashCli.prototype.defaultHandler = function defaultHandler(
subcmd, opts, args, cb) {
var keyRe = /^[A-Z]+-\d+$/;
if (keyRe.test(subcmd)) {
this.handlerFromSubcmd('issue').dispatch({
subcmd: 'get',
opts: {'short': true},
args: [subcmd]
}, cb);
} else {
Cmdln.prototype.defaultHandler.call(this, subcmd, opts, args, cb);
}
};

- `cli.showErrStack` boolean. Set to true to have `cmdln.main()`, if used,
print a full stack on a shown error. A common pattern of mine is to set
this in the `.init()` method if a top-level `-v,--verbose` option is given.

- `cli.handlerFromSubcmd()` will return the appropriate
`do_` method that handles the given sub-command. This resolves
sub-command aliases.

- `cli.helpFromSubcmd()` will return the help string for
that subcmd *or*, if defined, the help function defined for that subcmd.
This is used by the default `do_help` implementation.

- `cli.bashCompletion()` generates and returns bash completion for
the CLI.

## `cmdln.main()`

This is a convenience method for driving the mainline of your script using
the your defined `Cmdln` subclass. There are a number of options to control
how it works. Read the block comment on that function in "lib/cmdln.js" for
the best docs.

## `errHelp` and Errors

cmdln v4 introduced subcmd *synopses*, *`errHelp`*, and some related
functionality to help provide brief automatic command help for some usage
errors. `errHelp` is a brief message after a printed error, giving potentially
helpful info. Some examples from familiar commands (marked here with `>`):

$ ls -D
ls: illegal option -- D
> usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]

$ git foo
git: 'foo' is not a git command. See 'git --help'.

> Did you mean this?
> fo

Use the following suggestions to get this kind of error-help for your commands:

1. Optionally set `synopses` on your subcmd handlers. E.g.:

do_list.synopses = ['{{name}} list [OPTIONS] FILTERS...'];

Doing so allows two things: (a) the use of the `{{usage}}` template var
in your command help, and (b) use of those synopses for `errHelp`.

2. Optionally use the `{{usage}}` template var in your command help. E.g.:

do_list.help = [
'List instances.',
'',
'{{usage}}',
'',
'{{options}}'
].join('\n');

3. Optionally use the `cmdln.UsageError` error class for usage errors in
your subcmds. E.g.:

function do_list(subcmd, opts, args, callback) {
// ...
} else if (args.length < 1) {
callback(new cmdln.UsageError('missing FILTER args'));
return;
}

4. Use `cmdln.main()` for your mainline

This will now attempt to determine `errHelp` from any returned error and
print it on stderr -- use `options.showErrHelp=false` to disable. Or if
you are not using `cmdln.main()`, then you can use
`cmdln.errHelpFromErr(err)` to get errHelp to print, if you like.

Error help is determined by calling `err.cmdlnErrHelpFromErr()`, which is
implemented for cmdln's error classes:

- `cmdln.OptionError`: Show a synopsis of the command's options.
- `cmdln.UsageError`: Show the command's synopses, if available.
- `cmdln.UnknownCommandError`: List possible fuzzy matches.

You can implement that method for custom error classes if you like.

## `cmdln.dashdash`

This is a re-export of the [dashdash](https://github.com/trentm/node-dashdash)
option processing module that cmdln is using. This is exported so that calling
code can add option *types* if wanted, via `cmdln.dashdash.addOptionType`. E.g.,

var cmdln = require('cmdln');

function parseCommaSepStringNoEmpties(option, optstr, arg) {
return arg.trim().split(/\s*,\s*/g)
.filter(function (part) { return part; });
}

cmdln.dashdash.addOptionType({
name: 'commaSepString',
takesArg: true,
helpArg: 'STRING',
parseArg: parseCommaSepStringNoEmpties
});

// ...

See [the node-dashdash documentation](https://github.com/trentm/node-dashdash#custom-option-types)
for details.

# License

MIT. See LICENSE.txt