一、作用
- 作用:提供了用户命令行输入和参数解析强大功能(人话:强大解析process.argv功能及自动生成help)
- 写作缘由:npm官网案例不多,且看完只知道api的大概使用规则,不好快速理解起概念,这边来理顺
二、分析命令行结构结构
- 以上作为一个输入单元,[]代表都是可选的
- 👇来分析一些结构
- git add . // 子命令add;子命令值'.'
- git commit -m 'feat: 完成101迭代ui' // 子命令commit;选项 -m;选项值 "feat: 完成101迭代u"
- ⬆️ 最后会完成git的操作命令
三、分析commander要做什么
- 既然主要作用是解析命令行参数,然后doSomeThing,及生成友好的help提示
- 我们由上可知,细化颗粒有:命令、选项、参数、及捕获相关的、及描述(用于提示)
- command: 用于注册子命令(主代码其实就是主命令了),及该命令的描述
- option: 用于注册选项及接收参数,及该选项的描述
- version:用于标记当前命令的版本(可以理解为简化版选项)
- arguments: 用于定义当前命令的接收参数
- description:用于描述当前命令及该命令参数的描述
- => 也就是我们可以由以上注册单元来描述我们如何去解析命令行(正则解析)
- => commander根据我们的写的解析注册单元来 生成 相应的ast树(有子命令,即对应ast子节点)
- => 需要注意的是因为选项
- 以下,会重点分析各个注册解析单元怎么玩
四、命令
- 我们的代码其实就是主命令
- 这个命令的回调是注册一个action(理解主命令)
program
.action((t) => {
console.log('top action call',t)
})
.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1')
.action((t) => {
console.log('child1 action call')
})
program.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1')
.action((t) => {
console.log('child1 action call')
})
.command('cmd11')
.action((t) => {
console.log('child11 action call')
})
program
.command('cmd2')
.action((t) => {
console.log('child2 action call')
})
program.parse(process.argv);
- 命令 + 参数(理解参数的必传、选传、和...及action回调参数的值)
program
.command('cmd1 <arg1> [arg2] [arg3...]')
.action((...t) => {
console.log('child1 action call',t)
})
program.parse(process.argv);
- 命令 + 配置({isDefault:true})
program
.command('cmd1 <arg1> [arg2] [arg3...]',{isDefault:true})
.action((...t) => {
console.log('child1 action call',t)
})
program.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1')
.action((t) => {
console.log('child1 action call')
})
.command('cmd11',{isDefault:true})
.action((t) => {
console.log('child11 action call')
})
program.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1',{isDefault:true})
.action((t) => {
console.log('child1 action call')
})
.command('cmd11',{isDefault:true})
.action((t) => {
console.log('child11 action call')
})
program.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1')
.action((t) => {
console.log('child1 action call')
})
program.parse(process.argv);
program
.action((t) => {
console.log('top action call')
})
.command('cmd1',{hidden:true})
.action((t) => {
console.log('child1 action call')
})
program.parse(process.argv);
program
.description('主命令描述')
.action((...t) => {
console.log('top action call',t)
})
.parse(process.argv);
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述')
.action((...t) => {
console.log('top action call',t)
})
.parse(process.argv);
program
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述',{
arg1:'这个是arg1 的描述',
arg2:'这个是arg2 的描述',
arg3:'这个是arg3 的描述',
})
.action((...t) => {
console.log('top action call',t)
})
.parse(process.argv);
program
.arguments('<arg1> [arg2] [arg3]')
.description('主命令描述',{
arg1:'这个是arg1 的描述',
arg2:'这个是arg2 的描述',
arg3:'这个是arg3 的描述',
})
.action((...t) => {
console.log('top action call',t)
})
.command('cmd1 <carg1> [carg2]')
.description('子命令1描述',{
carg1:'这个是carg1 的描述',
carg2:'这个是carg2 的描述',
})
program.parse(process.argv);
五、选项
program
.option('-a,--add','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
Options:
-a,--add add something
-h, --help display help for command
program
.option('-a,--add <arg1>','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
program
.option('-a,--add [arg1] [arg2]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
program
.option('-a,--add [arg1...]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
program
.option('-a,--add [arg1...]','add something')
.parse(process.argv);
const options = program.opts()
console.log(options)
const addFormat = (value,Accumulator) => {
console.log(value,Accumulator)
return value
}
program
.option('-a,--add [arg1]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
1 undefined
{ add: '1' }
const addFormat = (value,Accumulator) => {
console.log(value,Accumulator)
return '我来修改返回值' + value
}
program
.option('-a,--add [arg1]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
1 undefined
{ add: '我来修改返回值1' }
1 undefined
2 我来修改返回值1
{ add: '我来修改返回值2' }
const addFormat = (value,Accumulator) => {
console.log(value,Accumulator)
return '我来修改返回值' + value
}
program
.option('-a,--add [arg1...]','add something',addFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
1 undefined
2 我来修改返回值1
3 我来修改返回值2
4 我来修改返回值3
{ add: '我来修改返回值4' }
program
.option('-a,--add [arg1]','add something',1)
.parse(process.argv);
const options = program.opts()
console.log(options)
const addFormat = (value,Accumulator) => {
console.log(value,Accumulator)
return value
}
program
.option('-a,--add [arg1]','add something',addFormat,1)
.parse(process.argv);
const options = program.opts()
console.log(options)
2 1
{ add: '2' }
- 选项 + 参数 + 入参格式函数(具体案例加深理解)
const intFormat = (value,Accumulator) => {
const formatValue = parseInt(value,10)
if(isNaN(formatValue)) {
throw new program.InvalidOptionArgumentError('Not a number.');
}
return formatValue
}
program
.option('-i,--integer [arg1...]','to integer',intFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
error: option '-i,--integer [arg1...]' argument 'ad' is invalid. Not a number.
const splitFormat = (value,Accumulator) => {
return value.split(', ')
}
program
.option('-s,--split [arg1...]','to integer',splitFormat)
.parse(process.argv);
const options = program.opts()
console.log(options)
const collectFormat = (value,Accumulator) => {
Accumulator.push(value)
return Accumulator
}
program
.option('-c,--collect [arg1...]','to integer',collectFormat,[])
.parse(process.argv);
const options = program.opts()
console.log(options)
const statisticsFormat = (value,Accumulator) => {
Accumulator +=1
console.log('currentValue',value,'execCounter',Accumulator)
return Accumulator
}
program
.option('-s,--statistics [arg1...]','to integer',statisticsFormat,0)
.parse(process.argv);
const options = program.opts()
console.log(options)
currentValue a execCounter 1
currentValue b execCounter 2
currentValue c execCounter 3
{ statistics: 3 }
program
.version('0.1.1','-v,--version','版本描述')
.parse(process.argv)
Options:
-v,--version 版本描述
-h, --help display help for command
六、参数(以下内容上面都有涉及,主要是总结和加深理解)
program
.action((...t)=>{
console.log('top action call',t)
})
.parse()
program
.arguments('<arg1> [arg2]')
.action((...t)=>{
console.log('top action call',t)
})
.parse()
program
.arguments('<arg1> [arg2]')
.action((...t)=>{
console.log('top action call',t)
})
.command('cmd1 <cArg1> [cArg2] [cArg3]')
.action((...t)=>{
console.log('child1 action call',t)
})
program.parse()
program
.option('-a,--add [arg1...]')
program.parse()
-a,--add [arg1...]
七、描述(description)
program
.description('这个是top命令的描述')
program.parse()
program
.command('cmd1')
.description('这个是child1命令的描述')
program.parse()
Commands:
cmd1 这个是child1命令的描述
program
.command('cmd1','这个是child1命令的描述')
program.parse()
Commands:
cmd1 这个是child1命令的描述
program
.option('cmd1','这个是选项的描述')
program.parse()
Options:
cmd1 这个是选项的描述
program
.arguments('[arg1] <arg2>')
.description('这个是top命令的描述',{
arg1:'arg1 的描述',
arg2:'arg2 的描述'
})
program.parse()
这个是top命令的描述
Arguments:
arg1 arg1 的描述
arg2 arg2 的描述
program
.command('cmd1 [cArg1] [cArg2]')
.description('这个是child1命令的描述',{
cArg1:'cArg1 的描述',
cArg2:'cArg2 的描述'
})
program.parse()
这个是child1命令的描述
Arguments:
cArg1 cArg1 的描述
cArg2 cArg2 的描述
八、总结:来git来做案例
#!/usr/local/bin/node
const program = require('commander');
const { execSync } = require('child_process');
const run = () => {
program
.version('2.24.3','-v,--version')
.description('git command')
const addCmd =program.command('add <path>')
.description('This command updates the index using the current content...')
.option('-f,--force','Allow adding otherwise ignored files.')
.option('-a, --all','Update the index not only where the working tree...')
.action((...args)=>{
const [path,options,currentCmdRef] = args
let gitCmd = `git add ${path}`
if(options.force) {
gitCmd = `git add ${path} --force`
}
if(options.all) {
gitCmd = `git add ${path} --all`
}
try {
execSync(gitCmd)
} catch(res){console.log(res)}
})
const commitCmd = program.command('commit')
.description('Create a new commit containing the current contents of the index...')
.option('-m,--message <msg>','Use the given <msg> as the commit message.')
.option('-e,--edit','The message taken from file with -F...')
.option('--amend','Replace the tip of the current branch by creating a new commit')
.action((...args)=>{
const [options,currentCmdRef] = args
const currentOptions = currentCmdRef.opts()
let gitCmd = ``
if(currentOptions.message) {
gitCmd = `git commit -m ${currentOptions.message}`
}
if(currentOptions.edit) {
gitCmd = `git commit --edit`
}
if(options.amend) {
gitCmd = `git commit --amend`
}
try {
execSync(gitCmd)
} catch(res){console.log(res)}
})
const statusCmd = program.command('status')
.description('Displays paths that have differences between the index file')
.option('-s, --short','Give the output in the short-format')
.option('-b, --branch','Show the branch and tracking info even in short-format')
.action((...args)=>{
const [options] = args
console.log(args)
let gitCmd = ``
if(options.short) {
gitCmd = `git status --short`
}
if(options.branch) {
gitCmd = `git cstatus --branch`
}
try {
execSync(gitCmd)
} catch(res){console.log(res)}
})
program.parse(process.argv);
}
run()