Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse rework for nested commands #1149

Merged
merged 38 commits into from Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
01500af
First cut at parse rework
shadowspawn Jan 12, 2020
22c9ca5
Add check for requiredOption when calling executable subcommand
shadowspawn Jan 12, 2020
de0f20a
Set program name using supported approach
shadowspawn Jan 12, 2020
7dc2349
Add .addCommand, easy after previous work
shadowspawn Jan 13, 2020
5e43088
Add support for default command using action handler
shadowspawn Jan 13, 2020
d4f87b2
Add implicitHelpCommand and change help flags description
shadowspawn Jan 14, 2020
f51b0e3
Add implicit help command to help
shadowspawn Jan 14, 2020
e421524
Turn off implicit help command for most help tests
shadowspawn Jan 14, 2020
a257e49
.addHelpCommand
shadowspawn Jan 16, 2020
6b7a7c6
Remove addHelpCommand from tests and make match more narrow
shadowspawn Jan 18, 2020
b5b062c
Use test of complete default help output
shadowspawn Jan 18, 2020
3be5ba3
Add tests for whether implicit help appears in help
shadowspawn Jan 18, 2020
b4c055b
Add tests that help command dispatched to correct command
shadowspawn Jan 18, 2020
c455732
Add simple nested subcommand test
shadowspawn Jan 18, 2020
bb9fe74
Add default command tests for action based subcommand
shadowspawn Jan 18, 2020
f5b0798
Remove mainModule, out of scope for current PR
shadowspawn Jan 18, 2020
b28a088
Add legacy asterisk handling and tests
shadowspawn Jan 18, 2020
81185fe
Add more initialisation so object in known state
shadowspawn Jan 19, 2020
9d20c11
Tests for addCommand
shadowspawn Jan 19, 2020
460e3ec
Add first cut at enhanced default error detection
shadowspawn Jan 19, 2020
2a480a5
Add test that addCommand requires name
shadowspawn Jan 21, 2020
0736a77
Add block on automatic name generation for deeply nested executables
shadowspawn Jan 21, 2020
ae61194
Add block on automatic name generation for deeply nested executables
shadowspawn Jan 21, 2020
706b0f9
Merge branch 'feature/parse-rework' of github.com:shadowspawn/command…
shadowspawn Jan 21, 2020
d740db2
Fix describe name for tests
shadowspawn Jan 21, 2020
c529bff
Refine unknownCommand handling and add tests
shadowspawn Jan 21, 2020
365de99
Add suggestion to try help, when appropriate
shadowspawn Jan 21, 2020
aa3805d
Fix typo
shadowspawn Jan 22, 2020
4043f2b
Move common command configuration options in README, and add isDefaul…
shadowspawn Jan 22, 2020
24698c9
Add isDefault and example to README
shadowspawn Jan 22, 2020
cef94eb
Add nested commands
shadowspawn Jan 22, 2020
e5b68f4
Document .addHelpCommand, and tweaks
shadowspawn Jan 22, 2020
b980c2d
Remove old default command, and rework command:* example
shadowspawn Jan 22, 2020
30379ae
Document .addCommand
shadowspawn Jan 22, 2020
5fbec7e
Remove comment referring to removed code.
shadowspawn Jan 24, 2020
c0d62d3
Revert the error tip "try --help", not happy with the wording
shadowspawn Jan 27, 2020
fe896d1
Say "unknown command", like "unknown option"
shadowspawn Jan 27, 2020
432bb59
Set properties to null rather than undefined in constructor
shadowspawn Jan 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 52 additions & 33 deletions Readme.md
Expand Up @@ -11,7 +11,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)

- [Commander.js](#commanderjs)
- [Installation](#installation)
- [Declaring program variable](#declaring-program-variable)
- [Declaring _program_ variable](#declaring-program-variable)
- [Options](#options)
- [Common option types, boolean and value](#common-option-types-boolean-and-value)
- [Default option value](#default-option-value)
Expand All @@ -23,17 +23,18 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
- [Specify the argument syntax](#specify-the-argument-syntax)
- [Action handler (sub)commands](#action-handler-subcommands)
- [Git-style executable (sub)commands](#git-style-executable-subcommands)
- [Automated --help](#automated---help)
- [Automated help](#automated-help)
- [Custom help](#custom-help)
- [.usage and .name](#usage-and-name)
- [.outputHelp(cb)](#outputhelpcb)
- [.helpOption(flags, description)](#helpoptionflags-description)
- [.addHelpCommand()](#addhelpcommand)
- [.help(cb)](#helpcb)
- [Custom event listeners](#custom-event-listeners)
- [Bits and pieces](#bits-and-pieces)
- [Avoiding option name clashes](#avoiding-option-name-clashes)
- [TypeScript](#typescript)
- [Node options such as --harmony](#node-options-such-as---harmony)
- [Node options such as `--harmony`](#node-options-such-as---harmony)
- [Node debugging](#node-debugging)
- [Override exit handling](#override-exit-handling)
- [Examples](#examples)
Expand Down Expand Up @@ -269,7 +270,7 @@ program
program.parse(process.argv);
```

```
```bash
$ pizza
error: required option '-c, --cheese <type>' not specified
```
Expand All @@ -296,7 +297,11 @@ program.version('0.0.1', '-v, --vers', 'output the current version');

## Commands

You can specify (sub)commands for your top-level command using `.command`. There are two ways these can be implemented: using an action handler attached to the command, or as a separate executable file (described in more detail later). In the first parameter to `.command` you specify the command name and any command arguments. The arguments may be `<required>` or `[optional]`, and the last argument may also be `variadic...`.
You can specify (sub)commands for your top-level command using `.command()` or `.addCommand()`. There are two ways these can be implemented: using an action handler attached to the command, or as a separate executable file (described in more detail later). The subcommands may be nested ([example](./examples/nestedCommands.js)).

In the first parameter to `.command()` you specify the command name and any command arguments. The arguments may be `<required>` or `[optional]`, and the last argument may also be `variadic...`.

You can use `.addCommand()` to add an already configured subcommand to the program.

For example:

Expand All @@ -315,8 +320,15 @@ program
program
.command('start <service>', 'start named service')
.command('stop [service]', 'stop named service, or all if no name supplied');

// Command prepared separately.
// Returns top-level command for adding more commands.
program
.addCommand(build.makeBuildCommand());
```

Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified ([example](./examples/defaultCommand.js)).

### Specify the argument syntax

You use `.arguments` to specify the arguments for the top-level command, and for subcommands they are included in the `.command` call. Angled brackets (e.g. `<required>`) indicate required input. Square brackets (e.g. `[optional]`) indicate optional input.
Expand Down Expand Up @@ -397,13 +409,11 @@ async function main() {
}
```

A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated.

Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output.
A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error.

### Git-style executable (sub)commands

When `.command()` is invoked with a description argument, this tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
When `.command()` is invoked with a description argument, this tells commander that you're going to use separate executables for sub-commands, much like `git` and other popular tools.
Commander will search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-subcommand`, like `pm-install`, `pm-search`.
You can specify a custom name with the `executableFile` configuration option.

Expand All @@ -422,14 +432,12 @@ program
.parse(process.argv);
```

Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.
Specifying a name with `executableFile` will override the default constructed name.

If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.

## Automated --help
## Automated help

The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
The help information is auto-generated based on the information commander already knows about your program. The default
help option is `-h,--help`.

```bash
$ ./examples/pizza --help
Expand All @@ -444,17 +452,25 @@ Options:
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese (default: "marble")
-C, --no-cheese You do not want any cheese
-h, --help output usage information
-h, --help display help for command
```

A `help` command is added by default if your command has subcommands. It can be used alone, or with a subcommand name to show
further help for the subcommand. These are effectively the same if the `shell` program has implicit help:

```bash
shell help
shell --help

shell help spawn
shell spawn --help
```

### Custom help

You can display arbitrary `-h, --help` information
You can display extra `-h, --help` information
by listening for "--help". Commander will automatically
exit once you are done so that the remainder of your program
does not execute causing undesired behaviors, for example
in the following executable "stuff" will not output when
`--help` is used.
exit after displaying the help.

```js
#!/usr/bin/env node
Expand All @@ -467,9 +483,7 @@ program
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');

// must be before .parse() since
// node's emit() is immediate

// must be before .parse()
program.on('--help', function(){
console.log('')
console.log('Examples:');
Expand All @@ -488,11 +502,11 @@ Yields the following help output when `node script-name.js -h` or `node script-n
Usage: custom-help [options]

Options:
-h, --help output usage information
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
-B, --baz enable some baz
-h, --help display help for command

Examples:
$ custom-help --help
Expand Down Expand Up @@ -550,6 +564,16 @@ program
.helpOption('-e, --HELP', 'read more information');
```

### .addHelpCommand()

You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`.

You can both turn on and customise the help command by supplying the name and description:

```js
program.addHelpCommand('assist [command]', 'show assistance');
```

### .help(cb)

Output help information and exit immediately.
Expand All @@ -564,9 +588,10 @@ program.on('option:verbose', function () {
process.env.VERBOSE = this.verbose;
});

// error on unknown commands
program.on('command:*', function () {
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args[0]]);
// custom error on unknown command
program.on('command:*', function (operands) {
console.error(`Invalid command '${operands[0]}'. Did you mean:`);
console.error(mySuggestions(operands[0]));
process.exit(1);
});
```
Expand Down Expand Up @@ -686,12 +711,6 @@ program
console.log(' $ deploy exec async');
});

program
.command('*')
.action(function(env){
console.log('deploying "%s"', env);
});

program.parse(process.argv);
```

Expand Down
20 changes: 6 additions & 14 deletions Readme_zh-CN.md
Expand Up @@ -31,15 +31,15 @@
- [.help(cb)](#helpcb)
- [自定义事件监听](#%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9b%91%e5%90%ac)
- [零碎知识](#%e9%9b%b6%e7%a2%8e%e7%9f%a5%e8%af%86)
- [避免选项命名冲突](#避免选项命名冲突)
- [避免选项命名冲突](#%e9%81%bf%e5%85%8d%e9%80%89%e9%a1%b9%e5%91%bd%e5%90%8d%e5%86%b2%e7%aa%81)
- [TypeScript](#typescript)
- [Node 选项例如 --harmony](#node-%e9%80%89%e9%a1%b9%e4%be%8b%e5%a6%82---harmony)
- [Node 选项例如 `--harmony`](#node-%e9%80%89%e9%a1%b9%e4%be%8b%e5%a6%82---harmony)
- [Node 调试](#node-%e8%b0%83%e8%af%95)
- [重载退出(exit)处理](#%e9%87%8d%e8%bd%bd%e9%80%80%e5%87%baexit%e5%a4%84%e7%90%86)
- [例子](#%e4%be%8b%e5%ad%90)
- [许可证](#%e8%ae%b8%e5%8f%af%e8%af%81)
- [支持](#%e6%94%af%e6%8c%81)
- [企业使用Commander](#企业使用Commander)
- [企业使用Commander](#%e4%bc%81%e4%b8%9a%e4%bd%bf%e7%94%a8commander)

## 安装

Expand Down Expand Up @@ -435,7 +435,7 @@ Options:
-b, --bbq Add bbq sauce
-c, --cheese <type> Add the specified type of cheese (default: "marble")
-C, --no-cheese You do not want any cheese
-h, --help output usage information
-h, --help display help for command
```

### 自定义帮助
Expand All @@ -453,9 +453,7 @@ program
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');

// must be before .parse() since
// node's emit() is immediate

// must be before .parse()
program.on('--help', function(){
console.log('');
console.log('Examples:');
Expand All @@ -474,7 +472,7 @@ console.log('stuff');
Usage: custom-help [options]

Options:
-h, --help output usage information
-h, --help display help for command
-V, --version output the version number
-f, --foo enable some foo
-b, --bar enable some bar
Expand Down Expand Up @@ -670,12 +668,6 @@ program
console.log(' $ deploy exec async');
});

program
.command('*')
.action(function(env){
console.log('deploying "%s"', env);
});

program.parse(process.argv);
```

Expand Down
4 changes: 1 addition & 3 deletions examples/custom-help
Expand Up @@ -9,9 +9,7 @@ program
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');

// must be before .parse() since
// node's emit() is immediate

// must be before .parse()
program.on('--help', function() {
console.log('');
console.log('Examples:');
Expand Down
36 changes: 36 additions & 0 deletions examples/defaultCommand.js
@@ -0,0 +1,36 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
const program = new commander.Command();

// Example program using the command configuration option isDefault to specify the default command.
//
// $ node defaultCommand.js build
// build
// $ node defaultCommand.js serve -p 8080
// server on port 8080
// $ node defaultCommand.js -p 443
// server on port 443

program
.command('build')
.description('build web site for deployment')
.action(() => {
console.log('build');
});

program
.command('deploy')
.description('deploy web site to production')
.action(() => {
console.log('deploy');
});

program
.command('serve', { isDefault: true })
.description('launch web server')
.option('-p,--port <port_number>', 'web port')
.action((opts) => {
console.log(`server on port ${opts.port}`);
});

program.parse(process.argv);
6 changes: 0 additions & 6 deletions examples/deploy
Expand Up @@ -34,10 +34,4 @@ program
console.log();
});

program
.command('*')
.action(function(env) {
console.log('deploying "%s"', env);
});

program.parse(process.argv);
47 changes: 47 additions & 0 deletions examples/nestedCommands.js
@@ -0,0 +1,47 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
const program = new commander.Command();

// Commander supports nested subcommands.
// .command() can add a subcommand with an action handler or an executable.
// .addCommand() adds a prepared command with an actiomn handler.

// Example output:
//
// $ node nestedCommands.js brew tea
// brew tea
// $ node nestedCommands.js heat jug
// heat jug

// Add nested commands using `.command()`.
const brew = program.command('brew');
brew
.command('tea')
.action(() => {
console.log('brew tea');
});
brew
.command('tea')
.action(() => {
console.log('brew tea');
});

// Add nested commands using `.addCommand().
// The command could be created separately in another module.
function makeHeatCommand() {
const heat = new commander.Command('heat');
heat
.command('jug')
.action(() => {
console.log('heat jug');
});
heat
.command('pot')
.action(() => {
console.log('heat pot');
});
return heat;
}
program.addCommand(makeHeatCommand());

program.parse(process.argv);