aboutsummaryrefslogtreecommitdiff
path: root/node_modules/yargs/lib/usage.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/yargs/lib/usage.js')
-rw-r--r--node_modules/yargs/lib/usage.js531
1 files changed, 531 insertions, 0 deletions
diff --git a/node_modules/yargs/lib/usage.js b/node_modules/yargs/lib/usage.js
new file mode 100644
index 0000000..c323c1b
--- /dev/null
+++ b/node_modules/yargs/lib/usage.js
@@ -0,0 +1,531 @@
+'use strict'
+// this file handles outputting usage instructions,
+// failures, etc. keeps logging in one place.
+const stringWidth = require('string-width')
+const objFilter = require('./obj-filter')
+const path = require('path')
+const setBlocking = require('set-blocking')
+const YError = require('./yerror')
+
+module.exports = function usage (yargs, y18n) {
+ const __ = y18n.__
+ const self = {}
+
+ // methods for ouputting/building failure message.
+ const fails = []
+ self.failFn = function failFn (f) {
+ fails.push(f)
+ }
+
+ let failMessage = null
+ let showHelpOnFail = true
+ self.showHelpOnFail = function showHelpOnFailFn (enabled, message) {
+ if (typeof enabled === 'string') {
+ message = enabled
+ enabled = true
+ } else if (typeof enabled === 'undefined') {
+ enabled = true
+ }
+ failMessage = message
+ showHelpOnFail = enabled
+ return self
+ }
+
+ let failureOutput = false
+ self.fail = function fail (msg, err) {
+ const logger = yargs._getLoggerInstance()
+
+ if (fails.length) {
+ for (let i = fails.length - 1; i >= 0; --i) {
+ fails[i](msg, err, self)
+ }
+ } else {
+ if (yargs.getExitProcess()) setBlocking(true)
+
+ // don't output failure message more than once
+ if (!failureOutput) {
+ failureOutput = true
+ if (showHelpOnFail) yargs.showHelp('error')
+ if (msg || err) logger.error(msg || err)
+ if (failMessage) {
+ if (msg || err) logger.error('')
+ logger.error(failMessage)
+ }
+ }
+
+ err = err || new YError(msg)
+ if (yargs.getExitProcess()) {
+ return yargs.exit(1)
+ } else if (yargs._hasParseCallback()) {
+ return yargs.exit(1, err)
+ } else {
+ throw err
+ }
+ }
+ }
+
+ // methods for ouputting/building help (usage) message.
+ let usages = []
+ let usageDisabled = false
+ self.usage = (msg, description) => {
+ if (msg === null) {
+ usageDisabled = true
+ usages = []
+ return
+ }
+ usageDisabled = false
+ usages.push([msg, description || ''])
+ return self
+ }
+ self.getUsage = () => {
+ return usages
+ }
+ self.getUsageDisabled = () => {
+ return usageDisabled
+ }
+
+ self.getPositionalGroupName = () => {
+ return __('Positionals:')
+ }
+
+ let examples = []
+ self.example = (cmd, description) => {
+ examples.push([cmd, description || ''])
+ }
+
+ let commands = []
+ self.command = function command (cmd, description, isDefault, aliases) {
+ // the last default wins, so cancel out any previously set default
+ if (isDefault) {
+ commands = commands.map((cmdArray) => {
+ cmdArray[2] = false
+ return cmdArray
+ })
+ }
+ commands.push([cmd, description || '', isDefault, aliases])
+ }
+ self.getCommands = () => commands
+
+ let descriptions = {}
+ self.describe = function describe (key, desc) {
+ if (typeof key === 'object') {
+ Object.keys(key).forEach((k) => {
+ self.describe(k, key[k])
+ })
+ } else {
+ descriptions[key] = desc
+ }
+ }
+ self.getDescriptions = () => descriptions
+
+ let epilog
+ self.epilog = (msg) => {
+ epilog = msg
+ }
+
+ let wrapSet = false
+ let wrap
+ self.wrap = (cols) => {
+ wrapSet = true
+ wrap = cols
+ }
+
+ function getWrap () {
+ if (!wrapSet) {
+ wrap = windowWidth()
+ wrapSet = true
+ }
+
+ return wrap
+ }
+
+ const deferY18nLookupPrefix = '__yargsString__:'
+ self.deferY18nLookup = str => deferY18nLookupPrefix + str
+
+ const defaultGroup = 'Options:'
+ self.help = function help () {
+ normalizeAliases()
+
+ // handle old demanded API
+ const base$0 = path.basename(yargs.$0)
+ const demandedOptions = yargs.getDemandedOptions()
+ const demandedCommands = yargs.getDemandedCommands()
+ const groups = yargs.getGroups()
+ const options = yargs.getOptions()
+
+ let keys = []
+ keys = keys.concat(Object.keys(descriptions))
+ keys = keys.concat(Object.keys(demandedOptions))
+ keys = keys.concat(Object.keys(demandedCommands))
+ keys = keys.concat(Object.keys(options.default))
+ keys = keys.filter(key => {
+ if (options.hiddenOptions.indexOf(key) < 0) {
+ return true
+ } else if (yargs.parsed.argv[options.showHiddenOpt]) {
+ return true
+ }
+ })
+ keys = Object.keys(keys.reduce((acc, key) => {
+ if (key !== '_') acc[key] = true
+ return acc
+ }, {}))
+
+ const theWrap = getWrap()
+ const ui = require('cliui')({
+ width: theWrap,
+ wrap: !!theWrap
+ })
+
+ // the usage string.
+ if (!usageDisabled) {
+ if (usages.length) {
+ // 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]})
+ }
+ })
+ ui.div()
+ } else if (commands.length) {
+ let u = null
+ // demonstrate how commands are used.
+ if (demandedCommands._) {
+ u = `${base$0} <${__('command')}>\n`
+ } else {
+ 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(' ')} ` : ''
+
+ 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
+ },
+ {text: command[1]}
+ )
+ const hints = []
+ if (command[2]) hints.push(`[${__('default:').slice(0, -1)}]`) // TODO hacking around i18n here
+ if (command[3] && command[3].length) {
+ hints.push(`[${__('aliases:')} ${command[3].join(', ')}]`)
+ }
+ if (hints.length) {
+ ui.div({text: hints.join(' '), padding: [0, 0, 0, 2], align: 'right'})
+ } else {
+ ui.div()
+ }
+ })
+
+ ui.div()
+ }
+
+ // 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.
+ if (!groups[defaultGroup]) groups[defaultGroup] = []
+ addUngroupedKeys(keys, options.alias, groups)
+
+ // display 'Options:' table along with any custom tables:
+ Object.keys(groups).forEach((groupName) => {
+ if (!groups[groupName].length) return
+
+ ui.div(__(groupName))
+
+ // if we've grouped the key 'f', but 'f' aliases 'foobar',
+ // normalizedKeys should contain only 'foobar'.
+ const normalizedKeys = groups[groupName].map((key) => {
+ if (~aliasKeys.indexOf(key)) return key
+ for (let i = 0, aliasKey; (aliasKey = aliasKeys[i]) !== undefined; i++) {
+ if (~(options.alias[aliasKey] || []).indexOf(key)) return aliasKey
+ }
+ return key
+ })
+
+ // actually generate the switches string --foo, -f, --bar.
+ const switches = normalizedKeys.reduce((acc, 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 (sw.length > 1 ? '--' : '-') + sw
+ })
+ .join(', ')
+
+ return acc
+ }, {})
+
+ normalizedKeys.forEach((key) => {
+ const kswitch = switches[key]
+ let desc = descriptions[key] || ''
+ let type = null
+
+ if (~desc.lastIndexOf(deferY18nLookupPrefix)) desc = __(desc.substring(deferY18nLookupPrefix.length))
+
+ if (~options.boolean.indexOf(key)) type = `[${__('boolean')}]`
+ if (~options.count.indexOf(key)) type = `[${__('count')}]`
+ if (~options.string.indexOf(key)) type = `[${__('string')}]`
+ if (~options.normalize.indexOf(key)) type = `[${__('string')}]`
+ if (~options.array.indexOf(key)) type = `[${__('array')}]`
+ if (~options.number.indexOf(key)) type = `[${__('number')}]`
+
+ const extra = [
+ 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: 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()
+ })
+
+ ui.div()
+ })
+
+ // describe some common use-cases for your application.
+ if (examples.length) {
+ ui.div(__('Examples:'))
+
+ examples.forEach((example) => {
+ example[0] = example[0].replace(/\$0/g, base$0)
+ })
+
+ examples.forEach((example) => {
+ if (example[1] === '') {
+ ui.div(
+ {
+ text: example[0],
+ padding: [0, 2, 0, 2]
+ }
+ )
+ } else {
+ ui.div(
+ {
+ text: example[0],
+ padding: [0, 2, 0, 2],
+ width: maxWidth(examples, theWrap) + 4
+ }, {
+ text: example[1]
+ }
+ )
+ }
+ })
+
+ ui.div()
+ }
+
+ // the usage string.
+ if (epilog) {
+ const e = epilog.replace(/\$0/g, base$0)
+ ui.div(`${e}\n`)
+ }
+
+ return ui.toString()
+ }
+
+ // 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.keys(table).map(key => [table[key]])
+ }
+
+ 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, 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) => {
+ // 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)) yargs.count(key)
+ if (~options.string.indexOf(alias)) yargs.string(key)
+ if (~options.normalize.indexOf(alias)) yargs.normalize(key)
+ if (~options.array.indexOf(alias)) yargs.array(key)
+ if (~options.number.indexOf(alias)) yargs.number(key)
+ })
+ })
+ }
+
+ // given a set of keys, place any keys that are
+ // ungrouped under the 'Options:' grouping.
+ function addUngroupedKeys (keys, aliases, groups) {
+ let groupedKeys = []
+ let toCheck = null
+ Object.keys(groups).forEach((group) => {
+ groupedKeys = groupedKeys.concat(groups[group])
+ })
+
+ keys.forEach((key) => {
+ toCheck = [key].concat(aliases[key])
+ if (!toCheck.some(k => groupedKeys.indexOf(k) !== -1)) {
+ groups[defaultGroup].push(key)
+ }
+ })
+ return groupedKeys
+ }
+
+ self.showHelp = (level) => {
+ const logger = yargs._getLoggerInstance()
+ if (!level) level = 'error'
+ const emit = typeof level === 'function' ? level : logger[level]
+ emit(self.help())
+ }
+
+ self.functionDescription = (fn) => {
+ const description = fn.name ? require('decamelize')(fn.name, '-') : __('generated-value')
+ return ['(', description, ')'].join('')
+ }
+
+ self.stringifiedValues = function stringifiedValues (values, separator) {
+ let string = ''
+ const sep = separator || ', '
+ const array = [].concat(values)
+
+ if (!values || !array.length) return string
+
+ 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 null
+
+ if (defaultDescription) {
+ string += defaultDescription
+ } else {
+ switch (typeof value) {
+ case 'string':
+ string += `"${value}"`
+ break
+ case 'object':
+ string += JSON.stringify(value)
+ break
+ default:
+ string += value
+ }
+ }
+
+ return `${string}]`
+ }
+
+ // guess the width of the console window, max-width 80.
+ function windowWidth () {
+ const maxWidth = 80
+ 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) => {
+ version = ver
+ }
+
+ self.showVersion = () => {
+ const logger = yargs._getLoggerInstance()
+ logger.log(version)
+ }
+
+ self.reset = function reset (localLookup) {
+ // do not reset wrap here
+ // do not reset fails here
+ failMessage = null
+ failureOutput = false
+ usages = []
+ usageDisabled = false
+ epilog = undefined
+ examples = []
+ commands = []
+ descriptions = objFilter(descriptions, (k, v) => !localLookup[k])
+ return self
+ }
+
+ let frozen
+ self.freeze = function freeze () {
+ frozen = {}
+ frozen.failMessage = failMessage
+ frozen.failureOutput = failureOutput
+ frozen.usages = usages
+ frozen.usageDisabled = usageDisabled
+ frozen.epilog = epilog
+ frozen.examples = examples
+ frozen.commands = commands
+ frozen.descriptions = descriptions
+ }
+ self.unfreeze = function unfreeze () {
+ failMessage = frozen.failMessage
+ failureOutput = frozen.failureOutput
+ usages = frozen.usages
+ usageDisabled = frozen.usageDisabled
+ epilog = frozen.epilog
+ examples = frozen.examples
+ commands = frozen.commands
+ descriptions = frozen.descriptions
+ frozen = undefined
+ }
+
+ return self
+}