UNPKG

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