UNPKG

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