[6a3a178] | 1 | 'use strict'
|
---|
| 2 | const argsert = require('./lib/argsert')
|
---|
| 3 | const fs = require('fs')
|
---|
| 4 | const Command = require('./lib/command')
|
---|
| 5 | const Completion = require('./lib/completion')
|
---|
| 6 | const Parser = require('yargs-parser')
|
---|
| 7 | const path = require('path')
|
---|
| 8 | const Usage = require('./lib/usage')
|
---|
| 9 | const Validation = require('./lib/validation')
|
---|
| 10 | const Y18n = require('y18n')
|
---|
| 11 | const objFilter = require('./lib/obj-filter')
|
---|
| 12 | const setBlocking = require('set-blocking')
|
---|
| 13 | const applyExtends = require('./lib/apply-extends')
|
---|
| 14 | const { globalMiddlewareFactory } = require('./lib/middleware')
|
---|
| 15 | const YError = require('./lib/yerror')
|
---|
| 16 |
|
---|
| 17 | exports = module.exports = Yargs
|
---|
| 18 | function Yargs (processArgs, cwd, parentRequire) {
|
---|
| 19 | processArgs = processArgs || [] // handle calling yargs().
|
---|
| 20 |
|
---|
| 21 | const self = {}
|
---|
| 22 | let command = null
|
---|
| 23 | let completion = null
|
---|
| 24 | let groups = {}
|
---|
| 25 | let globalMiddleware = []
|
---|
| 26 | let output = ''
|
---|
| 27 | let preservedGroups = {}
|
---|
| 28 | let usage = null
|
---|
| 29 | let validation = null
|
---|
| 30 |
|
---|
| 31 | const y18n = Y18n({
|
---|
| 32 | directory: path.resolve(__dirname, './locales'),
|
---|
| 33 | updateFiles: false
|
---|
| 34 | })
|
---|
| 35 |
|
---|
| 36 | self.middleware = globalMiddlewareFactory(globalMiddleware, self)
|
---|
| 37 |
|
---|
| 38 | if (!cwd) cwd = process.cwd()
|
---|
| 39 |
|
---|
| 40 | self.scriptName = function scriptName (scriptName) {
|
---|
| 41 | self.$0 = scriptName
|
---|
| 42 | return self
|
---|
| 43 | }
|
---|
| 44 |
|
---|
| 45 | // ignore the node bin, specify this in your
|
---|
| 46 | // bin file with #!/usr/bin/env node
|
---|
| 47 | if (/\b(node|iojs|electron)(\.exe)?$/.test(process.argv[0])) {
|
---|
| 48 | self.$0 = process.argv.slice(1, 2)
|
---|
| 49 | } else {
|
---|
| 50 | self.$0 = process.argv.slice(0, 1)
|
---|
| 51 | }
|
---|
| 52 |
|
---|
| 53 | self.$0 = self.$0
|
---|
| 54 | .map((x, i) => {
|
---|
| 55 | const b = rebase(cwd, x)
|
---|
| 56 | return x.match(/^(\/|([a-zA-Z]:)?\\)/) && b.length < x.length ? b : x
|
---|
| 57 | })
|
---|
| 58 | .join(' ').trim()
|
---|
| 59 |
|
---|
| 60 | if (process.env._ !== undefined && process.argv[1] === process.env._) {
|
---|
| 61 | self.$0 = process.env._.replace(
|
---|
| 62 | `${path.dirname(process.execPath)}/`, ''
|
---|
| 63 | )
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | // use context object to keep track of resets, subcommand execution, etc
|
---|
| 67 | // submodules should modify and check the state of context as necessary
|
---|
| 68 | const context = { resets: -1, commands: [], fullCommands: [], files: [] }
|
---|
| 69 | self.getContext = () => context
|
---|
| 70 |
|
---|
| 71 | // puts yargs back into an initial state. any keys
|
---|
| 72 | // that have been set to "global" will not be reset
|
---|
| 73 | // by this action.
|
---|
| 74 | let options
|
---|
| 75 | self.resetOptions = self.reset = function resetOptions (aliases) {
|
---|
| 76 | context.resets++
|
---|
| 77 | aliases = aliases || {}
|
---|
| 78 | options = options || {}
|
---|
| 79 | // put yargs back into an initial state, this
|
---|
| 80 | // logic is used to build a nested command
|
---|
| 81 | // hierarchy.
|
---|
| 82 | const tmpOptions = {}
|
---|
| 83 | tmpOptions.local = options.local ? options.local : []
|
---|
| 84 | tmpOptions.configObjects = options.configObjects ? options.configObjects : []
|
---|
| 85 |
|
---|
| 86 | // if a key has been explicitly set as local,
|
---|
| 87 | // we should reset it before passing options to command.
|
---|
| 88 | const localLookup = {}
|
---|
| 89 | tmpOptions.local.forEach((l) => {
|
---|
| 90 | localLookup[l] = true
|
---|
| 91 | ;(aliases[l] || []).forEach((a) => {
|
---|
| 92 | localLookup[a] = true
|
---|
| 93 | })
|
---|
| 94 | })
|
---|
| 95 |
|
---|
| 96 | // preserve all groups not set to local.
|
---|
| 97 | preservedGroups = Object.keys(groups).reduce((acc, groupName) => {
|
---|
| 98 | const keys = groups[groupName].filter(key => !(key in localLookup))
|
---|
| 99 | if (keys.length > 0) {
|
---|
| 100 | acc[groupName] = keys
|
---|
| 101 | }
|
---|
| 102 | return acc
|
---|
| 103 | }, {})
|
---|
| 104 | // groups can now be reset
|
---|
| 105 | groups = {}
|
---|
| 106 |
|
---|
| 107 | const arrayOptions = [
|
---|
| 108 | 'array', 'boolean', 'string', 'skipValidation',
|
---|
| 109 | 'count', 'normalize', 'number',
|
---|
| 110 | 'hiddenOptions'
|
---|
| 111 | ]
|
---|
| 112 |
|
---|
| 113 | const objectOptions = [
|
---|
| 114 | 'narg', 'key', 'alias', 'default', 'defaultDescription',
|
---|
| 115 | 'config', 'choices', 'demandedOptions', 'demandedCommands', 'coerce'
|
---|
| 116 | ]
|
---|
| 117 |
|
---|
| 118 | arrayOptions.forEach((k) => {
|
---|
| 119 | tmpOptions[k] = (options[k] || []).filter(k => !localLookup[k])
|
---|
| 120 | })
|
---|
| 121 |
|
---|
| 122 | objectOptions.forEach((k) => {
|
---|
| 123 | tmpOptions[k] = objFilter(options[k], (k, v) => !localLookup[k])
|
---|
| 124 | })
|
---|
| 125 |
|
---|
| 126 | tmpOptions.envPrefix = options.envPrefix
|
---|
| 127 | options = tmpOptions
|
---|
| 128 |
|
---|
| 129 | // if this is the first time being executed, create
|
---|
| 130 | // instances of all our helpers -- otherwise just reset.
|
---|
| 131 | usage = usage ? usage.reset(localLookup) : Usage(self, y18n)
|
---|
| 132 | validation = validation ? validation.reset(localLookup) : Validation(self, usage, y18n)
|
---|
| 133 | command = command ? command.reset() : Command(self, usage, validation, globalMiddleware)
|
---|
| 134 | if (!completion) completion = Completion(self, usage, command)
|
---|
| 135 |
|
---|
| 136 | completionCommand = null
|
---|
| 137 | output = ''
|
---|
| 138 | exitError = null
|
---|
| 139 | hasOutput = false
|
---|
| 140 | self.parsed = false
|
---|
| 141 |
|
---|
| 142 | return self
|
---|
| 143 | }
|
---|
| 144 | self.resetOptions()
|
---|
| 145 |
|
---|
| 146 | // temporary hack: allow "freezing" of reset-able state for parse(msg, cb)
|
---|
| 147 | let frozen
|
---|
| 148 | function freeze () {
|
---|
| 149 | frozen = {}
|
---|
| 150 | frozen.options = options
|
---|
| 151 | frozen.configObjects = options.configObjects.slice(0)
|
---|
| 152 | frozen.exitProcess = exitProcess
|
---|
| 153 | frozen.groups = groups
|
---|
| 154 | usage.freeze()
|
---|
| 155 | validation.freeze()
|
---|
| 156 | command.freeze()
|
---|
| 157 | frozen.strict = strict
|
---|
| 158 | frozen.completionCommand = completionCommand
|
---|
| 159 | frozen.output = output
|
---|
| 160 | frozen.exitError = exitError
|
---|
| 161 | frozen.hasOutput = hasOutput
|
---|
| 162 | frozen.parsed = self.parsed
|
---|
| 163 | }
|
---|
| 164 | function unfreeze () {
|
---|
| 165 | options = frozen.options
|
---|
| 166 | options.configObjects = frozen.configObjects
|
---|
| 167 | exitProcess = frozen.exitProcess
|
---|
| 168 | groups = frozen.groups
|
---|
| 169 | output = frozen.output
|
---|
| 170 | exitError = frozen.exitError
|
---|
| 171 | hasOutput = frozen.hasOutput
|
---|
| 172 | self.parsed = frozen.parsed
|
---|
| 173 | usage.unfreeze()
|
---|
| 174 | validation.unfreeze()
|
---|
| 175 | command.unfreeze()
|
---|
| 176 | strict = frozen.strict
|
---|
| 177 | completionCommand = frozen.completionCommand
|
---|
| 178 | parseFn = null
|
---|
| 179 | parseContext = null
|
---|
| 180 | frozen = undefined
|
---|
| 181 | }
|
---|
| 182 |
|
---|
| 183 | self.boolean = function (keys) {
|
---|
| 184 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 185 | populateParserHintArray('boolean', keys)
|
---|
| 186 | return self
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | self.array = function (keys) {
|
---|
| 190 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 191 | populateParserHintArray('array', keys)
|
---|
| 192 | return self
|
---|
| 193 | }
|
---|
| 194 |
|
---|
| 195 | self.number = function (keys) {
|
---|
| 196 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 197 | populateParserHintArray('number', keys)
|
---|
| 198 | return self
|
---|
| 199 | }
|
---|
| 200 |
|
---|
| 201 | self.normalize = function (keys) {
|
---|
| 202 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 203 | populateParserHintArray('normalize', keys)
|
---|
| 204 | return self
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | self.count = function (keys) {
|
---|
| 208 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 209 | populateParserHintArray('count', keys)
|
---|
| 210 | return self
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | self.string = function (keys) {
|
---|
| 214 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 215 | populateParserHintArray('string', keys)
|
---|
| 216 | return self
|
---|
| 217 | }
|
---|
| 218 |
|
---|
| 219 | self.requiresArg = function (keys) {
|
---|
| 220 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 221 | populateParserHintObject(self.nargs, false, 'narg', keys, 1)
|
---|
| 222 | return self
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | self.skipValidation = function (keys) {
|
---|
| 226 | argsert('<array|string>', [keys], arguments.length)
|
---|
| 227 | populateParserHintArray('skipValidation', keys)
|
---|
| 228 | return self
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | function populateParserHintArray (type, keys, value) {
|
---|
| 232 | keys = [].concat(keys)
|
---|
| 233 | keys.forEach((key) => {
|
---|
| 234 | key = sanitizeKey(key)
|
---|
| 235 | options[type].push(key)
|
---|
| 236 | })
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | self.nargs = function (key, value) {
|
---|
| 240 | argsert('<string|object|array> [number]', [key, value], arguments.length)
|
---|
| 241 | populateParserHintObject(self.nargs, false, 'narg', key, value)
|
---|
| 242 | return self
|
---|
| 243 | }
|
---|
| 244 |
|
---|
| 245 | self.choices = function (key, value) {
|
---|
| 246 | argsert('<object|string|array> [string|array]', [key, value], arguments.length)
|
---|
| 247 | populateParserHintObject(self.choices, true, 'choices', key, value)
|
---|
| 248 | return self
|
---|
| 249 | }
|
---|
| 250 |
|
---|
| 251 | self.alias = function (key, value) {
|
---|
| 252 | argsert('<object|string|array> [string|array]', [key, value], arguments.length)
|
---|
| 253 | populateParserHintObject(self.alias, true, 'alias', key, value)
|
---|
| 254 | return self
|
---|
| 255 | }
|
---|
| 256 |
|
---|
| 257 | // TODO: actually deprecate self.defaults.
|
---|
| 258 | self.default = self.defaults = function (key, value, defaultDescription) {
|
---|
| 259 | argsert('<object|string|array> [*] [string]', [key, value, defaultDescription], arguments.length)
|
---|
| 260 | if (defaultDescription) options.defaultDescription[key] = defaultDescription
|
---|
| 261 | if (typeof value === 'function') {
|
---|
| 262 | if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
|
---|
| 263 | value = value.call()
|
---|
| 264 | }
|
---|
| 265 | populateParserHintObject(self.default, false, 'default', key, value)
|
---|
| 266 | return self
|
---|
| 267 | }
|
---|
| 268 |
|
---|
| 269 | self.describe = function (key, desc) {
|
---|
| 270 | argsert('<object|string|array> [string]', [key, desc], arguments.length)
|
---|
| 271 | populateParserHintObject(self.describe, false, 'key', key, true)
|
---|
| 272 | usage.describe(key, desc)
|
---|
| 273 | return self
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | self.demandOption = function (keys, msg) {
|
---|
| 277 | argsert('<object|string|array> [string]', [keys, msg], arguments.length)
|
---|
| 278 | populateParserHintObject(self.demandOption, false, 'demandedOptions', keys, msg)
|
---|
| 279 | return self
|
---|
| 280 | }
|
---|
| 281 |
|
---|
| 282 | self.coerce = function (keys, value) {
|
---|
| 283 | argsert('<object|string|array> [function]', [keys, value], arguments.length)
|
---|
| 284 | populateParserHintObject(self.coerce, false, 'coerce', keys, value)
|
---|
| 285 | return self
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | function populateParserHintObject (builder, isArray, type, key, value) {
|
---|
| 289 | if (Array.isArray(key)) {
|
---|
| 290 | const temp = Object.create(null)
|
---|
| 291 | // an array of keys with one value ['x', 'y', 'z'], function parse () {}
|
---|
| 292 | key.forEach((k) => {
|
---|
| 293 | temp[k] = value
|
---|
| 294 | })
|
---|
| 295 | builder(temp)
|
---|
| 296 | } else if (typeof key === 'object') {
|
---|
| 297 | // an object of key value pairs: {'x': parse () {}, 'y': parse() {}}
|
---|
| 298 | Object.keys(key).forEach((k) => {
|
---|
| 299 | builder(k, key[k])
|
---|
| 300 | })
|
---|
| 301 | } else {
|
---|
| 302 | key = sanitizeKey(key)
|
---|
| 303 | // a single key value pair 'x', parse() {}
|
---|
| 304 | if (isArray) {
|
---|
| 305 | options[type][key] = (options[type][key] || []).concat(value)
|
---|
| 306 | } else {
|
---|
| 307 | options[type][key] = value
|
---|
| 308 | }
|
---|
| 309 | }
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | // TODO(bcoe): in future major versions move more objects towards
|
---|
| 313 | // Object.create(null):
|
---|
| 314 | function sanitizeKey (key) {
|
---|
| 315 | if (key === '__proto__') return '___proto___'
|
---|
| 316 | return key
|
---|
| 317 | }
|
---|
| 318 |
|
---|
| 319 | function deleteFromParserHintObject (optionKey) {
|
---|
| 320 | // delete from all parsing hints:
|
---|
| 321 | // boolean, array, key, alias, etc.
|
---|
| 322 | Object.keys(options).forEach((hintKey) => {
|
---|
| 323 | const hint = options[hintKey]
|
---|
| 324 | if (Array.isArray(hint)) {
|
---|
| 325 | if (~hint.indexOf(optionKey)) hint.splice(hint.indexOf(optionKey), 1)
|
---|
| 326 | } else if (typeof hint === 'object') {
|
---|
| 327 | delete hint[optionKey]
|
---|
| 328 | }
|
---|
| 329 | })
|
---|
| 330 | // now delete the description from usage.js.
|
---|
| 331 | delete usage.getDescriptions()[optionKey]
|
---|
| 332 | }
|
---|
| 333 |
|
---|
| 334 | self.config = function config (key, msg, parseFn) {
|
---|
| 335 | argsert('[object|string] [string|function] [function]', [key, msg, parseFn], arguments.length)
|
---|
| 336 | // allow a config object to be provided directly.
|
---|
| 337 | if (typeof key === 'object') {
|
---|
| 338 | key = applyExtends(key, cwd)
|
---|
| 339 | options.configObjects = (options.configObjects || []).concat(key)
|
---|
| 340 | return self
|
---|
| 341 | }
|
---|
| 342 |
|
---|
| 343 | // allow for a custom parsing function.
|
---|
| 344 | if (typeof msg === 'function') {
|
---|
| 345 | parseFn = msg
|
---|
| 346 | msg = null
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | key = key || 'config'
|
---|
| 350 | self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file'))
|
---|
| 351 | ;(Array.isArray(key) ? key : [key]).forEach((k) => {
|
---|
| 352 | options.config[k] = parseFn || true
|
---|
| 353 | })
|
---|
| 354 |
|
---|
| 355 | return self
|
---|
| 356 | }
|
---|
| 357 |
|
---|
| 358 | self.example = function (cmd, description) {
|
---|
| 359 | argsert('<string> [string]', [cmd, description], arguments.length)
|
---|
| 360 | usage.example(cmd, description)
|
---|
| 361 | return self
|
---|
| 362 | }
|
---|
| 363 |
|
---|
| 364 | self.command = function (cmd, description, builder, handler, middlewares) {
|
---|
| 365 | argsert('<string|array|object> [string|boolean] [function|object] [function] [array]', [cmd, description, builder, handler, middlewares], arguments.length)
|
---|
| 366 | command.addHandler(cmd, description, builder, handler, middlewares)
|
---|
| 367 | return self
|
---|
| 368 | }
|
---|
| 369 |
|
---|
| 370 | self.commandDir = function (dir, opts) {
|
---|
| 371 | argsert('<string> [object]', [dir, opts], arguments.length)
|
---|
| 372 | const req = parentRequire || require
|
---|
| 373 | command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
|
---|
| 374 | return self
|
---|
| 375 | }
|
---|
| 376 |
|
---|
| 377 | // TODO: deprecate self.demand in favor of
|
---|
| 378 | // .demandCommand() .demandOption().
|
---|
| 379 | self.demand = self.required = self.require = function demand (keys, max, msg) {
|
---|
| 380 | // you can optionally provide a 'max' key,
|
---|
| 381 | // which will raise an exception if too many '_'
|
---|
| 382 | // options are provided.
|
---|
| 383 | if (Array.isArray(max)) {
|
---|
| 384 | max.forEach((key) => {
|
---|
| 385 | self.demandOption(key, msg)
|
---|
| 386 | })
|
---|
| 387 | max = Infinity
|
---|
| 388 | } else if (typeof max !== 'number') {
|
---|
| 389 | msg = max
|
---|
| 390 | max = Infinity
|
---|
| 391 | }
|
---|
| 392 |
|
---|
| 393 | if (typeof keys === 'number') {
|
---|
| 394 | self.demandCommand(keys, max, msg, msg)
|
---|
| 395 | } else if (Array.isArray(keys)) {
|
---|
| 396 | keys.forEach((key) => {
|
---|
| 397 | self.demandOption(key, msg)
|
---|
| 398 | })
|
---|
| 399 | } else {
|
---|
| 400 | if (typeof msg === 'string') {
|
---|
| 401 | self.demandOption(keys, msg)
|
---|
| 402 | } else if (msg === true || typeof msg === 'undefined') {
|
---|
| 403 | self.demandOption(keys)
|
---|
| 404 | }
|
---|
| 405 | }
|
---|
| 406 |
|
---|
| 407 | return self
|
---|
| 408 | }
|
---|
| 409 |
|
---|
| 410 | self.demandCommand = function demandCommand (min, max, minMsg, maxMsg) {
|
---|
| 411 | argsert('[number] [number|string] [string|null|undefined] [string|null|undefined]', [min, max, minMsg, maxMsg], arguments.length)
|
---|
| 412 |
|
---|
| 413 | if (typeof min === 'undefined') min = 1
|
---|
| 414 |
|
---|
| 415 | if (typeof max !== 'number') {
|
---|
| 416 | minMsg = max
|
---|
| 417 | max = Infinity
|
---|
| 418 | }
|
---|
| 419 |
|
---|
| 420 | self.global('_', false)
|
---|
| 421 |
|
---|
| 422 | options.demandedCommands._ = {
|
---|
| 423 | min,
|
---|
| 424 | max,
|
---|
| 425 | minMsg,
|
---|
| 426 | maxMsg
|
---|
| 427 | }
|
---|
| 428 |
|
---|
| 429 | return self
|
---|
| 430 | }
|
---|
| 431 |
|
---|
| 432 | self.getDemandedOptions = () => {
|
---|
| 433 | argsert([], 0)
|
---|
| 434 | return options.demandedOptions
|
---|
| 435 | }
|
---|
| 436 |
|
---|
| 437 | self.getDemandedCommands = () => {
|
---|
| 438 | argsert([], 0)
|
---|
| 439 | return options.demandedCommands
|
---|
| 440 | }
|
---|
| 441 |
|
---|
| 442 | self.implies = function (key, value) {
|
---|
| 443 | argsert('<string|object> [number|string|array]', [key, value], arguments.length)
|
---|
| 444 | validation.implies(key, value)
|
---|
| 445 | return self
|
---|
| 446 | }
|
---|
| 447 |
|
---|
| 448 | self.conflicts = function (key1, key2) {
|
---|
| 449 | argsert('<string|object> [string|array]', [key1, key2], arguments.length)
|
---|
| 450 | validation.conflicts(key1, key2)
|
---|
| 451 | return self
|
---|
| 452 | }
|
---|
| 453 |
|
---|
| 454 | self.usage = function (msg, description, builder, handler) {
|
---|
| 455 | argsert('<string|null|undefined> [string|boolean] [function|object] [function]', [msg, description, builder, handler], arguments.length)
|
---|
| 456 |
|
---|
| 457 | if (description !== undefined) {
|
---|
| 458 | // .usage() can be used as an alias for defining
|
---|
| 459 | // a default command.
|
---|
| 460 | if ((msg || '').match(/^\$0( |$)/)) {
|
---|
| 461 | return self.command(msg, description, builder, handler)
|
---|
| 462 | } else {
|
---|
| 463 | throw new YError('.usage() description must start with $0 if being used as alias for .command()')
|
---|
| 464 | }
|
---|
| 465 | } else {
|
---|
| 466 | usage.usage(msg)
|
---|
| 467 | return self
|
---|
| 468 | }
|
---|
| 469 | }
|
---|
| 470 |
|
---|
| 471 | self.epilogue = self.epilog = function (msg) {
|
---|
| 472 | argsert('<string>', [msg], arguments.length)
|
---|
| 473 | usage.epilog(msg)
|
---|
| 474 | return self
|
---|
| 475 | }
|
---|
| 476 |
|
---|
| 477 | self.fail = function (f) {
|
---|
| 478 | argsert('<function>', [f], arguments.length)
|
---|
| 479 | usage.failFn(f)
|
---|
| 480 | return self
|
---|
| 481 | }
|
---|
| 482 |
|
---|
| 483 | self.check = function (f, _global) {
|
---|
| 484 | argsert('<function> [boolean]', [f, _global], arguments.length)
|
---|
| 485 | validation.check(f, _global !== false)
|
---|
| 486 | return self
|
---|
| 487 | }
|
---|
| 488 |
|
---|
| 489 | self.global = function global (globals, global) {
|
---|
| 490 | argsert('<string|array> [boolean]', [globals, global], arguments.length)
|
---|
| 491 | globals = [].concat(globals)
|
---|
| 492 | if (global !== false) {
|
---|
| 493 | options.local = options.local.filter(l => globals.indexOf(l) === -1)
|
---|
| 494 | } else {
|
---|
| 495 | globals.forEach((g) => {
|
---|
| 496 | if (options.local.indexOf(g) === -1) options.local.push(g)
|
---|
| 497 | })
|
---|
| 498 | }
|
---|
| 499 | return self
|
---|
| 500 | }
|
---|
| 501 |
|
---|
| 502 | self.pkgConf = function pkgConf (key, rootPath) {
|
---|
| 503 | argsert('<string> [string]', [key, rootPath], arguments.length)
|
---|
| 504 | let conf = null
|
---|
| 505 | // prefer cwd to require-main-filename in this method
|
---|
| 506 | // since we're looking for e.g. "nyc" config in nyc consumer
|
---|
| 507 | // rather than "yargs" config in nyc (where nyc is the main filename)
|
---|
| 508 | const obj = pkgUp(rootPath || cwd)
|
---|
| 509 |
|
---|
| 510 | // If an object exists in the key, add it to options.configObjects
|
---|
| 511 | if (obj[key] && typeof obj[key] === 'object') {
|
---|
| 512 | conf = applyExtends(obj[key], rootPath || cwd)
|
---|
| 513 | options.configObjects = (options.configObjects || []).concat(conf)
|
---|
| 514 | }
|
---|
| 515 |
|
---|
| 516 | return self
|
---|
| 517 | }
|
---|
| 518 |
|
---|
| 519 | const pkgs = {}
|
---|
| 520 | function pkgUp (rootPath) {
|
---|
| 521 | const npath = rootPath || '*'
|
---|
| 522 | if (pkgs[npath]) return pkgs[npath]
|
---|
| 523 | const findUp = require('find-up')
|
---|
| 524 |
|
---|
| 525 | let obj = {}
|
---|
| 526 | try {
|
---|
| 527 | let startDir = rootPath || require('require-main-filename')(parentRequire || require)
|
---|
| 528 |
|
---|
| 529 | // When called in an environment that lacks require.main.filename, such as a jest test runner,
|
---|
| 530 | // startDir is already process.cwd(), and should not be shortened.
|
---|
| 531 | // Whether or not it is _actually_ a directory (e.g., extensionless bin) is irrelevant, find-up handles it.
|
---|
| 532 | if (!rootPath && path.extname(startDir)) {
|
---|
| 533 | startDir = path.dirname(startDir)
|
---|
| 534 | }
|
---|
| 535 |
|
---|
| 536 | const pkgJsonPath = findUp.sync('package.json', {
|
---|
| 537 | cwd: startDir
|
---|
| 538 | })
|
---|
| 539 | obj = JSON.parse(fs.readFileSync(pkgJsonPath))
|
---|
| 540 | } catch (noop) {}
|
---|
| 541 |
|
---|
| 542 | pkgs[npath] = obj || {}
|
---|
| 543 | return pkgs[npath]
|
---|
| 544 | }
|
---|
| 545 |
|
---|
| 546 | let parseFn = null
|
---|
| 547 | let parseContext = null
|
---|
| 548 | self.parse = function parse (args, shortCircuit, _parseFn) {
|
---|
| 549 | argsert('[string|array] [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length)
|
---|
| 550 | if (typeof args === 'undefined') {
|
---|
| 551 | return self._parseArgs(processArgs)
|
---|
| 552 | }
|
---|
| 553 |
|
---|
| 554 | // a context object can optionally be provided, this allows
|
---|
| 555 | // additional information to be passed to a command handler.
|
---|
| 556 | if (typeof shortCircuit === 'object') {
|
---|
| 557 | parseContext = shortCircuit
|
---|
| 558 | shortCircuit = _parseFn
|
---|
| 559 | }
|
---|
| 560 |
|
---|
| 561 | // by providing a function as a second argument to
|
---|
| 562 | // parse you can capture output that would otherwise
|
---|
| 563 | // default to printing to stdout/stderr.
|
---|
| 564 | if (typeof shortCircuit === 'function') {
|
---|
| 565 | parseFn = shortCircuit
|
---|
| 566 | shortCircuit = null
|
---|
| 567 | }
|
---|
| 568 | // completion short-circuits the parsing process,
|
---|
| 569 | // skipping validation, etc.
|
---|
| 570 | if (!shortCircuit) processArgs = args
|
---|
| 571 |
|
---|
| 572 | freeze()
|
---|
| 573 | if (parseFn) exitProcess = false
|
---|
| 574 |
|
---|
| 575 | const parsed = self._parseArgs(args, shortCircuit)
|
---|
| 576 | if (parseFn) parseFn(exitError, parsed, output)
|
---|
| 577 | unfreeze()
|
---|
| 578 |
|
---|
| 579 | return parsed
|
---|
| 580 | }
|
---|
| 581 |
|
---|
| 582 | self._getParseContext = () => parseContext || {}
|
---|
| 583 |
|
---|
| 584 | self._hasParseCallback = () => !!parseFn
|
---|
| 585 |
|
---|
| 586 | self.option = self.options = function option (key, opt) {
|
---|
| 587 | argsert('<string|object> [object]', [key, opt], arguments.length)
|
---|
| 588 | if (typeof key === 'object') {
|
---|
| 589 | Object.keys(key).forEach((k) => {
|
---|
| 590 | self.options(k, key[k])
|
---|
| 591 | })
|
---|
| 592 | } else {
|
---|
| 593 | if (typeof opt !== 'object') {
|
---|
| 594 | opt = {}
|
---|
| 595 | }
|
---|
| 596 |
|
---|
| 597 | options.key[key] = true // track manually set keys.
|
---|
| 598 |
|
---|
| 599 | if (opt.alias) self.alias(key, opt.alias)
|
---|
| 600 |
|
---|
| 601 | const demand = opt.demand || opt.required || opt.require
|
---|
| 602 |
|
---|
| 603 | // deprecated, use 'demandOption' instead
|
---|
| 604 | if (demand) {
|
---|
| 605 | self.demand(key, demand)
|
---|
| 606 | }
|
---|
| 607 |
|
---|
| 608 | if (opt.demandOption) {
|
---|
| 609 | self.demandOption(key, typeof opt.demandOption === 'string' ? opt.demandOption : undefined)
|
---|
| 610 | }
|
---|
| 611 |
|
---|
| 612 | if ('conflicts' in opt) {
|
---|
| 613 | self.conflicts(key, opt.conflicts)
|
---|
| 614 | }
|
---|
| 615 |
|
---|
| 616 | if ('default' in opt) {
|
---|
| 617 | self.default(key, opt.default)
|
---|
| 618 | }
|
---|
| 619 |
|
---|
| 620 | if ('implies' in opt) {
|
---|
| 621 | self.implies(key, opt.implies)
|
---|
| 622 | }
|
---|
| 623 |
|
---|
| 624 | if ('nargs' in opt) {
|
---|
| 625 | self.nargs(key, opt.nargs)
|
---|
| 626 | }
|
---|
| 627 |
|
---|
| 628 | if (opt.config) {
|
---|
| 629 | self.config(key, opt.configParser)
|
---|
| 630 | }
|
---|
| 631 |
|
---|
| 632 | if (opt.normalize) {
|
---|
| 633 | self.normalize(key)
|
---|
| 634 | }
|
---|
| 635 |
|
---|
| 636 | if ('choices' in opt) {
|
---|
| 637 | self.choices(key, opt.choices)
|
---|
| 638 | }
|
---|
| 639 |
|
---|
| 640 | if ('coerce' in opt) {
|
---|
| 641 | self.coerce(key, opt.coerce)
|
---|
| 642 | }
|
---|
| 643 |
|
---|
| 644 | if ('group' in opt) {
|
---|
| 645 | self.group(key, opt.group)
|
---|
| 646 | }
|
---|
| 647 |
|
---|
| 648 | if (opt.boolean || opt.type === 'boolean') {
|
---|
| 649 | self.boolean(key)
|
---|
| 650 | if (opt.alias) self.boolean(opt.alias)
|
---|
| 651 | }
|
---|
| 652 |
|
---|
| 653 | if (opt.array || opt.type === 'array') {
|
---|
| 654 | self.array(key)
|
---|
| 655 | if (opt.alias) self.array(opt.alias)
|
---|
| 656 | }
|
---|
| 657 |
|
---|
| 658 | if (opt.number || opt.type === 'number') {
|
---|
| 659 | self.number(key)
|
---|
| 660 | if (opt.alias) self.number(opt.alias)
|
---|
| 661 | }
|
---|
| 662 |
|
---|
| 663 | if (opt.string || opt.type === 'string') {
|
---|
| 664 | self.string(key)
|
---|
| 665 | if (opt.alias) self.string(opt.alias)
|
---|
| 666 | }
|
---|
| 667 |
|
---|
| 668 | if (opt.count || opt.type === 'count') {
|
---|
| 669 | self.count(key)
|
---|
| 670 | }
|
---|
| 671 |
|
---|
| 672 | if (typeof opt.global === 'boolean') {
|
---|
| 673 | self.global(key, opt.global)
|
---|
| 674 | }
|
---|
| 675 |
|
---|
| 676 | if (opt.defaultDescription) {
|
---|
| 677 | options.defaultDescription[key] = opt.defaultDescription
|
---|
| 678 | }
|
---|
| 679 |
|
---|
| 680 | if (opt.skipValidation) {
|
---|
| 681 | self.skipValidation(key)
|
---|
| 682 | }
|
---|
| 683 |
|
---|
| 684 | const desc = opt.describe || opt.description || opt.desc
|
---|
| 685 | self.describe(key, desc)
|
---|
| 686 | if (opt.hidden) {
|
---|
| 687 | self.hide(key)
|
---|
| 688 | }
|
---|
| 689 |
|
---|
| 690 | if (opt.requiresArg) {
|
---|
| 691 | self.requiresArg(key)
|
---|
| 692 | }
|
---|
| 693 | }
|
---|
| 694 |
|
---|
| 695 | return self
|
---|
| 696 | }
|
---|
| 697 | self.getOptions = () => options
|
---|
| 698 |
|
---|
| 699 | self.positional = function (key, opts) {
|
---|
| 700 | argsert('<string> <object>', [key, opts], arguments.length)
|
---|
| 701 | if (context.resets === 0) {
|
---|
| 702 | throw new YError(".positional() can only be called in a command's builder function")
|
---|
| 703 | }
|
---|
| 704 |
|
---|
| 705 | // .positional() only supports a subset of the configuration
|
---|
| 706 | // options available to .option().
|
---|
| 707 | const supportedOpts = ['default', 'defaultDescription', 'implies', 'normalize',
|
---|
| 708 | 'choices', 'conflicts', 'coerce', 'type', 'describe',
|
---|
| 709 | 'desc', 'description', 'alias']
|
---|
| 710 | opts = objFilter(opts, (k, v) => {
|
---|
| 711 | let accept = supportedOpts.indexOf(k) !== -1
|
---|
| 712 | // type can be one of string|number|boolean.
|
---|
| 713 | if (k === 'type' && ['string', 'number', 'boolean'].indexOf(v) === -1) accept = false
|
---|
| 714 | return accept
|
---|
| 715 | })
|
---|
| 716 |
|
---|
| 717 | // copy over any settings that can be inferred from the command string.
|
---|
| 718 | const fullCommand = context.fullCommands[context.fullCommands.length - 1]
|
---|
| 719 | const parseOptions = fullCommand ? command.cmdToParseOptions(fullCommand) : {
|
---|
| 720 | array: [],
|
---|
| 721 | alias: {},
|
---|
| 722 | default: {},
|
---|
| 723 | demand: {}
|
---|
| 724 | }
|
---|
| 725 | Object.keys(parseOptions).forEach((pk) => {
|
---|
| 726 | if (Array.isArray(parseOptions[pk])) {
|
---|
| 727 | if (parseOptions[pk].indexOf(key) !== -1) opts[pk] = true
|
---|
| 728 | } else {
|
---|
| 729 | if (parseOptions[pk][key] && !(pk in opts)) opts[pk] = parseOptions[pk][key]
|
---|
| 730 | }
|
---|
| 731 | })
|
---|
| 732 | self.group(key, usage.getPositionalGroupName())
|
---|
| 733 | return self.option(key, opts)
|
---|
| 734 | }
|
---|
| 735 |
|
---|
| 736 | self.group = function group (opts, groupName) {
|
---|
| 737 | argsert('<string|array> <string>', [opts, groupName], arguments.length)
|
---|
| 738 | const existing = preservedGroups[groupName] || groups[groupName]
|
---|
| 739 | if (preservedGroups[groupName]) {
|
---|
| 740 | // we now only need to track this group name in groups.
|
---|
| 741 | delete preservedGroups[groupName]
|
---|
| 742 | }
|
---|
| 743 |
|
---|
| 744 | const seen = {}
|
---|
| 745 | groups[groupName] = (existing || []).concat(opts).filter((key) => {
|
---|
| 746 | if (seen[key]) return false
|
---|
| 747 | return (seen[key] = true)
|
---|
| 748 | })
|
---|
| 749 | return self
|
---|
| 750 | }
|
---|
| 751 | // combine explicit and preserved groups. explicit groups should be first
|
---|
| 752 | self.getGroups = () => Object.assign({}, groups, preservedGroups)
|
---|
| 753 |
|
---|
| 754 | // as long as options.envPrefix is not undefined,
|
---|
| 755 | // parser will apply env vars matching prefix to argv
|
---|
| 756 | self.env = function (prefix) {
|
---|
| 757 | argsert('[string|boolean]', [prefix], arguments.length)
|
---|
| 758 | if (prefix === false) options.envPrefix = undefined
|
---|
| 759 | else options.envPrefix = prefix || ''
|
---|
| 760 | return self
|
---|
| 761 | }
|
---|
| 762 |
|
---|
| 763 | self.wrap = function (cols) {
|
---|
| 764 | argsert('<number|null|undefined>', [cols], arguments.length)
|
---|
| 765 | usage.wrap(cols)
|
---|
| 766 | return self
|
---|
| 767 | }
|
---|
| 768 |
|
---|
| 769 | let strict = false
|
---|
| 770 | self.strict = function (enabled) {
|
---|
| 771 | argsert('[boolean]', [enabled], arguments.length)
|
---|
| 772 | strict = enabled !== false
|
---|
| 773 | return self
|
---|
| 774 | }
|
---|
| 775 | self.getStrict = () => strict
|
---|
| 776 |
|
---|
| 777 | let parserConfig = {}
|
---|
| 778 | self.parserConfiguration = function parserConfiguration (config) {
|
---|
| 779 | argsert('<object>', [config], arguments.length)
|
---|
| 780 | parserConfig = config
|
---|
| 781 | return self
|
---|
| 782 | }
|
---|
| 783 | self.getParserConfiguration = () => parserConfig
|
---|
| 784 |
|
---|
| 785 | self.showHelp = function (level) {
|
---|
| 786 | argsert('[string|function]', [level], arguments.length)
|
---|
| 787 | if (!self.parsed) self._parseArgs(processArgs) // run parser, if it has not already been executed.
|
---|
| 788 | if (command.hasDefaultCommand()) {
|
---|
| 789 | context.resets++ // override the restriction on top-level positoinals.
|
---|
| 790 | command.runDefaultBuilderOn(self, true)
|
---|
| 791 | }
|
---|
| 792 | usage.showHelp(level)
|
---|
| 793 | return self
|
---|
| 794 | }
|
---|
| 795 |
|
---|
| 796 | let versionOpt = null
|
---|
| 797 | self.version = function version (opt, msg, ver) {
|
---|
| 798 | const defaultVersionOpt = 'version'
|
---|
| 799 | argsert('[boolean|string] [string] [string]', [opt, msg, ver], arguments.length)
|
---|
| 800 |
|
---|
| 801 | // nuke the key previously configured
|
---|
| 802 | // to return version #.
|
---|
| 803 | if (versionOpt) {
|
---|
| 804 | deleteFromParserHintObject(versionOpt)
|
---|
| 805 | usage.version(undefined)
|
---|
| 806 | versionOpt = null
|
---|
| 807 | }
|
---|
| 808 |
|
---|
| 809 | if (arguments.length === 0) {
|
---|
| 810 | ver = guessVersion()
|
---|
| 811 | opt = defaultVersionOpt
|
---|
| 812 | } else if (arguments.length === 1) {
|
---|
| 813 | if (opt === false) { // disable default 'version' key.
|
---|
| 814 | return self
|
---|
| 815 | }
|
---|
| 816 | ver = opt
|
---|
| 817 | opt = defaultVersionOpt
|
---|
| 818 | } else if (arguments.length === 2) {
|
---|
| 819 | ver = msg
|
---|
| 820 | msg = null
|
---|
| 821 | }
|
---|
| 822 |
|
---|
| 823 | versionOpt = typeof opt === 'string' ? opt : defaultVersionOpt
|
---|
| 824 | msg = msg || usage.deferY18nLookup('Show version number')
|
---|
| 825 |
|
---|
| 826 | usage.version(ver || undefined)
|
---|
| 827 | self.boolean(versionOpt)
|
---|
| 828 | self.describe(versionOpt, msg)
|
---|
| 829 | return self
|
---|
| 830 | }
|
---|
| 831 |
|
---|
| 832 | function guessVersion () {
|
---|
| 833 | const obj = pkgUp()
|
---|
| 834 |
|
---|
| 835 | return obj.version || 'unknown'
|
---|
| 836 | }
|
---|
| 837 |
|
---|
| 838 | let helpOpt = null
|
---|
| 839 | self.addHelpOpt = self.help = function addHelpOpt (opt, msg) {
|
---|
| 840 | const defaultHelpOpt = 'help'
|
---|
| 841 | argsert('[string|boolean] [string]', [opt, msg], arguments.length)
|
---|
| 842 |
|
---|
| 843 | // nuke the key previously configured
|
---|
| 844 | // to return help.
|
---|
| 845 | if (helpOpt) {
|
---|
| 846 | deleteFromParserHintObject(helpOpt)
|
---|
| 847 | helpOpt = null
|
---|
| 848 | }
|
---|
| 849 |
|
---|
| 850 | if (arguments.length === 1) {
|
---|
| 851 | if (opt === false) return self
|
---|
| 852 | }
|
---|
| 853 |
|
---|
| 854 | // use arguments, fallback to defaults for opt and msg
|
---|
| 855 | helpOpt = typeof opt === 'string' ? opt : defaultHelpOpt
|
---|
| 856 | self.boolean(helpOpt)
|
---|
| 857 | self.describe(helpOpt, msg || usage.deferY18nLookup('Show help'))
|
---|
| 858 | return self
|
---|
| 859 | }
|
---|
| 860 |
|
---|
| 861 | const defaultShowHiddenOpt = 'show-hidden'
|
---|
| 862 | options.showHiddenOpt = defaultShowHiddenOpt
|
---|
| 863 | self.addShowHiddenOpt = self.showHidden = function addShowHiddenOpt (opt, msg) {
|
---|
| 864 | argsert('[string|boolean] [string]', [opt, msg], arguments.length)
|
---|
| 865 |
|
---|
| 866 | if (arguments.length === 1) {
|
---|
| 867 | if (opt === false) return self
|
---|
| 868 | }
|
---|
| 869 |
|
---|
| 870 | const showHiddenOpt = typeof opt === 'string' ? opt : defaultShowHiddenOpt
|
---|
| 871 | self.boolean(showHiddenOpt)
|
---|
| 872 | self.describe(showHiddenOpt, msg || usage.deferY18nLookup('Show hidden options'))
|
---|
| 873 | options.showHiddenOpt = showHiddenOpt
|
---|
| 874 | return self
|
---|
| 875 | }
|
---|
| 876 |
|
---|
| 877 | self.hide = function hide (key) {
|
---|
| 878 | argsert('<string|object>', [key], arguments.length)
|
---|
| 879 | options.hiddenOptions.push(key)
|
---|
| 880 | return self
|
---|
| 881 | }
|
---|
| 882 |
|
---|
| 883 | self.showHelpOnFail = function showHelpOnFail (enabled, message) {
|
---|
| 884 | argsert('[boolean|string] [string]', [enabled, message], arguments.length)
|
---|
| 885 | usage.showHelpOnFail(enabled, message)
|
---|
| 886 | return self
|
---|
| 887 | }
|
---|
| 888 |
|
---|
| 889 | var exitProcess = true
|
---|
| 890 | self.exitProcess = function (enabled) {
|
---|
| 891 | argsert('[boolean]', [enabled], arguments.length)
|
---|
| 892 | if (typeof enabled !== 'boolean') {
|
---|
| 893 | enabled = true
|
---|
| 894 | }
|
---|
| 895 | exitProcess = enabled
|
---|
| 896 | return self
|
---|
| 897 | }
|
---|
| 898 | self.getExitProcess = () => exitProcess
|
---|
| 899 |
|
---|
| 900 | var completionCommand = null
|
---|
| 901 | self.completion = function (cmd, desc, fn) {
|
---|
| 902 | argsert('[string] [string|boolean|function] [function]', [cmd, desc, fn], arguments.length)
|
---|
| 903 |
|
---|
| 904 | // a function to execute when generating
|
---|
| 905 | // completions can be provided as the second
|
---|
| 906 | // or third argument to completion.
|
---|
| 907 | if (typeof desc === 'function') {
|
---|
| 908 | fn = desc
|
---|
| 909 | desc = null
|
---|
| 910 | }
|
---|
| 911 |
|
---|
| 912 | // register the completion command.
|
---|
| 913 | completionCommand = cmd || 'completion'
|
---|
| 914 | if (!desc && desc !== false) {
|
---|
| 915 | desc = 'generate completion script'
|
---|
| 916 | }
|
---|
| 917 | self.command(completionCommand, desc)
|
---|
| 918 |
|
---|
| 919 | // a function can be provided
|
---|
| 920 | if (fn) completion.registerFunction(fn)
|
---|
| 921 |
|
---|
| 922 | return self
|
---|
| 923 | }
|
---|
| 924 |
|
---|
| 925 | self.showCompletionScript = function ($0) {
|
---|
| 926 | argsert('[string]', [$0], arguments.length)
|
---|
| 927 | $0 = $0 || self.$0
|
---|
| 928 | _logger.log(completion.generateCompletionScript($0, completionCommand))
|
---|
| 929 | return self
|
---|
| 930 | }
|
---|
| 931 |
|
---|
| 932 | self.getCompletion = function (args, done) {
|
---|
| 933 | argsert('<array> <function>', [args, done], arguments.length)
|
---|
| 934 | completion.getCompletion(args, done)
|
---|
| 935 | }
|
---|
| 936 |
|
---|
| 937 | self.locale = function (locale) {
|
---|
| 938 | argsert('[string]', [locale], arguments.length)
|
---|
| 939 | if (arguments.length === 0) {
|
---|
| 940 | guessLocale()
|
---|
| 941 | return y18n.getLocale()
|
---|
| 942 | }
|
---|
| 943 | detectLocale = false
|
---|
| 944 | y18n.setLocale(locale)
|
---|
| 945 | return self
|
---|
| 946 | }
|
---|
| 947 |
|
---|
| 948 | self.updateStrings = self.updateLocale = function (obj) {
|
---|
| 949 | argsert('<object>', [obj], arguments.length)
|
---|
| 950 | detectLocale = false
|
---|
| 951 | y18n.updateLocale(obj)
|
---|
| 952 | return self
|
---|
| 953 | }
|
---|
| 954 |
|
---|
| 955 | let detectLocale = true
|
---|
| 956 | self.detectLocale = function (detect) {
|
---|
| 957 | argsert('<boolean>', [detect], arguments.length)
|
---|
| 958 | detectLocale = detect
|
---|
| 959 | return self
|
---|
| 960 | }
|
---|
| 961 | self.getDetectLocale = () => detectLocale
|
---|
| 962 |
|
---|
| 963 | var hasOutput = false
|
---|
| 964 | var exitError = null
|
---|
| 965 | // maybe exit, always capture
|
---|
| 966 | // context about why we wanted to exit.
|
---|
| 967 | self.exit = (code, err) => {
|
---|
| 968 | hasOutput = true
|
---|
| 969 | exitError = err
|
---|
| 970 | if (exitProcess) process.exit(code)
|
---|
| 971 | }
|
---|
| 972 |
|
---|
| 973 | // we use a custom logger that buffers output,
|
---|
| 974 | // so that we can print to non-CLIs, e.g., chat-bots.
|
---|
| 975 | const _logger = {
|
---|
| 976 | log () {
|
---|
| 977 | const args = []
|
---|
| 978 | for (let i = 0; i < arguments.length; i++) args.push(arguments[i])
|
---|
| 979 | if (!self._hasParseCallback()) console.log.apply(console, args)
|
---|
| 980 | hasOutput = true
|
---|
| 981 | if (output.length) output += '\n'
|
---|
| 982 | output += args.join(' ')
|
---|
| 983 | },
|
---|
| 984 | error () {
|
---|
| 985 | const args = []
|
---|
| 986 | for (let i = 0; i < arguments.length; i++) args.push(arguments[i])
|
---|
| 987 | if (!self._hasParseCallback()) console.error.apply(console, args)
|
---|
| 988 | hasOutput = true
|
---|
| 989 | if (output.length) output += '\n'
|
---|
| 990 | output += args.join(' ')
|
---|
| 991 | }
|
---|
| 992 | }
|
---|
| 993 | self._getLoggerInstance = () => _logger
|
---|
| 994 | // has yargs output an error our help
|
---|
| 995 | // message in the current execution context.
|
---|
| 996 | self._hasOutput = () => hasOutput
|
---|
| 997 |
|
---|
| 998 | self._setHasOutput = () => {
|
---|
| 999 | hasOutput = true
|
---|
| 1000 | }
|
---|
| 1001 |
|
---|
| 1002 | let recommendCommands
|
---|
| 1003 | self.recommendCommands = function (recommend) {
|
---|
| 1004 | argsert('[boolean]', [recommend], arguments.length)
|
---|
| 1005 | recommendCommands = typeof recommend === 'boolean' ? recommend : true
|
---|
| 1006 | return self
|
---|
| 1007 | }
|
---|
| 1008 |
|
---|
| 1009 | self.getUsageInstance = () => usage
|
---|
| 1010 |
|
---|
| 1011 | self.getValidationInstance = () => validation
|
---|
| 1012 |
|
---|
| 1013 | self.getCommandInstance = () => command
|
---|
| 1014 |
|
---|
| 1015 | self.terminalWidth = () => {
|
---|
| 1016 | argsert([], 0)
|
---|
| 1017 | return typeof process.stdout.columns !== 'undefined' ? process.stdout.columns : null
|
---|
| 1018 | }
|
---|
| 1019 |
|
---|
| 1020 | Object.defineProperty(self, 'argv', {
|
---|
| 1021 | get: () => self._parseArgs(processArgs),
|
---|
| 1022 | enumerable: true
|
---|
| 1023 | })
|
---|
| 1024 |
|
---|
| 1025 | self._parseArgs = function parseArgs (args, shortCircuit, _skipValidation, commandIndex) {
|
---|
| 1026 | let skipValidation = !!_skipValidation
|
---|
| 1027 | args = args || processArgs
|
---|
| 1028 |
|
---|
| 1029 | options.__ = y18n.__
|
---|
| 1030 | options.configuration = self.getParserConfiguration()
|
---|
| 1031 |
|
---|
| 1032 | // Deprecated
|
---|
| 1033 | let pkgConfig = pkgUp()['yargs']
|
---|
| 1034 | if (pkgConfig) {
|
---|
| 1035 | console.warn('Configuring yargs through package.json is deprecated and will be removed in the next major release, please use the JS API instead.')
|
---|
| 1036 | options.configuration = Object.assign({}, pkgConfig, options.configuration)
|
---|
| 1037 | }
|
---|
| 1038 |
|
---|
| 1039 | const parsed = Parser.detailed(args, options)
|
---|
| 1040 | let argv = parsed.argv
|
---|
| 1041 | if (parseContext) argv = Object.assign({}, argv, parseContext)
|
---|
| 1042 | const aliases = parsed.aliases
|
---|
| 1043 |
|
---|
| 1044 | argv.$0 = self.$0
|
---|
| 1045 | self.parsed = parsed
|
---|
| 1046 |
|
---|
| 1047 | try {
|
---|
| 1048 | guessLocale() // guess locale lazily, so that it can be turned off in chain.
|
---|
| 1049 |
|
---|
| 1050 | // while building up the argv object, there
|
---|
| 1051 | // are two passes through the parser. If completion
|
---|
| 1052 | // is being performed short-circuit on the first pass.
|
---|
| 1053 | if (shortCircuit) {
|
---|
| 1054 | return argv
|
---|
| 1055 | }
|
---|
| 1056 |
|
---|
| 1057 | // if there's a handler associated with a
|
---|
| 1058 | // command defer processing to it.
|
---|
| 1059 | if (helpOpt) {
|
---|
| 1060 | // consider any multi-char helpOpt alias as a valid help command
|
---|
| 1061 | // unless all helpOpt aliases are single-char
|
---|
| 1062 | // note that parsed.aliases is a normalized bidirectional map :)
|
---|
| 1063 | const helpCmds = [helpOpt]
|
---|
| 1064 | .concat(aliases[helpOpt] || [])
|
---|
| 1065 | .filter(k => k.length > 1)
|
---|
| 1066 | // check if help should trigger and strip it from _.
|
---|
| 1067 | if (~helpCmds.indexOf(argv._[argv._.length - 1])) {
|
---|
| 1068 | argv._.pop()
|
---|
| 1069 | argv[helpOpt] = true
|
---|
| 1070 | }
|
---|
| 1071 | }
|
---|
| 1072 |
|
---|
| 1073 | const handlerKeys = command.getCommands()
|
---|
| 1074 | const requestCompletions = completion.completionKey in argv
|
---|
| 1075 | const skipRecommendation = argv[helpOpt] || requestCompletions
|
---|
| 1076 | const skipDefaultCommand = skipRecommendation && (handlerKeys.length > 1 || handlerKeys[0] !== '$0')
|
---|
| 1077 |
|
---|
| 1078 | if (argv._.length) {
|
---|
| 1079 | if (handlerKeys.length) {
|
---|
| 1080 | let firstUnknownCommand
|
---|
| 1081 | for (let i = (commandIndex || 0), cmd; argv._[i] !== undefined; i++) {
|
---|
| 1082 | cmd = String(argv._[i])
|
---|
| 1083 | if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
|
---|
| 1084 | // commands are executed using a recursive algorithm that executes
|
---|
| 1085 | // the deepest command first; we keep track of the position in the
|
---|
| 1086 | // argv._ array that is currently being executed.
|
---|
| 1087 | return command.runCommand(cmd, self, parsed, i + 1)
|
---|
| 1088 | } else if (!firstUnknownCommand && cmd !== completionCommand) {
|
---|
| 1089 | firstUnknownCommand = cmd
|
---|
| 1090 | break
|
---|
| 1091 | }
|
---|
| 1092 | }
|
---|
| 1093 |
|
---|
| 1094 | // run the default command, if defined
|
---|
| 1095 | if (command.hasDefaultCommand() && !skipDefaultCommand) {
|
---|
| 1096 | return command.runCommand(null, self, parsed)
|
---|
| 1097 | }
|
---|
| 1098 |
|
---|
| 1099 | // recommend a command if recommendCommands() has
|
---|
| 1100 | // been enabled, and no commands were found to execute
|
---|
| 1101 | if (recommendCommands && firstUnknownCommand && !skipRecommendation) {
|
---|
| 1102 | validation.recommendCommands(firstUnknownCommand, handlerKeys)
|
---|
| 1103 | }
|
---|
| 1104 | }
|
---|
| 1105 |
|
---|
| 1106 | // generate a completion script for adding to ~/.bashrc.
|
---|
| 1107 | if (completionCommand && ~argv._.indexOf(completionCommand) && !requestCompletions) {
|
---|
| 1108 | if (exitProcess) setBlocking(true)
|
---|
| 1109 | self.showCompletionScript()
|
---|
| 1110 | self.exit(0)
|
---|
| 1111 | }
|
---|
| 1112 | } else if (command.hasDefaultCommand() && !skipDefaultCommand) {
|
---|
| 1113 | return command.runCommand(null, self, parsed)
|
---|
| 1114 | }
|
---|
| 1115 |
|
---|
| 1116 | // we must run completions first, a user might
|
---|
| 1117 | // want to complete the --help or --version option.
|
---|
| 1118 | if (requestCompletions) {
|
---|
| 1119 | if (exitProcess) setBlocking(true)
|
---|
| 1120 |
|
---|
| 1121 | // we allow for asynchronous completions,
|
---|
| 1122 | // e.g., loading in a list of commands from an API.
|
---|
| 1123 | const completionArgs = args.slice(args.indexOf(`--${completion.completionKey}`) + 1)
|
---|
| 1124 | completion.getCompletion(completionArgs, (completions) => {
|
---|
| 1125 | ;(completions || []).forEach((completion) => {
|
---|
| 1126 | _logger.log(completion)
|
---|
| 1127 | })
|
---|
| 1128 |
|
---|
| 1129 | self.exit(0)
|
---|
| 1130 | })
|
---|
| 1131 | return argv
|
---|
| 1132 | }
|
---|
| 1133 |
|
---|
| 1134 | // Handle 'help' and 'version' options
|
---|
| 1135 | // if we haven't already output help!
|
---|
| 1136 | if (!hasOutput) {
|
---|
| 1137 | Object.keys(argv).forEach((key) => {
|
---|
| 1138 | if (key === helpOpt && argv[key]) {
|
---|
| 1139 | if (exitProcess) setBlocking(true)
|
---|
| 1140 |
|
---|
| 1141 | skipValidation = true
|
---|
| 1142 | self.showHelp('log')
|
---|
| 1143 | self.exit(0)
|
---|
| 1144 | } else if (key === versionOpt && argv[key]) {
|
---|
| 1145 | if (exitProcess) setBlocking(true)
|
---|
| 1146 |
|
---|
| 1147 | skipValidation = true
|
---|
| 1148 | usage.showVersion()
|
---|
| 1149 | self.exit(0)
|
---|
| 1150 | }
|
---|
| 1151 | })
|
---|
| 1152 | }
|
---|
| 1153 |
|
---|
| 1154 | // Check if any of the options to skip validation were provided
|
---|
| 1155 | if (!skipValidation && options.skipValidation.length > 0) {
|
---|
| 1156 | skipValidation = Object.keys(argv).some(key => options.skipValidation.indexOf(key) >= 0 && argv[key] === true)
|
---|
| 1157 | }
|
---|
| 1158 |
|
---|
| 1159 | // If the help or version options where used and exitProcess is false,
|
---|
| 1160 | // or if explicitly skipped, we won't run validations.
|
---|
| 1161 | if (!skipValidation) {
|
---|
| 1162 | if (parsed.error) throw new YError(parsed.error.message)
|
---|
| 1163 |
|
---|
| 1164 | // if we're executed via bash completion, don't
|
---|
| 1165 | // bother with validation.
|
---|
| 1166 | if (!requestCompletions) {
|
---|
| 1167 | self._runValidation(argv, aliases, {}, parsed.error)
|
---|
| 1168 | }
|
---|
| 1169 | }
|
---|
| 1170 | } catch (err) {
|
---|
| 1171 | if (err instanceof YError) usage.fail(err.message, err)
|
---|
| 1172 | else throw err
|
---|
| 1173 | }
|
---|
| 1174 |
|
---|
| 1175 | return argv
|
---|
| 1176 | }
|
---|
| 1177 |
|
---|
| 1178 | self._runValidation = function runValidation (argv, aliases, positionalMap, parseErrors) {
|
---|
| 1179 | if (parseErrors) throw new YError(parseErrors.message || parseErrors)
|
---|
| 1180 | validation.nonOptionCount(argv)
|
---|
| 1181 | validation.requiredArguments(argv)
|
---|
| 1182 | if (strict) validation.unknownArguments(argv, aliases, positionalMap)
|
---|
| 1183 | validation.customChecks(argv, aliases)
|
---|
| 1184 | validation.limitedChoices(argv)
|
---|
| 1185 | validation.implications(argv)
|
---|
| 1186 | validation.conflicting(argv)
|
---|
| 1187 | }
|
---|
| 1188 |
|
---|
| 1189 | function guessLocale () {
|
---|
| 1190 | if (!detectLocale) return
|
---|
| 1191 |
|
---|
| 1192 | try {
|
---|
| 1193 | const { env } = process
|
---|
| 1194 | const locale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE || 'en_US'
|
---|
| 1195 | self.locale(locale.replace(/[.:].*/, ''))
|
---|
| 1196 | } catch (err) {
|
---|
| 1197 | // if we explode looking up locale just noop
|
---|
| 1198 | // we'll keep using the default language 'en'.
|
---|
| 1199 | }
|
---|
| 1200 | }
|
---|
| 1201 |
|
---|
| 1202 | // an app should almost always have --version and --help,
|
---|
| 1203 | // if you *really* want to disable this use .help(false)/.version(false).
|
---|
| 1204 | self.help()
|
---|
| 1205 | self.version()
|
---|
| 1206 |
|
---|
| 1207 | return self
|
---|
| 1208 | }
|
---|
| 1209 |
|
---|
| 1210 | // rebase an absolute path to a relative one with respect to a base directory
|
---|
| 1211 | // exported for tests
|
---|
| 1212 | exports.rebase = rebase
|
---|
| 1213 | function rebase (base, dir) {
|
---|
| 1214 | return path.relative(base, dir)
|
---|
| 1215 | }
|
---|