commander.js 命令行开发工具

198 阅读5分钟

v2-809a23dd4cd0de81fcf03baf240827a3_r.jpg

commander.js是完整的 node.js 命令行解决方案。我们用它来开发命令行程序。

安装

npm install commander

编写代码来描述你的命令行界面。 Commander 负责将参数解析为选项和命令参数,为问题显示使用错误,并实现一个有帮助的系统。

选项

Commander 使用.option()方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。

常用选项类型,boolean 型选项和带参数选项

有两种最常用的选项,一类是 boolean 型选项,选项无需配置参数,另一类选项则可以设置参数(使用尖括号声明在该选项后,如--expect <value>)。

示例: index.js

const { program } = require('commander');

program
  .option('--first', '这是一个长选项名称')
  .option('-s, --separator <char>', '这是一个有短选项名称和长选项名称,并且带了参数');

program.parse();

const options = program.opts();
console.log('options', options);
console.log('program.args', program.args);

运行:

node index --first
// options { first: true }   program.args []

node index --first xxx xx
// options { first: true }   program.args ['xxx', 'xx']

node index -s type xx
// options { separator: 'type' }   program.args ['xx']

node index --separator type xx
// options { separator: 'type' }   program.args ['xx']

上面的例子中,options选项分为 --first, -s或者--separator 它带了一个参数,如果运行它不加上参数就会报错。从运行结果来看,不带参数的 first 的options选项对象值是布尔值,带参数的 --separator 的Options选项对象值是它的参数。

program.args的值是 没有被使用的选项

调用方式可以如下

node index -s 80
node index -s80
node index --separator 80
node index --separator=80

多个布尔短选项可以在破折号之后组合在一起,并且可以跟一个取值的单一选项。

const { program } = require('commander');

program
  .option('-d, --debug', 'output extra debugging')
  .option('-s, --small', 'small pizza size')
  .option('-p, --pizza-type <type>', 'flavour of pizza');

program.parse(process.argv);

const options = program.opts();
if (options.debug) console.log(options);
console.log('pizza details:');
if (options.small) console.log('- small pizza size');
if (options.pizzaType) console.log(`- ${options.pizzaType}`);

上面的例子可以这样调用: -d -s -p cheese 可以写成 -ds -p cheese 甚至 -dsp cheese

选项的默认值

选项可以设置一个默认值。

program
  .option('-c, --color', '给color选项设置一个蓝色的默认值', 'blue')

这样子直接运行 node index 获取的Options就是 { color: blue }, 如果运行 node index -c时还是得加上参数,不然会报错

选项取反

可以定义一个以no-开头的 boolean 型长选项。在命令行中使用该选项时,会将对应选项的值置为false。当只定义了带no-的选项,未定义对应不带no-的选项时,该选项的默认值会被置为true

const { program } = require('commander');

program
  .option('--no-sauce', 'Remove sauce')
  .option('--sauce', 'Remove sauce')
  .option('--cheese <flavour>', 'cheese flavour', 'mozzarella')
  .option('--no-cheese', 'plain with no cheese')
  .parse();

const options = program.opts();
console.log(options);

运行

$ node index
// { sauce: true, cheese: 'mozzarella' }
$ node index --no-sauce
// { sauce: false, cheese: 'mozzarella' }
$ node index --sauce
// { sauce: true, cheese: 'mozzarella' }
$ node index --no-cheese
// { sauce: true, cheese: false }

可选参数

选项的参数使用方括号声明表示参数是可选参数(如--optional [value])。该选项在不带参数时可用作 boolean 选项,在带有参数时则从参数中得到值。

const { program } = require('commander');

program
.option('-c, --cheese [type]', '这是一个可选参数');
program.parse();

const options = program.opts();
console.log(options);

运行

$ node index -c
// { cheese: true }
$ node index -c xxx
// { cheese: 'xx' }

变长参数选项

定义选项时,可以通过使用...来设置参数为可变长参数。在命令行中,用户可以输入多个参数,解析后会以数组形式存储在对应属性字段中。

const { program } = require('commander');

program
.option('-n, --number <numbers...>', 'specify numbers')
.option('-l, --letter [letters...]', 'specify letters');

program.parse();

const options = program.opts();
console.log('process.argv', process.argv);
console.log(options);

运行:

$ node index -n 1 2 3
// { number: [ '1', '2', '3' ] }

版本选项

.version()方法可以设置版本,其默认选项为-V--version,设置了版本后,命令行会输出当前的版本号。

program.version('0.0.1')

运行

$ node index -V
// 0.0.1
$ node index --version

其他选项配置

大多数情况下,选项均可通过.option()方法添加。但对某些不常见的用例,也可以直接构造Option对象,对选项进行更详尽的配置。

const { program, Option } = require('commander');
program
  .addOption(new Option('-s, --secret').hideHelp())
  .addOption(new Option('-t, --timeout <delay>', 'timeout in seconds').default(60, 'one minute'))
  .addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large']))
  .addOption(new Option('-p, --port <number>', 'port number').env('PORT'))
  .addOption(new Option('--donate [amount]', 'optional donation in dollars').preset('20').argParser(parseFloat))
  .addOption(new Option('--disable-server', 'disables the server').conflicts('port'))
  .addOption(new Option('--free-drink', 'small drink included free ').implies({ drink: 'small' }));

program.parse();

const options = program.opts();
console.log(options);

自定义选项处理

选项的参数可以通过自定义函数来处理,该函数接收两个参数,即用户新输入的参数值和当前已有的参数值(定义的默认值),返回新的选项参数值。

const { program } = require('commander');
function myParseInt(value, dummyPrevious) {
  // parseInt 参数为字符串和进制数
  // value: 传递的选项值
  // dummyPrevious: 默认值: 20
  
  const parsedValue = parseInt(value, 10);
  if (isNaN(parsedValue)) {
    throw new commander.InvalidArgumentError('Not a number.');
  }
  return parsedValue;
}

program
  .option('-i, --integer <number>', 'integer argument', myParseInt, 20)
;

program.parse();

const options = program.opts();
console.log(options);

运行

$ node index -i
// { integer: 20 }
$ node index -i 16.89
// { integer: 16 }

命令

通过.command()可以配置命令,有两种实现方式:为命令绑定处理函数,或者将命令单独写成一个可执行文件。

.command()的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用.argument()单独指定。参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。

// 通过绑定处理函数实现命令(这里的指令描述为放在`.command`中)
// 返回新生成的命令(即该子命令)以供继续配置
program
  .command('clone <source> [destination]')
  .description('clone a repository into a newly created directory')
  .action((source, destination) => {
    console.log('clone command called');
  });

// 通过独立的的可执行文件实现命令 (注意这里指令描述是作为`.command`的第二个参数)
// 返回最顶层的命令以供继续添加子命令
program
  .command('start <service>', 'start named service')
  .command('stop [service]', 'stop named service, or all if no name supplied');

运行

Options:
  -h, --help                    display help for command

Commands:
  clone <source> [destination]  clone a repository into a newly created directory
  start <service>               start named service
  stop [service]                stop named service, or all if no name supplied
  help [command]                display help for command

命令参数

如上所述,子命令的参数可以通过.command()指定。对于有独立可执行文件的子命令来说,参数只能以这种方法指定。而对其他子命令,参数也可用以下方法。

Command对象上使用.argument()来按次序指定命令参数。该方法接受参数名称和参数描述。参数可为必选的(尖括号表示,例如<required>)或可选的(方括号表示,例如[optional]

const { program } = require('commander');
program
  .version('0.1.0')
  .argument('<username>', 'user to login')
  .argument('[password]', 'password for user, if required', 'no password given')
  .action((username, password) => {
    console.log('username:', username);
    console.log('password:', password);
  });
program.parse();

运行

$ node index blink 123456
// username: blink  password: 123456

自定义参数处理

选项的参数可以通过自定义函数来处理(与处理选项参数时类似),该函数接收两个参数:用户新输入的参数值和当前已有的参数值(定义的默认值),返回新的命令参数值。

program
  .command('add')
  .argument('<first>', 'integer argument', myParseInt)
  .argument('[second]', 'integer argument', myParseInt, 1000)
  .action((first, second) => {
    console.log(`${first} + ${second} = ${first + second}`);
  })
;

处理函数

命令处理函数的参数,为该命令声明的所有参数,除此之外还会附加两个额外参数:一个是解析出的选项,另一个则是该命令对象自身。

const { program } = require('commander');
program
  .argument('<name>')
  .option('-t, --title <honorific>', 'title to use before name')
  .option('-d, --debug', 'display some debugging')
  .action((name, options, command) => {
    console.log('name', name);
    console.log('options', options);
    console.log('command', command);
    
  });
program.parse();

运行

node index add -t titlename
// name title
// options { title: titlename }
// command 该命令对象自身

这是一个使用子命令并带有帮助描述的更完整的程序。在多命令程序中,每个命令(或命令的独立可执行文件)都有一个操作处理程序。

const { Command } = require('commander');
const program = new Command();

program
  .name('string-util')
  .description('CLI to some JavaScript string utilities')
  .version('0.8.0');

program.command('split')
  .description('Split a string into substrings and display as an array')
  .argument('<string>', 'string to split')
  .option('--first', 'display just the first substring')
  .option('-s, --separator <char>', 'separator character', ',')
  .action((str, options) => {
    const limit = options.first ? 1 : undefined;
    console.log(str.split(options.separator, limit));
  });

program.parse();