關(guān)于commander.js使用及源碼分析
commander.js
commander是一個輕巧的nodejs模塊,提供了用戶命令行輸入和參數(shù)解析強大功能。
commander的特性:
- 自記錄代碼
- 自動生成幫助
- 合并短參數(shù)
- 默認選項
- 強制選項??
- 命令解析
- 提示符
安裝
npm install commander
使用
在文件中引入commander,可以通過直接引入一個program對象或者創(chuàng)建一個實例的方式進行使用。
const { program } = require('commander');
program.version("v1.0.0");const { Command } = require('commander');
const program = new Command();
program.version("v1.0.0");通過查看源碼可以得知program其實就是一個剛創(chuàng)建的實例,可以更明確地訪問全局命令。
源碼片段:
exports = module.exports = new Command(); exports.program = exports; exports.Command = Command;
option選項
Commander 使用.option()方法來定義選項,同時可以附加選項的簡介。
每個選項可以定義一個短選項名稱(-后面接單個字符)和一個長選項名稱(--后面接一個或多個單詞),使用逗號、空格或|分隔。
語法:
options(flags, description, fn, defaultValue)
commander.option(
"-f, --filename [filename]",
"The filename to use when reading from stdin. This will be used in source-maps, errors etc."
);源碼解析:
lib\command.js option()
采用柯里化寫法, 實際調(diào)用的是_optionsEx()方法
options(flags, description, fn, defaultValue) {
return this._optionEx({}, flags, description, fn, defaultValue);
}_optionsEx(), 創(chuàng)建一個options實例,fn可以是函數(shù)、正則等形式,需要注意的是盡量不要使用正則形式,自 Commander v7 起,該功能不再推薦使用。
_optionEx(config, flags, description, fn, defaultValue) {
// 創(chuàng)建一個option實例
const option = this.createOption(flags, description);
if (typeof fn === "function") {
option.default(defaultValue).argParser(fn);
} else if (fn instanceof RegExp) {
// deprecated
...
} else {
option.default(fn);
}
return this.addOption(option);
}在Option構(gòu)造函數(shù)中,會調(diào)用一個splitOptionFlags()方法,用于解析長、斷標識,比如說-m,--mixed <value>。
attributeName()會返回一個camelcase的字符,例如--file-name會被解析成fileName。
通過空格、|和,來切割出不同的字符。
function splitOptionFlags(flags) {
let shortFlag;
let longFlag;
const flagParts = flags.split(/[ |,]+/);
if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
shortFlag = flagParts.shift();
longFlag = flagParts.shift();
if (!shortFlag && /^-[^-]$/.test(longFlag)) {
shortFlag = longFlag;
longFlag = undefined;
}
return { shortFlag, longFlag };
}同時,根據(jù)flags中的字符進行判斷是否有設(shè)置接收參數(shù)。
<value>表示執(zhí)行該命令時必須傳入一個參數(shù),<value...>表示可接受多個參數(shù),[value]表示可配置參數(shù)。
this.required = flags.includes("<");
this.optional = flags.includes("[");
this.variadic = /\w\.\.\.[>\]]$/.test(flags)在創(chuàng)建了一個option對象后,將其放入addOption()中進行處理,該方法會對option進行注冊、監(jiān)聽等操作。
addOption(option) {
const oname = option.name();
const name = option.attributeName();
// 注冊
this.options.push(option);
const handleOptionValue = (val, invalidValueMessage, valueSource) => {
// ...
};
this.on("option:" + oname, (val) => {
const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
handleOptionValue(val, invalidValueMessage, "cli");
});
if (option.envVar) {
this.on("optionEnv:" + oname, (val) => {
const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
handleOptionValue(val, invalidValueMessage, "env");
});
}
return this;
}Command繼承了node中的事件模塊EventEmitter實現(xiàn)對option的監(jiān)聽和觸發(fā)。
在寫好option配置后,需要調(diào)用program.parse(process.argv)方法對用戶的輸入進行解析。
parse(argv, parseOptions)
parse(argv, parseOptions) {
const userArgs = this._prepareUserArgs(argv, parseOptions);
this._parseCommand([], userArgs);
return this;
}在_prepareUserArgvs方法中解析讀取用戶的輸入。
process.argv獲取的數(shù)組前兩個元素分別是node安裝地址跟運行的script路徑,后邊的才是用戶的輸入,因此需要先過濾掉這兩個。
同時還支持多個argv約定。
_prepareUserArgs(argv, parseOptions) {
parseOptions = parseOptions || {};
this.rawArgs = argv.slice();
let userArgs;
switch (parseOptions.from) {
case undefined:
case 'node':
this._scriptPath = argv[1];
userArgs = argv.slice(2);
break;
case 'electron':
if (process.defaultApp) {
this._scriptPath = argv[1];
userArgs = argv.slice(2);
} else {
userArgs = argv.slice(1);
}
break;
case 'user':
userArgs = argv.slice(0);
break;
default:
throw new Error(
`unexpected parse option { from: '${parseOptions.from}' }`
);
}
if (!this._scriptPath && require.main) {
this._scriptPath = require.main.filename;
}
this._name =
this._name ||
(this._scriptPath &&
path.basename(this._scriptPath, path.extname(this._scriptPath)));
return userArgs;
}在獲取了用戶輸入后,就是對這些參數(shù)進行解析。
_parseCommand()
_parseCommand(operands, unknown) {
const parsed = this.parseOptions(unknown);
// command部分下邊繼續(xù)講解
}parseOptions讀取輸入的配置
parseOptions(argv) {
const operands = [];
const unknown = [];
let dest = operands;
const args = argv.slice();
// 判斷是否是option配置, 必須要有一個-開頭
function maybeOption(arg) {
return arg.length > 1 && arg[0] === '-';
}
let activeVariadicOption = null;
// 逐步讀取配置
while (args.length) {
const arg = args.shift();
// --終止讀取 也就是說 -- 后邊的配置并不會生效
// 例如 node src/index.js - --number 1 2 3 -- -c a b
// -c其實沒有用
if (arg === '--') {
if (dest === unknown) dest.push(arg);
dest.push(...args);
break;
}
// 處理多參數(shù)情況
if (activeVariadicOption && !maybeOption(arg)) {
// 當(dāng)觸發(fā)這個監(jiān)聽時會執(zhí)行handleOptionValue,里邊會判斷variadic的值將所有的參數(shù)放進一個數(shù)組中
this.emit(`option:${activeVariadicOption.name()}`, arg);
continue;
}
activeVariadicOption = null;
if (maybeOption(arg)) {
// 查看是否已經(jīng)配置有該命令
const option = this._findOption(arg);
if (option) {
// 前邊提到的當(dāng)配置option時如果有配置 <value> 必填參數(shù),那么該option的required值為true
if (option.required) {
// 讀取value
const value = args.shift();
if (value === undefined) this.optionMissingArgument(option);
// 觸發(fā)監(jiān)聽方法
this.emit(`option:${option.name()}`, value);
// 配置了可選參數(shù)
} else if (option.optional) {
let value = null;
if (args.length > 0 && !maybeOption(args[0])) {
value = args.shift();
}
this.emit(`option:${option.name()}`, value);
} else {
// boolean flag
this.emit(`option:${option.name()}`);
}
// 前面提到當(dāng)配置的option中存在 ...> 如<value...>時,variadic值為true
activeVariadicOption = option.variadic ? option : null;
continue;
}
}
// 組合flag, 比如像 -abc , 會遍歷判斷 -a -b -c是否是配置過的option
if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
// 拆解組合
const option = this._findOption(`-${arg[1]}`);
if (option) {
if (
option.required ||
(option.optional && this._combineFlagAndOptionalValue)
) {
this.emit(`option:${option.name()}`, arg.slice(2));
} else {
this.emit(`option:${option.name()}`);
args.unshift(`-${arg.slice(2)}`);
}
continue;
}
}
// 解析--foo=bar傳參格式
if (/^--[^=]+=/.test(arg)) {
const index = arg.indexOf('=');
const option = this._findOption(arg.slice(0, index));
if (option && (option.required || option.optional)) {
this.emit(`option:${option.name()}`, arg.slice(index + 1));
continue;
}
}
if (maybeOption(arg)) {
dest = unknown;
}
// 解析command 下面在講解
dest.push(arg);
}
return { operands, unknown };
}從上面的源碼解析中可以總結(jié)如下:
- 多個短選項可以合并簡寫,最后一個選項可以附加參數(shù), 比如
-a -b -c 1可以寫成-abc 1。 --可以標記選項的結(jié)束,后續(xù)的參數(shù)均不會被命令解釋,可以正常使用。- 可以通過
=傳參 - 可以傳遞多個參數(shù)
常用選項類型
boolean型, 選項無需配置參數(shù), 通常我們使用的都是此類型。- 設(shè)置參數(shù)(使用尖括號聲明在該選項后,如
--expect <value>),如果在命令行中不指定具體的選項及參數(shù),則會被定義為undefined。
const { Command } = require('commander');
const program = new Command();
program.option('-e --example', 'this is a boolean type option');
program.option('-t --type <type>', 'must set an param or an error will occur');
program.parse(process.argv);
console.log(program.opts());eg.
node index.js -e
{ example: true }
node index.js -t
error: option '-t --type <type>' argument missing
node index.js -t a
{ type: 'a' }取反, 可以定義一個以 no- 開頭的 boolean 型長選項。
在命令行中使用該選項時,會將對應(yīng)選項的值置為 false。
當(dāng)只定義了帶 no- 的選項,未定義對應(yīng)不帶 no- 的選項時,該選項的默認值會被置為 true值。
program.option('--no-example', 'no example');node index.js --no-example
{ example: false }源碼解析:
在配置了option之后,會對該命令進行監(jiān)聽,執(zhí)行該flag會觸發(fā)handleOptionValue方法,根據(jù)用戶是否有設(shè)置取反或者有默認值,來設(shè)置該option的值
handleOptionValue
const handleOptionValue = (val, invalidValueMessage, valueSource) => {
const oldValue = this.getOptionValue(name);
// 參數(shù)處理這部分放在自定義選項部分講解
// ..
if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
if (val == null) {
this.setOptionValueWithSource(
name,
// 是否取反,取反的話值為false, 不取反就判斷是否有值,沒有就賦值為true
option.negate ? false : defaultValue || true,
valueSource
);
} else {
this.setOptionValueWithSource(name, val, valueSource);
}
} else if (val !== null) {
this.setOptionValueWithSource(
name,
option.negate ? false : val,
valueSource
);
}
};可選參數(shù) (--optional [value]), 該選項在不帶參數(shù)時可用作 boolean 選項,在帶有參數(shù)時則從參數(shù)中得到值。
program.option('-f [filename]', 'optional');node index.js -f
{ f: true }
node index.js -f index.js
{ f: 'index.js' }必填選項
通過.requiredOption()方法可以設(shè)置選項為必填。
必填選項要么設(shè)有默認值,要么必須在命令行中輸入,對應(yīng)的屬性字段在解析時必定會有賦值。該方法其余參數(shù)與.option()一致。
program.requiredOption('-r --required <type>', 'must');node index.js error: required option '-r --required <type>' not specified
當(dāng)然可以設(shè)置一個默認值
program.requiredOption('-r --required <type>', 'must', 'a');node index.js
{ required: 'a' }變長參數(shù)選項
定義選項時,可以通過使用...來設(shè)置參數(shù)為可變長參數(shù)。在命令行中,用戶可以輸入多個參數(shù),解析后會以數(shù)組形式存儲在對應(yīng)屬性字段中。在輸入下一個選項前(-或--開頭),用戶輸入的指令均會被視作變長參數(shù)。與普通參數(shù)一樣的是,可以通過--標記當(dāng)前命令的結(jié)束。
program.option('-n <numbers...>', 'set numbers');
program.option('-c <chars...>', 'set chars');node index.js -n 1 2 3 4 -c a b c
{ n: [ '1', '2', '3', '4' ], c: [ 'a', 'b', 'c' ] }版本
.version()方法可以設(shè)置版本,其默認選項為-V和--version,設(shè)置了版本后,命令行會輸出當(dāng)前的版本號。
program.version('v1.0.0')版本選項也支持自定義設(shè)置選項名稱,可以在.version()方法里再傳遞一些參數(shù)(長選項名稱、描述信息),用法與.option()方法類似。
program.version('v1.0.0', '-a --aversion', 'current version');源碼解析:
version方法其實很簡單,就是判斷用戶配置version信息時有沒有自定義信息,比如說啟動命令這些。之后就是創(chuàng)建一個option注冊后監(jiān)聽。
version(str, flags, description) {
if (str === undefined) return this._version;
this._version = str;
flags = flags || '-V, --version';
description = description || 'output the version number';
const versionOption = this.createOption(flags, description);
this._versionOptionName = versionOption.attributeName();
this.options.push(versionOption);
this.on('option:' + versionOption.name(), () => {
this._outputConfiguration.writeOut(`${str}\n`);
this._exit(0, 'commander.version', str);
});
return this;
}使用addOption方法
大多數(shù)情況下,選項均可通過.option()方法添加。但對某些不常見的用例,也可以直接構(gòu)造Option對象,對選項進行更詳盡的配置。
program
.addOption(
new Option('-t, --timeout <delay>', 'timeout in seconds').default(
60,
'one minute'
)
)
.addOption(
new Option('-s, --size <type>', 'size').choices([
'small',
'medium',
'large',
])
);node index.js -s small
{ timeout: 60, size: 'small' }
node index.js -s mini
error: option '-s, --size <type>' argument 'mini' is invalid. Allowed choices are small, medium, large.源碼解析:
就是添加一個fn在傳入值的時候判斷一下值就可以。
function choices(values) {
this.argChoices = values;
this.parseArg = (arg, previous) => {
if (!values.includes(arg)) {
throw new InvalidArgumentError(
`Allowed choices are ${values.join(', ')}.`
);
}
if (this.variadic) {
return this._concatValue(arg, previous);
}
return arg;
};
return this;
}自定義選項
選項的參數(shù)可以通過自定義函數(shù)來處理,該函數(shù)接收兩個參數(shù),即用戶新輸入的參數(shù)值和當(dāng)前已有的參數(shù)值(即上一次調(diào)用自定義處理函數(shù)后的返回值),返回新的選項參數(shù)值。 也就是option中的第三個參數(shù)。第四個參數(shù)就是初始值
自定義函數(shù)適用場景包括參數(shù)類型轉(zhuǎn)換,參數(shù)暫存,或者其他自定義處理的場景。
program.option(
'-f --float <number>',
'process float argument',
(value, previous) => {
return Number(value) + previous;
},
2
);node index.js -f 3
{ float: 5 }源碼解析:
const handleOptionValue = (val, invalidValueMessage, valueSource) => {
const oldValue = this.getOptionValue(name);
// 當(dāng)配置了option的第三個參數(shù)
if (val !== null && option.parseArg) {
try {
val = option.parseArg(
val,
// defaultValue為第四個參數(shù)
oldValue === undefined ? defaultValue : oldValue
);
} catch (err) {
if (err.code === "commander.invalidArgument") {
const message = `${invalidValueMessage} ${err.message}`;
this._displayError(err.exitCode, err.code, message);
}
throw err;
}
// 處理多參數(shù)情況
} else if (val !== null && option.variadic) {
val = option._concatValue(val, oldValue);
}
// ...
// 查看取反部分講解
}配置命令
通過.command()或.addCommand()可以配置命令,有兩種實現(xiàn)方式:為命令綁定處理函數(shù),或者將命令單獨寫成一個可執(zhí)行文件。子命令支持嵌套。
.command()的第一個參數(shù)為命令名稱。命令參數(shù)可以跟在名稱后面,也可以用.argument()單獨指定。參數(shù)可為必選的(尖括號表示)、可選的(方括號表示)或變長參數(shù)(點號表示,如果使用,只能是最后一個參數(shù))。
program
.command('clone <source> [destination]')
.description('clone a repository into a newly created directory')
.action((source, destination) => {
console.log('clone command called');
});在Command對象上使用.argument()來按次序指定命令參數(shù)。該方法接受參數(shù)名稱和參數(shù)描述。參數(shù)可為必選的(尖括號表示,例如<required>)或可選的(方括號表示,例如[optional])。
program
.command('login')
.description('login')
.argument('<username>', 'user')
.argument('[password]', 'password', 'no password')
.action((username, password) => {
console.log(`login, ${username} - ${password}`);
});在參數(shù)名后加上...來聲明可變參數(shù),且只有最后一個參數(shù)支持這種用法??勺儏?shù)會以數(shù)組的形式傳遞給處理函數(shù)。
program
.command('readFile')
.description('read multiple file')
.argument('<username>', 'user')
.argument('[password]', 'password', 'no password')
.argument('<filepath...>')
.action((username, password, args) => {
args.forEach((dir) => {
console.log('rmdir %s', dir);
});
console.log(`username: ${username}, pass: ${password}, args: ${args}`);
});源碼解析:
新建一個command
command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
let desc = actionOptsOrExecDesc;
let opts = execOpts;
if (typeof desc === 'object' && desc !== null) {
opts = desc;
desc = null;
}
opts = opts || {};
// 解析獲取輸入的命令及參數(shù)
const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
// 創(chuàng)建一個command
const cmd = this.createCommand(name);
if (desc) {
cmd.description(desc);
cmd._executableHandler = true;
}
if (opts.isDefault) this._defaultCommandName = cmd._name;
cmd._hidden = !!(opts.noHelp || opts.hidden);
cmd._executableFile = opts.executableFile || null;
// 添加參數(shù)
if (args) cmd.arguments(args);
this.commands.push(cmd);
cmd.parent = this;
// 繼承屬性
cmd.copyInheritedSettings(this);
if (desc) return this;
return cmd;
}action用于注冊命令回調(diào)。
action(fn) {
const listener = (args) => {
const expectedArgsCount = this._args.length;
const actionArgs = args.slice(0, expectedArgsCount);
if (this._storeOptionsAsProperties) {
actionArgs[expectedArgsCount] = this;
} else {
actionArgs[expectedArgsCount] = this.opts();
}
actionArgs.push(this);
return fn.apply(this, actionArgs);
};
this._actionHandler = listener;
return this;
}接下來就是如何解析執(zhí)行命令部分了。還記得我們之前解析option時的流程嘛
parse() -> _parseCommand(), 在parseOptions方法中,我們還剩余了處理命令部分代碼,現(xiàn)在來看一下。
源碼解析:
parseOptions(argv) {
const operands = [];
const unknown = [];
let dest = operands;
const args = argv.slice();
while (args.length) {
// 解析option部分
// ...
// 當(dāng)開啟了enablePositionalOptions后, 會將命令后邊的參數(shù)全部定義為未知參數(shù),而不是命令選項,同時如果想要在子命令中啟動enablePositionalOptions,需要在父命令中先開啟。
if (
(this._enablePositionalOptions || this._passThroughOptions) &&
operands.length === 0 &&
unknown.length === 0
) {
if (this._findCommand(arg)) {
operands.push(arg);
if (args.length > 0) unknown.push(...args);
break;
} else if (
arg === this._helpCommandName &&
this._hasImplicitHelpCommand()
) {
operands.push(arg);
if (args.length > 0) operands.push(...args);
break;
} else if (this._defaultCommandName) {
unknown.push(arg);
if (args.length > 0) unknown.push(...args);
break;
}
}
if (this._passThroughOptions) {
dest.push(arg);
if (args.length > 0) dest.push(...args);
break;
}
dest.push(arg);
}
return { operands, unknown };
}沒有開啟enablePositionalOptions配置,命令及后邊的參數(shù)都會保存在operands數(shù)組中,當(dāng)開啟后,命令后邊的參數(shù)都會變成unknown。
在解析了用戶輸入的參數(shù)后繼續(xù)往下執(zhí)行_parseCommand方法
_parseCommand(operands, unknown) {
// 解析配置
const parsed = this.parseOptions(unknown);
this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
operands = operands.concat(parsed.operands);
unknown = parsed.unknown;
this.args = operands.concat(unknown);
// 匹配找到命令, 使用子進程運行命令
if (operands && this._findCommand(operands[0])) {
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
}
// 沒找到命令就判斷一下是否有幫助命令, 而且用戶輸入的第一個參數(shù)是help
if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
// 有且只有個命令
if (operands.length === 1) {
this.help();
}
// 執(zhí)行第二條命令
return this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]);
}
// 幫助命令也沒有找到或者用戶輸入的第一個參數(shù)未知但是存在一個默認名字
if (this._defaultCommandName) {
outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
return this._dispatchSubcommand(
this._defaultCommandName,
operands,
unknown
);
}
// ...
const commandEvent = `command:${this.name()}`;
// 存在command處理器
if (this._actionHandler) {
checkForUnknownOptions();
// 處理參數(shù)
this._processArguments();
let actionResult;
actionResult = this._chainOrCallHooks(actionResult, 'preAction');
actionResult = this._chainOrCall(actionResult, () =>
this._actionHandler(this.processedArgs)
);
// 觸發(fā)父命令
if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy
actionResult = this._chainOrCallHooks(actionResult, 'postAction');
return actionResult;
}
// 觸發(fā)父監(jiān)聽器
if (this.parent && this.parent.listenerCount(commandEvent)) {
checkForUnknownOptions();
this._processArguments();
this.parent.emit(commandEvent, operands, unknown);
// 處理參數(shù)
} else if (operands.length) {
if (this._findCommand('*')) {
return this._dispatchSubcommand('*', operands, unknown);
}
if (this.listenerCount('command:*')) {
this.emit('command:*', operands, unknown);
} else if (this.commands.length) {
this.unknownCommand();
} else {
checkForUnknownOptions();
this._processArguments();
}
// 不存在命令
} else if (this.commands.length) {
checkForUnknownOptions();
this.help({ error: true });
} else {
checkForUnknownOptions();
this._processArguments();
}
}到此,整個解析過程就完成了。
聲明統(tǒng)一參數(shù)
命令處理函數(shù)的參數(shù),為該命令聲明的所有參數(shù),除此之外還會附加兩個額外參數(shù):一個是解析出的選項,另一個則是該命令對象自身。
program
.argument('<name>')
.option('-t, --title <title>', 'title to use before name')
.option('-d, --de')
.action((name, options, command) => {
console.log(name);
console.log(options);
console.log(command.name());
});幫助信息
幫助信息是 Commander 基于你的程序自動生成的,默認的幫助選項是-h,--help。
node index.js -h Usage: index [options] Options: -h, --help display help for command
源碼解析:
commander.js提供了一個help類,但是它的方法都是靜態(tài)的,允許被覆蓋。
在command類中有一個createHelp方法,通過Object.assign()方法覆蓋help的原始方法。
createHelp() {
return Object.assign(new Help(), this.configureHelp());
}formatHelp生成幫助文字,解析處理設(shè)置的command、option、argument等
formatHelp(cmd, helper) {
const termWidth = helper.padWidth(cmd, helper);
const helpWidth = helper.helpWidth || 80;
const itemIndentWidth = 2;
const itemSeparatorWidth = 2;
// 格式化
function formatItem(term, description) {
if (description) {
const fullText = `${term.padEnd(
termWidth + itemSeparatorWidth
)}${description}`;
return helper.wrap(
fullText,
helpWidth - itemIndentWidth,
termWidth + itemSeparatorWidth
);
}
return term;
}
function formatList(textArray) {
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
}
let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
// 描述符
const commandDescription = helper.commandDescription(cmd);
if (commandDescription.length > 0) {
output = output.concat([commandDescription, '']);
}
// 參數(shù)
const argumentList = helper.visibleArguments(cmd).map((argument) => {
return formatItem(
helper.argumentTerm(argument),
helper.argumentDescription(argument)
);
});
if (argumentList.length > 0) {
output = output.concat(['Arguments:', formatList(argumentList), '']);
}
// 選項
const optionList = helper.visibleOptions(cmd).map((option) => {
return formatItem(
helper.optionTerm(option),
helper.optionDescription(option)
);
});
if (optionList.length > 0) {
output = output.concat(['Options:', formatList(optionList), '']);
}
// 命令
const commandList = helper.visibleCommands(cmd).map((cmd) => {
return formatItem(
helper.subcommandTerm(cmd),
helper.subcommandDescription(cmd)
);
});
if (commandList.length > 0) {
output = output.concat(['Commands:', formatList(commandList), '']);
}
return output.join('\n');
}自定義
使用addHelpText方法添加額外的幫助信息。
program.addHelpText('after', `call help`);node index.js -h Usage: index [options] Options: -h, --help display help for command call help
addHelpText方法的第一個參數(shù)是添加的幫助信息展示的位置,
包括如下:
beforeAll:作為全局標頭欄展示before:在內(nèi)建幫助信息之前展示after:在內(nèi)建幫助信息之后展示afterAll:作為全局末尾欄展示
源碼解析:
在執(zhí)行addHelpText方法后,會對該事件進行監(jiān)聽,同時執(zhí)行輸入幫助命令時會按順序執(zhí)行所有事件,從而實現(xiàn)自定義信息的展示。
addHelpText
addHelpText(position, text) {
const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
// 過濾
if (!allowedValues.includes(position)) {
throw new Error(`Unexpected value for position to addHelpText.
Expecting one of '${allowedValues.join("', '")}'`);
}
const helpEvent = `${position}Help`;
// 監(jiān)聽事件
this.on(helpEvent, (context) => {
let helpStr;
if (typeof text === 'function') {
helpStr = text({ error: context.error, command: context.command });
} else {
helpStr = text;
}
// Ignore falsy value when nothing to output.
if (helpStr) {
context.write(`${helpStr}\n`);
}
});
return this;
}outputHelp
outputHelp(contextOptions) {
let deprecatedCallback;
if (typeof contextOptions === 'function') {
deprecatedCallback = contextOptions;
contextOptions = undefined;
}
const context = this._getHelpContext(contextOptions);
// 遍歷觸發(fā)所有祖先命令的 beforeAllHelp 事件
getCommandAndParents(this)
.reverse()
// 觸發(fā) beforeAllHelp
.forEach((command) => command.emit('beforeAllHelp', context));
// 觸發(fā) beforeHelp
this.emit('beforeHelp', context);
let helpInformation = this.helpInformation(context);
if (deprecatedCallback) {
helpInformation = deprecatedCallback(helpInformation);
if (
typeof helpInformation !== 'string' &&
!Buffer.isBuffer(helpInformation)
) {
throw new Error('outputHelp callback must return a string or a Buffer');
}
}
context.write(helpInformation);
// 觸發(fā) help 指令
this.emit(this._helpLongFlag); // deprecated
// 觸發(fā) afterHelp
this.emit('afterHelp', context);
// 遍歷觸發(fā)所有祖先命令的 afterAllHelp 事件
getCommandAndParents(this).forEach((command) =>
// 觸發(fā) afterAllHelp
command.emit('afterAllHelp', context)
);
}showHelpAfterError展示幫助信息
program.showHelpAfterError();
// 或者
program.showHelpAfterError('(add --help for additional information)');node index.js -asd error: unknown option '-asd' (add --help for additional information)
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Node.js巧妙實現(xiàn)Web應(yīng)用代碼熱更新
本文給大家講解的是Node.js的代碼熱更新的問題,其主要實現(xiàn)原理 是怎么對 module 對象做處理,也就是手工監(jiān)聽文件修改, 然后清楚模塊緩存, 重新掛載模塊,思路清晰考慮細致, 雖然有點冗余代碼,但還是推薦給大家2015-10-10
nodeJS服務(wù)器的創(chuàng)建和重新啟動的實現(xiàn)方法
今天小編就為大家分享一篇nodeJS服務(wù)器的創(chuàng)建和重新啟動的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05
使用nodejs?spider爬取圖片及數(shù)據(jù)實現(xiàn)
這篇文章主要為大家介紹了使用nodejs?spider爬取圖片及數(shù)據(jù)實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07

