commandos
Version:
Command line parser, compatible with DOS style command
284 lines (215 loc) • 10.3 kB
Markdown
# commandos
__命令字符串分析工具__
其他语言 / [英语](./README.md)
包名 *commandos* 的灵感来自于经典策略游戏 *Commandos: Behind Enemy Lines*,但在此处,其真实意义是单词 *command* 和 *DOS* 的合体。__commandos__ 是一个轻量级的命令行分析工具,令你可以更加轻松地使用 Node.js 开发 CLI 应用程序。
## 目录
* [快速开始](#快速开始)
* [API](#api)
* [进阶](#进阶)
* [示例](#示例)
* [起源](#起源)
* [关于](#关于)
* [参考](#参考)
## 链接
* [更新日志](./CHANGELOG.md)
* [项目主页](https://github.com/YounGoat/nodejs.commandos)
## 快速开始
```javascript
const commandos = require('commandos');
// -- example 1 --
// Parse the command line of current process with default settings.
const cmd = commandos.parse();
// -- example 2 --
// Parse command line with default settings.
const cmdline = 'foo -v 1.0 --name JACK';
const cmd = commandos.parse(cmdline);
// RETURN { v, name, $: [] };
// -- example 3 --
// Parse command line with customised settings.
const cmdline = 'foo -v 1.0 --name JACK';
const options = [
'--version -v',
'--name -n' ];
const cmd = commandos.parse(cmdline, options);
// RETURN { version, name, $: [] }
```
如果需要更强大的设置,请阅读 [API: commandos.parse()](#commandosparse) 一节。
### 返回对象
如无意外,`commandos.parse()` 将返回一个 JSON 对象:
* 它的属性或与命令行 `cmdline` 中的选项(`-` 或 `--` 前导)同名,或由 `settings.options` 决定(见下例);
* 此外,返回对象中还包括一个数组类型、名为 `$` 的属性,它将包括命令行 `cmdline` 中所有不属于任何选项的片断(通常附加在命令行的最后)。
```javascript
const cmdline = 'foo -v 1.0 --name JACK bar quz';
const options = [
'--version -v',
'--name -n' ];
const settings = { options };
const cmd = commandos.parse(cmdline, settings);
// RETURN { version, name, $: ['bar', 'quz'] }
```
### 例外
以下情形将导致 `commandos.parse()` 出现例外:
* 参数 *cmdline* 中包含 *settings.options* 中未声明的选项,与此同时参数 *settings.explicit* 设为 `true`;
* 参数 *cmdline* 中出现非法选项。例如,`--no-foo=bar` 即为非法选项,因为开关量是不可以赋值的;
* 参数 *cmdline* 中包含的选项与 *settings* 及 *settings.options* 中声明的选项特征相冲突。
出现异常时,如果通过 *settings.catcher* 指定了异常捕获函数,则该函数将被调用,而相应异常(`Error` 实例)将作为唯一的参数。否则,该异常将被直接抛出:
```javascript
let catcher = (ex) => {
console.warn(ex.message);
};
// The exception will be catched and passed into *catcher*.
commandos.parse('foo --no-foo=bar', { catcher });
```
### 选项组
假如 *cmdline* 可能存在多种不同形式,我们当然可以通过多次运行 `commandos.parse()` 以正确获取命令行携带的参数。不过,通过参数 *settings.groups* 声明选项组是一个更优雅的方式。
```javascript
// Option groups.
const settings = {
explicit: true,
groups: [
[ '--version -v' ],
[ '--name -n', '--gender -g' ]
]
};
commandos.parse('foo -v 1.0', settings);
// RETURN { version, $ }
commandos.parse('foo -n JACK -g male', settings);
// RETURN { name, gender, $ }
```
注意:如果 *settings.explicit* 未明确赋值为 `true`,最好在选项组中的选项声明中合理使用 `'REQUIRED'` 关键字。下面是一个反模式:
```javascript
// -- ANTI PATTERN --
// Option groups.
const settings = {
explicit: false,
groups: [
[ '--version -v' ],
[ '--name -n', '--gender -g' ]
]
};
commandos.parse('foo -n JACK -g male', settings);
// RETURN { $ }
// The first option group matched, becuase it does NOT require any options.
```
### 传入预解析对象
`commandos.parse()` 也可以接受 *options* 对象,并依据 *settings* 对其进行校验和规范化。例如:
```javascript
const options = { v: '1.0', name: 'JACK' };
const settings = {
explicit: true,
options: [
'--version -v',
'--name -n' ]
};
commandos.parse(options, settings);
// RETURN { version, name }
```
## API
### commandos.parse()
* Object __commandos.parse__()
* Object __commandos.parse__(string | string[] *cmdline*)
* Object __commandos.parse__(Object *options*)
* Object __commandos.parse__(*cmdline* | *options*, Array *optionDefinitions*)
* Object __commandos.parse__(*cmdline* | *options*, Object *settings*)
参数 *settings* 通过以下属性设定 __commandos.parse__ 的行为模式:
* __overwrite__ *boolean* DEFAULT `true` OPTIONAL
* __caseSensitive__ *boolean* DEFAULT `true` OPTIONAL
* __explicit__ *boolean* DEFAULT `false` OPTIONAL
* __ignoreInvalidArgument__ *boolean* DEFAULT `false` OPTIONAL
* __options__ *Array* OPTIONAL
* __groups__ *Array* OPTIONAL
一个标准的 *选项定义*(数组 *optionDefinitions* 或 *settings.options* 的元素)可以是一个对象或字符串。当它是一个对象时,可以包含以下属性:
* __name__ *string*
选项的正式名称,将作为相应选项在 `commandos.parse()` 函数返回对象中相应的属性名。
* __alias__ *string | string[]* OPTIONAL
选项在 *cmdline* 中可能使用的别名。
* __assignable__ *boolean* DEFAULT `true` OPTIONAL
若选项被声明为 __assignable__,*cmdline* 中紧随选项名之后的片断将视为该选项的值。
* __caseSensitive__ *boolean* DEFAULT INHERIT OPTIONAL
若选项被声明为 __caseSensitive__,则 `version` 和 `Version` 将被视为不同选项。
* __multiple__ *boolean* DEFAULT `false` OPTIONAL
若选项被声明为 __multiple__,将选项的解析值将是一个数组。
* __nullable__ *boolean* DEFAULT `true` OPTIONAL
若选项的 __nullable__ 属性非真,则该选项__不应当__在 *cmdline* 中单独出现,若出现则必须附带选项值.
* __overwrite__ *boolean* DEFAULT INHERIT OPTIONAL
若选项的 __overwrite__ 属性非真,该选项(包括其别)__不应当__在 *cmdline* 中出现多次。
* __required__ *boolean* DEFAULT `false` OPTIONAL
若选项被声明为 __required__,该选项__应当__在 *cmdline* 中至少出现一次。
__注意:以上属性之间可能是互斥的。__
例如,如果选项声明为 __multiple__,则它__不应当__同时是一个开关量。也就是说,该选项是可赋值的且不能为空,因此选项声明中的 __nullable__ / __assignable__ / __overwrite__ 属性将被忽略。
选项声明也可以是一个字符串,遵从类似 [column definition in MySQL](https://dev.mysql.com/doc/refman/8.0/en/create-table.html) 的私有语法:
```javascript
// * The option is named "version", or "v" in short. The first name is formal.
// * The option SHOULD be appended with some value.
// * The option SHOULD exist.
// * The name is case-insensitive, that means "Version" and "V" are also
// acceptable.
'--version -v NOT NULL REQUIRED NOT CASE_SENSITIVE'
// * The first word is regarded as formal name of the option.
// * Alias "v" and "edition" are also acceptable.
'version alias(v, edition)'
```
## 进阶
### ODL 选项定义语言
ODL 是用于定义选项的微语言,它是选项定义对象的简易替代品。例如:
```javascript
// * The option is named "version", or "v" in short. The first name is formal.
// * The option SHOULD be appended with some value.
// * The option SHOULD exist.
// * The name is case-insensitive, that means "Version" and "V" are also
// acceptable.
'--version -v NOT NULL REQUIRED NOT CASE_SENSITIVE'
// * If named option not offered, the first non-argument will be used as value of option "action".
'--action [0:~* (start|stop|restart)]'
// * The first word is regarded as formal name of the option.
// * Alias "v" and "edition" are also acceptable.
'version alias(v, edition)'
```
ODL 中的关键字大小写不敏感:
* []
* ALIAS
* ASSIGNABLE
* CASE_SENSITIVE
* CASE_INSENSITIVE
* COMMENT
* DEFAULT
* MULTIPLE
* NULLALBE
* OVERWRITE
* REQUIRED
### 非选项参数替代选项值
为了让命令行更灵活,__commandos.parse__ 支持通过在 *选项定义* 添加 __nonOption__ 属性,在命令行中未找到命名选项时,取非选项参数作为选项值。属性 __nonOption__ 支持以下类型的重载:
* __nonOption__ *number*
* __nonOption__ *string*
* __nonOption__ *RegExp*
* __nonOption__ *Function*(value, index)
而在 ODL 中则使用定界符 `[]` 来定义 nonOption 属性:
```javascript
// * Fixed position of non-option argument.
// * Fixed value.
'--help [0:=* help] NOT ASSIGNABLE'
// * Any position.
// * Use regular expression (case-insensitive) to validate the arguments.
'--action [*:~* (start|stop|restart)]'
// * Position range.
'--name [>1]'
```
## 示例
阅读测试用例代码以获取更多示例:
* [commandos.parse: basic usage](./test/parse.basic-usage.js)
* [commandos.parse: global settings](./test/parse.global-settings.js)
* [commandos.parse: option settings](./test/parse.option-settings.js)
* [commandos.parse: take non-option argument as option value](./test/parse.option-nonoption.js)
* [commandos.parse: option groups](./test/parse.option-groups.js)
## 起源
在众多使用 Node.js 编写的命令行解析工具中,[minimist](https://www.npmjs.com/package/minimist) 和 [commander](https://www.npmjs.com/package/commander) 较为知名。但是,有时候 __minimist__ 功能太过单薄,而 __commander__ 又太重,这就是我为什么设计 __commandos__ 的原因。
## 关于
*commandos* = *command* + *DOS*
## 参考
如果 __commandos__ 不合你的胃口, 有很多类似功能的包可供选择(按字母顺序排列):
* [commander](https://www.npmjs.com/package/commander)
* [getopts](https://www.npmjs.com/package/getopts)
* [minimist](https://www.npmjs.com/package/minimist)
* [nomnom](https://www.npmjs.com/package/nomnom)
* [optimist](https://www.npmjs.com/package/optimist)
* [yargs](https://www.npmjs.com/package/yargs)