# 可变参数的选项

README 文档介绍了选项的声明与使用。大多数情况下，选项的解析行为，与您和您程序用户的期望相符。本文将详述一些特殊用例和细节问题。

- [可变参数的选项](#%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E7%9A%84%E9%80%89%E9%A1%B9)
	- [解析中的歧义](#%E8%A7%A3%E6%9E%90%E4%B8%AD%E7%9A%84%E6%AD%A7%E4%B9%89)
		- [方案一：让`--`成为语法的一部分](#%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E8%AE%A9%60--%60%E6%88%90%E4%B8%BA%E8%AF%AD%E6%B3%95%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86)
		- [方案二：把选项放在最后](#%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E6%8A%8A%E9%80%89%E9%A1%B9%E6%94%BE%E5%9C%A8%E6%9C%80%E5%90%8E)
		- [方案三：使用选项替代命令参数](#%E6%96%B9%E6%A1%88%E4%B8%89%EF%BC%9A%E4%BD%BF%E7%94%A8%E9%80%89%E9%A1%B9%E6%9B%BF%E4%BB%A3%E5%91%BD%E4%BB%A4%E5%8F%82%E6%95%B0)
	- [合并短选项，和带有参数的选项](#%E5%90%88%E5%B9%B6%E7%9F%AD%E9%80%89%E9%A1%B9%E5%92%8C%E5%B8%A6%E6%9C%89%E5%8F%82%E6%95%B0%E7%9A%84%E9%80%89%E9%A1%B9)
		- [将短选项视作 boolean 类型选项合并](#%E5%B0%86%E7%9F%AD%E9%80%89%E9%A1%B9%E8%A7%86%E4%BD%9C-boolean-%E7%B1%BB%E5%9E%8B%E9%80%89%E9%A1%B9%E5%90%88%E5%B9%B6)

有些选项可以接受可变数量的参数：

```js
program
   .option('-c, --compress [percentage]') // 0 或 1 个参数
   .option('--preprocess <file...>') // 至少 1 个参数
   .option('--test [name...]') // 0 或多个参数
```

本文中使用的示例代码接受 0 或 1 个参数。但相关的讨论对接受更多参数的选项同样适用。

关于本文档中使用的术语，参见[术语表](./%E6%9C%AF%E8%AF%AD%E8%A1%A8.md)。

## 解析中的歧义

解析选项时，有一些潜在的问题值得注意。当一个命令既有命令参数，又有选项的时候，在解析过程中有可能出现歧义。这可能会影响用户使用您的程序。
Commander 首先解析选项的参数，而用户有可能想将选项后面跟的参数作为命令，或是作为命令参数进行解析。

```js
program
  .name('cook')
  .argument('[technique]')
  .option('-i, --ingredient [ingredient]', 'add cheese or given ingredient')
  .action((technique, options) => {
    console.log(`technique: ${technique}`);
    const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient;
    console.log(`ingredient: ${ingredient}`);
  });

program.parse();
```

```sh
$ cook scrambled
technique: scrambled
ingredient: undefined

$ cook -i
technique: undefined
ingredient: cheese

$ cook -i egg
technique: undefined
ingredient: egg

$ cook -i scrambled  # oops
technique: undefined
ingredient: scrambled
```

可以通过使用`--`来表示选项和选项参数部分的结束，以此来显式地解决这一冲突：

```sh
$ node cook.js -i -- scrambled
technique: scrambled
ingredient: cheese
```

如果不希望强制用户掌握这种使用`--`的写法，您可以尝试以下几种方案。

### 方案一：让`--`成为语法的一部分

与其让用户理解`--`的用法，您也可以直接把它作为程序语法的一部分。

```js
program.usage('[options] -- [technique]');
```

```sh
$ cook --help
Usage: cook [options] -- [technique]

Options:
  -i, --ingredient [ingredient]  add cheese or given ingredient
  -h, --help                     display help for command

$ cook -- scrambled
technique: scrambled
ingredient: undefined

$ cook -i -- scrambled
technique: scrambled
ingredient: cheese
```

### 方案二：把选项放在最后

Commander 遵循 GNU 解析命令的惯例，允许选项出现在命令参数之前或者之后，也可以将选项和命令参数相互穿插。
因此，通过要求把选项放在最后，命令参数就不会和选项参数相混淆。

```js
program.usage('[technique] [options]');
```

```sh
$ cook --help
Usage: cook [technique] [options]

Options:
  -i, --ingredient [ingredient]  add cheese or given ingredient
  -h, --help                     display help for command

$ node cook.js scrambled -i
technique: scrambled
ingredient: cheese
```

### 方案三：使用选项替代命令参数

这个方案虽然有点激进，但是可以完美避免解析中的歧义。

```js
program
  .name('cook')
  .option('-t, --technique <technique>', 'cooking technique')
  .option('-i, --ingredient [ingredient]', 'add cheese or given ingredient')
  .action((options) => {
    console.log(`technique: ${options.technique}`);
    const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient;
    console.log(`ingredient: ${ingredient}`);
  });
```

```sh
$ cook -i -t scrambled
technique: scrambled
ingredient: cheese
```

## 合并短选项和带有参数的选项

多个 boolean 型的短选项可以合并起来写在同一个`-`号后面，比如`ls -al`。同时，你也可以在其中包括一个接受参数的短选项，此时，任何跟在其后的字符都将被视作这个选项的值。

这就是说，默认情况下并不可以把多个带参数的选项合并起来。

```js
program
  .name('collect')
  .option("-o, --other [count]", "other serving(s)")
  .option("-v, --vegan [count]", "vegan serving(s)")
  .option("-l, --halal [count]", "halal serving(s)");
program.parse(process.argv);

const opts = program.opts();
if (opts.other) console.log(`other servings: ${opts.other}`);
if (opts.vegan) console.log(`vegan servings: ${opts.vegan}`);
if (opts.halal) console.log(`halal servings: ${opts.halal}`);
```

```sh
$ collect -o 3
other servings: 3
$ collect -o3
other servings: 3
$ collect -l -v
vegan servings: true
halal servings: true
$ collect -lv  # oops
halal servings: v
```

如果需要使用多个既可用作 boolean，又可以接受参数的选项，只能把它们分别声明。

```
$ collect -a -v -l
any servings: true
vegan servings: true
halal servings: true
```

### 将短选项视作 boolean 类型选项合并

在 Commander v5 之前，并不支持合并短选项和值。合并的 boolean 类型选项会被展开。
因此，`-avl`将被展开为`-a -v -l`。

如果您需要后向兼容，或是倾向于在短选项合并的时候把它们视作 boolean 类型，可以改变此行为：

```js
.combineFlagAndOptionalValue(true)  // `-v45` 被视为 `--vegan=45`，这是默认的行为
.combineFlagAndOptionalValue(false) // `-vl` 被视为 `-v -l`
```
