最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關于commander.js使用及源碼分析

 更新時間:2023年06月06日 11:00:39   作者:dralexsanderl  
這篇文章主要介紹了關于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ù)。

<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)) {
      // 當觸發(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) {
          // 前邊提到的當配置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()}`);
        }
        // 前面提到當配置的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ù)(使用尖括號聲明在該選項后,如--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長選項。

在命令行中使用該選項時,會將對應選項的值置為 false。

當只定義了帶 no- 的選項,未定義對應不帶 no- 的選項時,該選項的默認值會被置為 true值。

program.option('--no-example', 'no example');
node index.js --no-example
{ example: false }

源碼解析:

在配置了option之后,會對該命令進行監(jiān)聽,執(zhí)行該flag會觸發(fā)handleOptionValue方法,根據(jù)用戶是否有設置取反或者有默認值,來設置該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ù)與.option()一致。

program.requiredOption('-r --required <type>', 'must');
node index.js   
error: required option '-r --required <type>' not specified

當然可以設置一個默認值

program.requiredOption('-r --required <type>', 'must', 'a');
node index.js   
{ required: 'a' }

變長參數(shù)選項

定義選項時,可以通過使用...來設置參數(shù)為可變長參數(shù)。在命令行中,用戶可以輸入多個參數(shù),解析后會以數(shù)組形式存儲在對應屬性字段中。在輸入下一個選項前(---開頭),用戶輸入的指令均會被視作變長參數(shù)。與普通參數(shù)一樣的是,可以通過--標記當前命令的結(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()方法可以設置版本,其默認選項為-V--version,設置了版本后,命令行會輸出當前的版本號。

program.version('v1.0.0')

版本選項也支持自定義設置選項名稱,可以在.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ù)值和當前已有的參數(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);
  // 當配置了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部分
    // ...
    // 當開啟了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ù)組中,當開啟后,命令后邊的參數(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生成幫助文字,解析處理設置的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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Node.js使用多進程提高任務執(zhí)行效率

    Node.js使用多進程提高任務執(zhí)行效率

    在Node.JS中使用多進程非常簡單,合理使用多進程,可以解放硬件的能力,讓軟件的運行效率得到肉眼可見的提升。本文詳細講解了Node.js使用多進程提高任務執(zhí)行效率的方法,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • Node.js 阻塞與非阻塞的實現(xiàn)

    Node.js 阻塞與非阻塞的實現(xiàn)

    本文主要介紹了Node.js中阻塞和非阻塞調(diào)用之間的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-05-05
  • Node.js完整安裝配置指南(包含國內(nèi)鏡像配置)

    Node.js完整安裝配置指南(包含國內(nèi)鏡像配置)

    本文介紹Node.js的安裝配置過程,包括使用Chocolatey、官網(wǎng)下載、國內(nèi)鏡像下載等安裝方法,驗證安裝,配置國內(nèi)鏡像源,環(huán)境變量配置,npm配置優(yōu)化,常用鏡像測速腳本,使用nrm管理鏡像源,及故障排查內(nèi)容,最后推薦了完整的配置命令和立即解決方案,感興趣的朋友跟隨小編一起看看吧
    2026-03-03
  • node.js中的fs.existsSync方法使用說明

    node.js中的fs.existsSync方法使用說明

    這篇文章主要介紹了node.js中的fs.existsSync方法使用說明,本文介紹了fs.existsSync方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • nodejs中方法和模塊用法示例

    nodejs中方法和模塊用法示例

    這篇文章主要介紹了nodejs中方法和模塊用法,結(jié)合實例形式分析了nodejs方法與模塊的定義及使用方法,需要的朋友可以參考下
    2018-12-12
  • Node.js學習之地址解析模塊URL的使用詳解

    Node.js學習之地址解析模塊URL的使用詳解

    url模塊是nodejs里面的一個簡單的模塊,下面這篇文章主要給大家介紹了關于Node.js學習之地址解析模塊URL使用的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-09-09
  • Node.js參數(shù)校驗模塊minijoi使用詳解

    Node.js參數(shù)校驗模塊minijoi使用詳解

    這篇文章主要為大家介紹了Node.js參數(shù)校驗模塊minijoi使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • nodejs中函數(shù)的調(diào)用實例詳解

    nodejs中函數(shù)的調(diào)用實例詳解

    本文通過實例代碼給大家介紹了nodejs函數(shù)的調(diào)用,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-10-10
  • Pycharm配置Node.js運行js代碼詳細過程

    Pycharm配置Node.js運行js代碼詳細過程

    在PyCharm中寫JavaScript代碼并進行調(diào)試是非常方便的,但是有些用戶可能對如何在PyCharm中準確地運行JavaScript代碼感到困惑,這篇文章主要給大家介紹了關于Pycharm配置Node.js運行js代碼的相關資料,需要的朋友可以參考下
    2023-11-11
  • 使用Node.js實現(xiàn)HTTP 206內(nèi)容分片的教程

    使用Node.js實現(xiàn)HTTP 206內(nèi)容分片的教程

    這篇文章主要介紹了使用Node.js實現(xiàn)HTTP 206內(nèi)容分片的教程,Node.js是一款用于服務器端的JavaScript框架,需要的朋友可以參考下
    2015-06-06

最新評論

济阳县| 清水河县| 菏泽市| 紫金县| 桃江县| 固阳县| 汝城县| 永胜县| 鲁甸县| 广宁县| 安义县| 邯郸市| 宜州市| 房产| 阆中市| 德江县| 滨海县| 库尔勒市| 滨州市| 永平县| 民勤县| 青神县| 西乡县| 明光市| 齐河县| 广水市| 昌江| 安义县| 东丰县| 河北区| 明星| 浪卡子县| 兴城市| 天气| 安宁市| 大足县| 来凤县| 旅游| 宁德市| 临汾市| 成武县|