| | |
| | | import { assertNotStrictEqual, } from './typings/common-types.js'; |
| | | import { objFilter } from './utils/obj-filter.js'; |
| | | import { YError } from './yerror.js'; |
| | | import setBlocking from './utils/set-blocking.js'; |
| | | export function usage(yargs, y18n, shim) { |
| | | "use strict"; |
| | | Object.defineProperty(exports, "__esModule", { value: true }); |
| | | exports.usage = void 0; |
| | | // this file handles outputting usage instructions, |
| | | // failures, etc. keeps logging in one place. |
| | | const common_types_1 = require("./common-types"); |
| | | const obj_filter_1 = require("./obj-filter"); |
| | | const path = require("path"); |
| | | const yerror_1 = require("./yerror"); |
| | | const decamelize = require("decamelize"); |
| | | const setBlocking = require("set-blocking"); |
| | | const stringWidth = require("string-width"); |
| | | function usage(yargs, y18n) { |
| | | const __ = y18n.__; |
| | | const self = {}; |
| | | // methods for ouputting/building failure message. |
| | | const fails = []; |
| | | self.failFn = function failFn(f) { |
| | | fails.push(f); |
| | |
| | | else { |
| | | if (yargs.getExitProcess()) |
| | | setBlocking(true); |
| | | // don't output failure message more than once |
| | | if (!failureOutput) { |
| | | failureOutput = true; |
| | | if (showHelpOnFail) { |
| | |
| | | logger.error(failMessage); |
| | | } |
| | | } |
| | | err = err || new YError(msg); |
| | | err = err || new yerror_1.YError(msg); |
| | | if (yargs.getExitProcess()) { |
| | | return yargs.exit(1); |
| | | } |
| | |
| | | } |
| | | } |
| | | }; |
| | | // methods for ouputting/building help (usage) message. |
| | | let usages = []; |
| | | let usageDisabled = false; |
| | | self.usage = (msg, description) => { |
| | |
| | | }; |
| | | let commands = []; |
| | | self.command = function command(cmd, description, isDefault, aliases, deprecated = false) { |
| | | // the last default wins, so cancel out any previously set default |
| | | if (isDefault) { |
| | | commands = commands.map(cmdArray => { |
| | | commands = commands.map((cmdArray) => { |
| | | cmdArray[2] = false; |
| | | return cmdArray; |
| | | }); |
| | |
| | | let descriptions = {}; |
| | | self.describe = function describe(keyOrKeys, desc) { |
| | | if (Array.isArray(keyOrKeys)) { |
| | | keyOrKeys.forEach(k => { |
| | | keyOrKeys.forEach((k) => { |
| | | self.describe(k, desc); |
| | | }); |
| | | } |
| | | else if (typeof keyOrKeys === 'object') { |
| | | Object.keys(keyOrKeys).forEach(k => { |
| | | Object.keys(keyOrKeys).forEach((k) => { |
| | | self.describe(k, keyOrKeys[k]); |
| | | }); |
| | | } |
| | |
| | | }; |
| | | self.getDescriptions = () => descriptions; |
| | | let epilogs = []; |
| | | self.epilog = msg => { |
| | | self.epilog = (msg) => { |
| | | epilogs.push(msg); |
| | | }; |
| | | let wrapSet = false; |
| | | let wrap; |
| | | self.wrap = cols => { |
| | | self.wrap = (cols) => { |
| | | wrapSet = true; |
| | | wrap = cols; |
| | | }; |
| | |
| | | if (cachedHelpMessage) |
| | | return cachedHelpMessage; |
| | | normalizeAliases(); |
| | | const base$0 = yargs.customScriptName |
| | | ? yargs.$0 |
| | | : shim.path.basename(yargs.$0); |
| | | // handle old demanded API |
| | | const base$0 = yargs.customScriptName ? yargs.$0 : path.basename(yargs.$0); |
| | | const demandedOptions = yargs.getDemandedOptions(); |
| | | const demandedCommands = yargs.getDemandedCommands(); |
| | | const deprecatedOptions = yargs.getDeprecatedOptions(); |
| | |
| | | return acc; |
| | | }, {})); |
| | | const theWrap = getWrap(); |
| | | const ui = shim.cliui({ |
| | | const ui = require('cliui')({ |
| | | width: theWrap, |
| | | wrap: !!theWrap, |
| | | wrap: !!theWrap |
| | | }); |
| | | // the usage string. |
| | | if (!usageDisabled) { |
| | | if (usages.length) { |
| | | usages.forEach(usage => { |
| | | // user-defined usage. |
| | | usages.forEach((usage) => { |
| | | ui.div(`${usage[0].replace(/\$0/g, base$0)}`); |
| | | if (usage[1]) { |
| | | ui.div({ text: `${usage[1]}`, padding: [1, 0, 0, 0] }); |
| | |
| | | } |
| | | else if (commands.length) { |
| | | let u = null; |
| | | // demonstrate how commands are used. |
| | | if (demandedCommands._) { |
| | | u = `${base$0} <${__('command')}>\n`; |
| | | } |
| | |
| | | ui.div(`${u}`); |
| | | } |
| | | } |
| | | // your application's commands, i.e., non-option |
| | | // arguments populated in '_'. |
| | | if (commands.length) { |
| | | ui.div(__('Commands:')); |
| | | const context = yargs.getContext(); |
| | | const parentCommands = context.commands.length |
| | | ? `${context.commands.join(' ')} ` |
| | | : ''; |
| | | const parentCommands = context.commands.length ? `${context.commands.join(' ')} ` : ''; |
| | | if (yargs.getParserConfiguration()['sort-commands'] === true) { |
| | | commands = commands.sort((a, b) => a[0].localeCompare(b[0])); |
| | | } |
| | | commands.forEach(command => { |
| | | const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}`; |
| | | commands.forEach((command) => { |
| | | const commandString = `${base$0} ${parentCommands}${command[0].replace(/^\$0 ?/, '')}`; // drop $0 from default commands. |
| | | ui.span({ |
| | | text: commandString, |
| | | padding: [0, 2, 0, 2], |
| | | width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4, |
| | | width: maxWidth(commands, theWrap, `${base$0}${parentCommands}`) + 4 |
| | | }, { text: command[1] }); |
| | | const hints = []; |
| | | if (command[2]) |
| | |
| | | } |
| | | } |
| | | if (hints.length) { |
| | | ui.div({ |
| | | text: hints.join(' '), |
| | | padding: [0, 0, 0, 2], |
| | | align: 'right', |
| | | }); |
| | | ui.div({ text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right' }); |
| | | } |
| | | else { |
| | | ui.div(); |
| | |
| | | }); |
| | | ui.div(); |
| | | } |
| | | const aliasKeys = (Object.keys(options.alias) || []).concat(Object.keys(yargs.parsed.newAliases) || []); |
| | | keys = keys.filter(key => !yargs.parsed.newAliases[key] && |
| | | aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1)); |
| | | // perform some cleanup on the keys array, making it |
| | | // only include top-level keys not their aliases. |
| | | const aliasKeys = (Object.keys(options.alias) || []) |
| | | .concat(Object.keys(yargs.parsed.newAliases) || []); |
| | | keys = keys.filter(key => !yargs.parsed.newAliases[key] && aliasKeys.every(alias => (options.alias[alias] || []).indexOf(key) === -1)); |
| | | // populate 'Options:' group with any keys that have not |
| | | // explicitly had a group set. |
| | | const defaultGroup = __('Options:'); |
| | | if (!groups[defaultGroup]) |
| | | groups[defaultGroup] = []; |
| | | addUngroupedKeys(keys, options.alias, groups, defaultGroup); |
| | | const isLongSwitch = (sw) => /^--/.test(getText(sw)); |
| | | const displayedGroups = Object.keys(groups) |
| | | .filter(groupName => groups[groupName].length > 0) |
| | | .map(groupName => { |
| | | const normalizedKeys = groups[groupName] |
| | | .filter(filterHiddenOptions) |
| | | .map(key => { |
| | | // display 'Options:' table along with any custom tables: |
| | | Object.keys(groups).forEach((groupName) => { |
| | | if (!groups[groupName].length) |
| | | return; |
| | | // if we've grouped the key 'f', but 'f' aliases 'foobar', |
| | | // normalizedKeys should contain only 'foobar'. |
| | | const normalizedKeys = groups[groupName].filter(filterHiddenOptions).map((key) => { |
| | | if (~aliasKeys.indexOf(key)) |
| | | return key; |
| | | for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) { |
| | |
| | | } |
| | | return key; |
| | | }); |
| | | return { groupName, normalizedKeys }; |
| | | }) |
| | | .filter(({ normalizedKeys }) => normalizedKeys.length > 0) |
| | | .map(({ groupName, normalizedKeys }) => { |
| | | if (normalizedKeys.length < 1) |
| | | return; |
| | | ui.div(groupName); |
| | | // actually generate the switches string --foo, -f, --bar. |
| | | const switches = normalizedKeys.reduce((acc, key) => { |
| | | acc[key] = [key] |
| | | .concat(options.alias[key] || []) |
| | | acc[key] = [key].concat(options.alias[key] || []) |
| | | .map(sw => { |
| | | // for the special positional group don't |
| | | // add '--' or '-' prefix. |
| | | if (groupName === self.getPositionalGroupName()) |
| | | return sw; |
| | | else { |
| | | return ((/^[0-9]$/.test(sw) |
| | | ? ~options.boolean.indexOf(key) |
| | | ? '-' |
| | | : '--' |
| | | : sw.length > 1 |
| | | ? '--' |
| | | : '-') + sw); |
| | | return ( |
| | | // matches yargs-parser logic in which single-digits |
| | | // aliases declared with a boolean type are now valid |
| | | /^[0-9]$/.test(sw) |
| | | ? ~options.boolean.indexOf(key) ? '-' : '--' |
| | | : sw.length > 1 ? '--' : '-') + sw; |
| | | } |
| | | }) |
| | | .sort((sw1, sw2) => isLongSwitch(sw1) === isLongSwitch(sw2) |
| | | ? 0 |
| | | : isLongSwitch(sw1) |
| | | ? 1 |
| | | : -1) |
| | | .join(', '); |
| | | return acc; |
| | | }, {}); |
| | | return { groupName, normalizedKeys, switches }; |
| | | }); |
| | | const shortSwitchesUsed = displayedGroups |
| | | .filter(({ groupName }) => groupName !== self.getPositionalGroupName()) |
| | | .some(({ normalizedKeys, switches }) => !normalizedKeys.every(key => isLongSwitch(switches[key]))); |
| | | if (shortSwitchesUsed) { |
| | | displayedGroups |
| | | .filter(({ groupName }) => groupName !== self.getPositionalGroupName()) |
| | | .forEach(({ normalizedKeys, switches }) => { |
| | | normalizedKeys.forEach(key => { |
| | | if (isLongSwitch(switches[key])) { |
| | | switches[key] = addIndentation(switches[key], '-x, '.length); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | displayedGroups.forEach(({ groupName, normalizedKeys, switches }) => { |
| | | ui.div(groupName); |
| | | normalizedKeys.forEach(key => { |
| | | normalizedKeys.forEach((key) => { |
| | | const kswitch = switches[key]; |
| | | let desc = descriptions[key] || ''; |
| | | let type = null; |
| | |
| | | ? `[${__('deprecated: %s', deprecated)}]` |
| | | : `[${__('deprecated')}]`; |
| | | const extra = [ |
| | | key in deprecatedOptions |
| | | ? deprecatedExtra(deprecatedOptions[key]) |
| | | : null, |
| | | (key in deprecatedOptions) ? deprecatedExtra(deprecatedOptions[key]) : null, |
| | | type, |
| | | key in demandedOptions ? `[${__('required')}]` : null, |
| | | options.choices && options.choices[key] |
| | | ? `[${__('choices:')} ${self.stringifiedValues(options.choices[key])}]` |
| | | : null, |
| | | defaultString(options.default[key], options.defaultDescription[key]), |
| | | ] |
| | | .filter(Boolean) |
| | | .join(' '); |
| | | ui.span({ |
| | | text: getText(kswitch), |
| | | padding: [0, 2, 0, 2 + getIndentation(kswitch)], |
| | | width: maxWidth(switches, theWrap) + 4, |
| | | }, desc); |
| | | (key in demandedOptions) ? `[${__('required')}]` : null, |
| | | options.choices && options.choices[key] ? `[${__('choices:')} ${self.stringifiedValues(options.choices[key])}]` : null, |
| | | defaultString(options.default[key], options.defaultDescription[key]) |
| | | ].filter(Boolean).join(' '); |
| | | ui.span({ text: kswitch, padding: [0, 2, 0, 2], width: maxWidth(switches, theWrap) + 4 }, desc); |
| | | if (extra) |
| | | ui.div({ text: extra, padding: [0, 0, 0, 2], align: 'right' }); |
| | | else |
| | |
| | | }); |
| | | ui.div(); |
| | | }); |
| | | // describe some common use-cases for your application. |
| | | if (examples.length) { |
| | | ui.div(__('Examples:')); |
| | | examples.forEach(example => { |
| | | examples.forEach((example) => { |
| | | example[0] = example[0].replace(/\$0/g, base$0); |
| | | }); |
| | | examples.forEach(example => { |
| | | examples.forEach((example) => { |
| | | if (example[1] === '') { |
| | | ui.div({ |
| | | text: example[0], |
| | | padding: [0, 2, 0, 2], |
| | | padding: [0, 2, 0, 2] |
| | | }); |
| | | } |
| | | else { |
| | | ui.div({ |
| | | text: example[0], |
| | | padding: [0, 2, 0, 2], |
| | | width: maxWidth(examples, theWrap) + 4, |
| | | width: maxWidth(examples, theWrap) + 4 |
| | | }, { |
| | | text: example[1], |
| | | text: example[1] |
| | | }); |
| | | } |
| | | }); |
| | | ui.div(); |
| | | } |
| | | // the usage string. |
| | | if (epilogs.length > 0) { |
| | | const e = epilogs |
| | | .map(epilog => epilog.replace(/\$0/g, base$0)) |
| | | .join('\n'); |
| | | const e = epilogs.map(epilog => epilog.replace(/\$0/g, base$0)).join('\n'); |
| | | ui.div(`${e}\n`); |
| | | } |
| | | // Remove the trailing white spaces |
| | | return ui.toString().replace(/\s*$/, ''); |
| | | }; |
| | | // return the maximum width of a string |
| | | // in the left-hand column of a table. |
| | | function maxWidth(table, theWrap, modifier) { |
| | | let width = 0; |
| | | // table might be of the form [leftColumn], |
| | | // or {key: leftColumn} |
| | | if (!Array.isArray(table)) { |
| | | table = Object.values(table).map(v => [v]); |
| | | } |
| | | table.forEach(v => { |
| | | width = Math.max(shim.stringWidth(modifier ? `${modifier} ${getText(v[0])}` : getText(v[0])) + getIndentation(v[0]), width); |
| | | table.forEach((v) => { |
| | | width = Math.max(stringWidth(modifier ? `${modifier} ${v[0]}` : v[0]), width); |
| | | }); |
| | | // if we've enabled 'wrap' we should limit |
| | | // the max-width of the left-column. |
| | | if (theWrap) |
| | | width = Math.min(width, parseInt((theWrap * 0.5).toString(), 10)); |
| | | return width; |
| | | } |
| | | // make sure any options set for aliases, |
| | | // are copied to the keys being aliased. |
| | | function normalizeAliases() { |
| | | // handle old demanded API |
| | | const demandedOptions = yargs.getDemandedOptions(); |
| | | const options = yargs.getOptions(); |
| | | (Object.keys(options.alias) || []).forEach(key => { |
| | | options.alias[key].forEach(alias => { |
| | | (Object.keys(options.alias) || []).forEach((key) => { |
| | | options.alias[key].forEach((alias) => { |
| | | // copy descriptions. |
| | | if (descriptions[alias]) |
| | | self.describe(key, descriptions[alias]); |
| | | // copy demanded. |
| | | if (alias in demandedOptions) |
| | | yargs.demandOption(key, demandedOptions[alias]); |
| | | // type messages. |
| | | if (~options.boolean.indexOf(alias)) |
| | | yargs.boolean(key); |
| | | if (~options.count.indexOf(alias)) |
| | |
| | | }); |
| | | }); |
| | | } |
| | | // if yargs is executing an async handler, we take a snapshot of the |
| | | // help message to display on failure: |
| | | let cachedHelpMessage; |
| | | self.cacheHelpMessage = function () { |
| | | cachedHelpMessage = this.help(); |
| | | }; |
| | | // however this snapshot must be cleared afterwards |
| | | // not to be be used by next calls to parse |
| | | self.clearCachedHelpMessage = function () { |
| | | cachedHelpMessage = undefined; |
| | | }; |
| | | // given a set of keys, place any keys that are |
| | | // ungrouped under the 'Options:' grouping. |
| | | function addUngroupedKeys(keys, aliases, groups, defaultGroup) { |
| | | let groupedKeys = []; |
| | | let toCheck = null; |
| | | Object.keys(groups).forEach(group => { |
| | | Object.keys(groups).forEach((group) => { |
| | | groupedKeys = groupedKeys.concat(groups[group]); |
| | | }); |
| | | keys.forEach(key => { |
| | | keys.forEach((key) => { |
| | | toCheck = [key].concat(aliases[key]); |
| | | if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) { |
| | | groups[defaultGroup].push(key); |
| | |
| | | return groupedKeys; |
| | | } |
| | | function filterHiddenOptions(key) { |
| | | return (yargs.getOptions().hiddenOptions.indexOf(key) < 0 || |
| | | yargs.parsed.argv[yargs.getOptions().showHiddenOpt]); |
| | | return yargs.getOptions().hiddenOptions.indexOf(key) < 0 || yargs.parsed.argv[yargs.getOptions().showHiddenOpt]; |
| | | } |
| | | self.showHelp = (level) => { |
| | | const logger = yargs._getLoggerInstance(); |
| | |
| | | const emit = typeof level === 'function' ? level : logger[level]; |
| | | emit(self.help()); |
| | | }; |
| | | self.functionDescription = fn => { |
| | | const description = fn.name |
| | | ? shim.Parser.decamelize(fn.name, '-') |
| | | : __('generated-value'); |
| | | self.functionDescription = (fn) => { |
| | | const description = fn.name ? decamelize(fn.name, '-') : __('generated-value'); |
| | | return ['(', description, ')'].join(''); |
| | | }; |
| | | self.stringifiedValues = function stringifiedValues(values, separator) { |
| | |
| | | const array = [].concat(values); |
| | | if (!values || !array.length) |
| | | return string; |
| | | array.forEach(value => { |
| | | array.forEach((value) => { |
| | | if (string.length) |
| | | string += sep; |
| | | string += JSON.stringify(value); |
| | | }); |
| | | return string; |
| | | }; |
| | | // format the default-value-string displayed in |
| | | // the right-hand column. |
| | | function defaultString(value, defaultDescription) { |
| | | let string = `[${__('default:')} `; |
| | | if (value === undefined && !defaultDescription) |
| | |
| | | } |
| | | return `${string}]`; |
| | | } |
| | | // guess the width of the console window, max-width 80. |
| | | function windowWidth() { |
| | | const maxWidth = 80; |
| | | if (shim.process.stdColumns) { |
| | | return Math.min(maxWidth, shim.process.stdColumns); |
| | | // CI is not a TTY |
| | | /* c8 ignore next 2 */ |
| | | if (typeof process === 'object' && process.stdout && process.stdout.columns) { |
| | | return Math.min(maxWidth, process.stdout.columns); |
| | | } |
| | | else { |
| | | return maxWidth; |
| | | } |
| | | } |
| | | // logic for displaying application version. |
| | | let version = null; |
| | | self.version = ver => { |
| | | self.version = (ver) => { |
| | | version = ver; |
| | | }; |
| | | self.showVersion = () => { |
| | |
| | | logger.log(version); |
| | | }; |
| | | self.reset = function reset(localLookup) { |
| | | // do not reset wrap here |
| | | // do not reset fails here |
| | | failMessage = null; |
| | | failureOutput = false; |
| | | usages = []; |
| | |
| | | epilogs = []; |
| | | examples = []; |
| | | commands = []; |
| | | descriptions = objFilter(descriptions, k => !localLookup[k]); |
| | | descriptions = obj_filter_1.objFilter(descriptions, k => !localLookup[k]); |
| | | return self; |
| | | }; |
| | | const frozens = []; |
| | |
| | | epilogs, |
| | | examples, |
| | | commands, |
| | | descriptions, |
| | | descriptions |
| | | }); |
| | | }; |
| | | self.unfreeze = function unfreeze() { |
| | | const frozen = frozens.pop(); |
| | | assertNotStrictEqual(frozen, undefined, shim); |
| | | common_types_1.assertNotStrictEqual(frozen, undefined); |
| | | ({ |
| | | failMessage, |
| | | failureOutput, |
| | |
| | | epilogs, |
| | | examples, |
| | | commands, |
| | | descriptions, |
| | | descriptions |
| | | } = frozen); |
| | | }; |
| | | return self; |
| | | } |
| | | function isIndentedText(text) { |
| | | return typeof text === 'object'; |
| | | } |
| | | function addIndentation(text, indent) { |
| | | return isIndentedText(text) |
| | | ? { text: text.text, indentation: text.indentation + indent } |
| | | : { text, indentation: indent }; |
| | | } |
| | | function getIndentation(text) { |
| | | return isIndentedText(text) ? text.indentation : 0; |
| | | } |
| | | function getText(text) { |
| | | return isIndentedText(text) ? text.text : text; |
| | | } |
| | | exports.usage = usage; |