[SignalingServer] Optimize dependent modules
[platform/framework/web/wrtjs.git] / signaling_server / service / node_modules / yargs / yargs.js
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 }