CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
help.js521 linesDownload Raw Back to lib
1const { humanReadableArgName } = require('./argument.js');2 3/**4 * TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`5 * https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types6 * @typedef { import("./argument.js").Argument } Argument7 * @typedef { import("./command.js").Command } Command8 * @typedef { import("./option.js").Option } Option9 */10 11// Although this is a class, methods are static in style to allow override using subclass or just functions.12class Help {13  constructor() {14    this.helpWidth = undefined;15    this.sortSubcommands = false;16    this.sortOptions = false;17    this.showGlobalOptions = false;18  }19 20  /**21   * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.22   *23   * @param {Command} cmd24   * @returns {Command[]}25   */26 27  visibleCommands(cmd) {28    const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);29    const helpCommand = cmd._getHelpCommand();30    if (helpCommand && !helpCommand._hidden) {31      visibleCommands.push(helpCommand);32    }33    if (this.sortSubcommands) {34      visibleCommands.sort((a, b) => {35        // @ts-ignore: because overloaded return type36        return a.name().localeCompare(b.name());37      });38    }39    return visibleCommands;40  }41 42  /**43   * Compare options for sort.44   *45   * @param {Option} a46   * @param {Option} b47   * @returns {number}48   */49  compareOptions(a, b) {50    const getSortKey = (option) => {51      // WYSIWYG for order displayed in help. Short used for comparison if present. No special handling for negated.52      return option.short53        ? option.short.replace(/^-/, '')54        : option.long.replace(/^--/, '');55    };56    return getSortKey(a).localeCompare(getSortKey(b));57  }58 59  /**60   * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.61   *62   * @param {Command} cmd63   * @returns {Option[]}64   */65 66  visibleOptions(cmd) {67    const visibleOptions = cmd.options.filter((option) => !option.hidden);68    // Built-in help option.69    const helpOption = cmd._getHelpOption();70    if (helpOption && !helpOption.hidden) {71      // Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.72      const removeShort = helpOption.short && cmd._findOption(helpOption.short);73      const removeLong = helpOption.long && cmd._findOption(helpOption.long);74      if (!removeShort && !removeLong) {75        visibleOptions.push(helpOption); // no changes needed76      } else if (helpOption.long && !removeLong) {77        visibleOptions.push(78          cmd.createOption(helpOption.long, helpOption.description),79        );80      } else if (helpOption.short && !removeShort) {81        visibleOptions.push(82          cmd.createOption(helpOption.short, helpOption.description),83        );84      }85    }86    if (this.sortOptions) {87      visibleOptions.sort(this.compareOptions);88    }89    return visibleOptions;90  }91 92  /**93   * Get an array of the visible global options. (Not including help.)94   *95   * @param {Command} cmd96   * @returns {Option[]}97   */98 99  visibleGlobalOptions(cmd) {100    if (!this.showGlobalOptions) return [];101 102    const globalOptions = [];103    for (104      let ancestorCmd = cmd.parent;105      ancestorCmd;106      ancestorCmd = ancestorCmd.parent107    ) {108      const visibleOptions = ancestorCmd.options.filter(109        (option) => !option.hidden,110      );111      globalOptions.push(...visibleOptions);112    }113    if (this.sortOptions) {114      globalOptions.sort(this.compareOptions);115    }116    return globalOptions;117  }118 119  /**120   * Get an array of the arguments if any have a description.121   *122   * @param {Command} cmd123   * @returns {Argument[]}124   */125 126  visibleArguments(cmd) {127    // Side effect! Apply the legacy descriptions before the arguments are displayed.128    if (cmd._argsDescription) {129      cmd.registeredArguments.forEach((argument) => {130        argument.description =131          argument.description || cmd._argsDescription[argument.name()] || '';132      });133    }134 135    // If there are any arguments with a description then return all the arguments.136    if (cmd.registeredArguments.find((argument) => argument.description)) {137      return cmd.registeredArguments;138    }139    return [];140  }141 142  /**143   * Get the command term to show in the list of subcommands.144   *145   * @param {Command} cmd146   * @returns {string}147   */148 149  subcommandTerm(cmd) {150    // Legacy. Ignores custom usage string, and nested commands.151    const args = cmd.registeredArguments152      .map((arg) => humanReadableArgName(arg))153      .join(' ');154    return (155      cmd._name +156      (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +157      (cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option158      (args ? ' ' + args : '')159    );160  }161 162  /**163   * Get the option term to show in the list of options.164   *165   * @param {Option} option166   * @returns {string}167   */168 169  optionTerm(option) {170    return option.flags;171  }172 173  /**174   * Get the argument term to show in the list of arguments.175   *176   * @param {Argument} argument177   * @returns {string}178   */179 180  argumentTerm(argument) {181    return argument.name();182  }183 184  /**185   * Get the longest command term length.186   *187   * @param {Command} cmd188   * @param {Help} helper189   * @returns {number}190   */191 192  longestSubcommandTermLength(cmd, helper) {193    return helper.visibleCommands(cmd).reduce((max, command) => {194      return Math.max(max, helper.subcommandTerm(command).length);195    }, 0);196  }197 198  /**199   * Get the longest option term length.200   *201   * @param {Command} cmd202   * @param {Help} helper203   * @returns {number}204   */205 206  longestOptionTermLength(cmd, helper) {207    return helper.visibleOptions(cmd).reduce((max, option) => {208      return Math.max(max, helper.optionTerm(option).length);209    }, 0);210  }211 212  /**213   * Get the longest global option term length.214   *215   * @param {Command} cmd216   * @param {Help} helper217   * @returns {number}218   */219 220  longestGlobalOptionTermLength(cmd, helper) {221    return helper.visibleGlobalOptions(cmd).reduce((max, option) => {222      return Math.max(max, helper.optionTerm(option).length);223    }, 0);224  }225 226  /**227   * Get the longest argument term length.228   *229   * @param {Command} cmd230   * @param {Help} helper231   * @returns {number}232   */233 234  longestArgumentTermLength(cmd, helper) {235    return helper.visibleArguments(cmd).reduce((max, argument) => {236      return Math.max(max, helper.argumentTerm(argument).length);237    }, 0);238  }239 240  /**241   * Get the command usage to be displayed at the top of the built-in help.242   *243   * @param {Command} cmd244   * @returns {string}245   */246 247  commandUsage(cmd) {248    // Usage249    let cmdName = cmd._name;250    if (cmd._aliases[0]) {251      cmdName = cmdName + '|' + cmd._aliases[0];252    }253    let ancestorCmdNames = '';254    for (255      let ancestorCmd = cmd.parent;256      ancestorCmd;257      ancestorCmd = ancestorCmd.parent258    ) {259      ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;260    }261    return ancestorCmdNames + cmdName + ' ' + cmd.usage();262  }263 264  /**265   * Get the description for the command.266   *267   * @param {Command} cmd268   * @returns {string}269   */270 271  commandDescription(cmd) {272    // @ts-ignore: because overloaded return type273    return cmd.description();274  }275 276  /**277   * Get the subcommand summary to show in the list of subcommands.278   * (Fallback to description for backwards compatibility.)279   *280   * @param {Command} cmd281   * @returns {string}282   */283 284  subcommandDescription(cmd) {285    // @ts-ignore: because overloaded return type286    return cmd.summary() || cmd.description();287  }288 289  /**290   * Get the option description to show in the list of options.291   *292   * @param {Option} option293   * @return {string}294   */295 296  optionDescription(option) {297    const extraInfo = [];298 299    if (option.argChoices) {300      extraInfo.push(301        // use stringify to match the display of the default value302        `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,303      );304    }305    if (option.defaultValue !== undefined) {306      // default for boolean and negated more for programmer than end user,307      // but show true/false for boolean option as may be for hand-rolled env or config processing.308      const showDefault =309        option.required ||310        option.optional ||311        (option.isBoolean() && typeof option.defaultValue === 'boolean');312      if (showDefault) {313        extraInfo.push(314          `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`,315        );316      }317    }318    // preset for boolean and negated are more for programmer than end user319    if (option.presetArg !== undefined && option.optional) {320      extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);321    }322    if (option.envVar !== undefined) {323      extraInfo.push(`env: ${option.envVar}`);324    }325    if (extraInfo.length > 0) {326      return `${option.description} (${extraInfo.join(', ')})`;327    }328 329    return option.description;330  }331 332  /**333   * Get the argument description to show in the list of arguments.334   *335   * @param {Argument} argument336   * @return {string}337   */338 339  argumentDescription(argument) {340    const extraInfo = [];341    if (argument.argChoices) {342      extraInfo.push(343        // use stringify to match the display of the default value344        `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,345      );346    }347    if (argument.defaultValue !== undefined) {348      extraInfo.push(349        `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`,350      );351    }352    if (extraInfo.length > 0) {353      const extraDescripton = `(${extraInfo.join(', ')})`;354      if (argument.description) {355        return `${argument.description} ${extraDescripton}`;356      }357      return extraDescripton;358    }359    return argument.description;360  }361 362  /**363   * Generate the built-in help text.364   *365   * @param {Command} cmd366   * @param {Help} helper367   * @returns {string}368   */369 370  formatHelp(cmd, helper) {371    const termWidth = helper.padWidth(cmd, helper);372    const helpWidth = helper.helpWidth || 80;373    const itemIndentWidth = 2;374    const itemSeparatorWidth = 2; // between term and description375    function formatItem(term, description) {376      if (description) {377        const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;378        return helper.wrap(379          fullText,380          helpWidth - itemIndentWidth,381          termWidth + itemSeparatorWidth,382        );383      }384      return term;385    }386    function formatList(textArray) {387      return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));388    }389 390    // Usage391    let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];392 393    // Description394    const commandDescription = helper.commandDescription(cmd);395    if (commandDescription.length > 0) {396      output = output.concat([397        helper.wrap(commandDescription, helpWidth, 0),398        '',399      ]);400    }401 402    // Arguments403    const argumentList = helper.visibleArguments(cmd).map((argument) => {404      return formatItem(405        helper.argumentTerm(argument),406        helper.argumentDescription(argument),407      );408    });409    if (argumentList.length > 0) {410      output = output.concat(['Arguments:', formatList(argumentList), '']);411    }412 413    // Options414    const optionList = helper.visibleOptions(cmd).map((option) => {415      return formatItem(416        helper.optionTerm(option),417        helper.optionDescription(option),418      );419    });420    if (optionList.length > 0) {421      output = output.concat(['Options:', formatList(optionList), '']);422    }423 424    if (this.showGlobalOptions) {425      const globalOptionList = helper426        .visibleGlobalOptions(cmd)427        .map((option) => {428          return formatItem(429            helper.optionTerm(option),430            helper.optionDescription(option),431          );432        });433      if (globalOptionList.length > 0) {434        output = output.concat([435          'Global Options:',436          formatList(globalOptionList),437          '',438        ]);439      }440    }441 442    // Commands443    const commandList = helper.visibleCommands(cmd).map((cmd) => {444      return formatItem(445        helper.subcommandTerm(cmd),446        helper.subcommandDescription(cmd),447      );448    });449    if (commandList.length > 0) {450      output = output.concat(['Commands:', formatList(commandList), '']);451    }452 453    return output.join('\n');454  }455 456  /**457   * Calculate the pad width from the maximum term length.458   *459   * @param {Command} cmd460   * @param {Help} helper461   * @returns {number}462   */463 464  padWidth(cmd, helper) {465    return Math.max(466      helper.longestOptionTermLength(cmd, helper),467      helper.longestGlobalOptionTermLength(cmd, helper),468      helper.longestSubcommandTermLength(cmd, helper),469      helper.longestArgumentTermLength(cmd, helper),470    );471  }472 473  /**474   * Wrap the given string to width characters per line, with lines after the first indented.475   * Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.476   *477   * @param {string} str478   * @param {number} width479   * @param {number} indent480   * @param {number} [minColumnWidth=40]481   * @return {string}482   *483   */484 485  wrap(str, width, indent, minColumnWidth = 40) {486    // Full \s characters, minus the linefeeds.487    const indents =488      ' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff';489    // Detect manually wrapped and indented strings by searching for line break followed by spaces.490    const manualIndent = new RegExp(`[\\n][${indents}]+`);491    if (str.match(manualIndent)) return str;492    // Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).493    const columnWidth = width - indent;494    if (columnWidth < minColumnWidth) return str;495 496    const leadingStr = str.slice(0, indent);497    const columnText = str.slice(indent).replace('\r\n', '\n');498    const indentString = ' '.repeat(indent);499    const zeroWidthSpace = '\u200B';500    const breaks = `\\s${zeroWidthSpace}`;501    // Match line end (so empty lines don't collapse),502    // or as much text as will fit in column, or excess text up to first break.503    const regex = new RegExp(504      `\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`,505      'g',506    );507    const lines = columnText.match(regex) || [];508    return (509      leadingStr +510      lines511        .map((line, i) => {512          if (line === '\n') return ''; // preserve empty lines513          return (i > 0 ? indentString : '') + line.trimEnd();514        })515        .join('\n')516    );517  }518}519 520exports.Help = Help;521 
basant307/AI_Governance_Project · CoolFace