Initial commit
[yaffs-website] / node_modules / yargs / yargs.js
1 const assert = require('assert')
2 const assign = require('lodash.assign')
3 const Command = require('./lib/command')
4 const Completion = require('./lib/completion')
5 const Parser = require('yargs-parser')
6 const path = require('path')
7 const Usage = require('./lib/usage')
8 const Validation = require('./lib/validation')
9 const Y18n = require('y18n')
10 const requireMainFilename = require('require-main-filename')
11 const objFilter = require('./lib/obj-filter')
12 const setBlocking = require('set-blocking')
13
14 var exports = module.exports = Yargs
15 function Yargs (processArgs, cwd, parentRequire) {
16   processArgs = processArgs || [] // handle calling yargs().
17
18   const self = {}
19   var command = null
20   var completion = null
21   var groups = {}
22   var preservedGroups = {}
23   var usage = null
24   var validation = null
25
26   const y18n = Y18n({
27     directory: path.resolve(__dirname, './locales'),
28     updateFiles: false
29   })
30
31   if (!cwd) cwd = process.cwd()
32
33   self.$0 = process.argv
34     .slice(0, 2)
35     .map(function (x, i) {
36       // ignore the node bin, specify this in your
37       // bin file with #!/usr/bin/env node
38       if (i === 0 && /\b(node|iojs)(\.exe)?$/.test(x)) return
39       var b = rebase(cwd, x)
40       return x.match(/^(\/|([a-zA-Z]:)?\\)/) && b.length < x.length ? b : x
41     })
42     .join(' ').trim()
43
44   if (process.env._ !== undefined && process.argv[1] === process.env._) {
45     self.$0 = process.env._.replace(
46       path.dirname(process.execPath) + '/', ''
47     )
48   }
49
50   // use context object to keep track of resets, subcommand execution, etc
51   // submodules should modify and check the state of context as necessary
52   const context = { resets: -1, commands: [], files: [] }
53   self.getContext = function () {
54     return context
55   }
56
57   // puts yargs back into an initial state. any keys
58   // that have been set to "global" will not be reset
59   // by this action.
60   var options
61   self.resetOptions = self.reset = function (aliases) {
62     context.resets++
63     aliases = aliases || {}
64     options = options || {}
65     // put yargs back into an initial state, this
66     // logic is used to build a nested command
67     // hierarchy.
68     var tmpOptions = {}
69     tmpOptions.global = options.global ? options.global : []
70
71     // if a key has been set as a global, we
72     // do not want to reset it or its aliases.
73     var globalLookup = {}
74     tmpOptions.global.forEach(function (g) {
75       globalLookup[g] = true
76       ;(aliases[g] || []).forEach(function (a) {
77         globalLookup[a] = true
78       })
79     })
80
81     // preserve groups containing global keys
82     preservedGroups = Object.keys(groups).reduce(function (acc, groupName) {
83       var keys = groups[groupName].filter(function (key) {
84         return key in globalLookup
85       })
86       if (keys.length > 0) {
87         acc[groupName] = keys
88       }
89       return acc
90     }, {})
91     // groups can now be reset
92     groups = {}
93
94     var arrayOptions = [
95       'array', 'boolean', 'string', 'requiresArg', 'skipValidation',
96       'count', 'normalize', 'number'
97     ]
98
99     var objectOptions = [
100       'narg', 'key', 'alias', 'default', 'defaultDescription',
101       'config', 'choices', 'demanded'
102     ]
103
104     arrayOptions.forEach(function (k) {
105       tmpOptions[k] = (options[k] || []).filter(function (k) {
106         return globalLookup[k]
107       })
108     })
109
110     objectOptions.forEach(function (k) {
111       tmpOptions[k] = objFilter(options[k], function (k, v) {
112         return globalLookup[k]
113       })
114     })
115
116     tmpOptions.envPrefix = undefined
117     options = tmpOptions
118
119     // if this is the first time being executed, create
120     // instances of all our helpers -- otherwise just reset.
121     usage = usage ? usage.reset(globalLookup) : Usage(self, y18n)
122     validation = validation ? validation.reset(globalLookup) : Validation(self, usage, y18n)
123     command = command ? command.reset() : Command(self, usage, validation)
124     if (!completion) completion = Completion(self, usage, command)
125
126     exitProcess = true
127     strict = false
128     completionCommand = null
129     self.parsed = false
130
131     return self
132   }
133   self.resetOptions()
134
135   self.boolean = function (bools) {
136     options.boolean.push.apply(options.boolean, [].concat(bools))
137     return self
138   }
139
140   self.array = function (arrays) {
141     options.array.push.apply(options.array, [].concat(arrays))
142     return self
143   }
144
145   self.nargs = function (key, n) {
146     if (typeof key === 'object') {
147       Object.keys(key).forEach(function (k) {
148         self.nargs(k, key[k])
149       })
150     } else {
151       options.narg[key] = n
152     }
153     return self
154   }
155
156   self.number = function (numbers) {
157     options.number.push.apply(options.number, [].concat(numbers))
158     return self
159   }
160
161   self.choices = function (key, values) {
162     if (typeof key === 'object') {
163       Object.keys(key).forEach(function (k) {
164         self.choices(k, key[k])
165       })
166     } else {
167       options.choices[key] = (options.choices[key] || []).concat(values)
168     }
169     return self
170   }
171
172   self.normalize = function (strings) {
173     options.normalize.push.apply(options.normalize, [].concat(strings))
174     return self
175   }
176
177   self.config = function (key, msg, parseFn) {
178     // allow to pass a configuration object
179     if (typeof key === 'object') {
180       options.configObjects = (options.configObjects || []).concat(key)
181       return self
182     }
183
184     // allow to provide a parsing function
185     if (typeof msg === 'function') {
186       parseFn = msg
187       msg = null
188     }
189
190     key = key || 'config'
191     self.describe(key, msg || usage.deferY18nLookup('Path to JSON config file'))
192     ;(Array.isArray(key) ? key : [key]).forEach(function (k) {
193       options.config[k] = parseFn || true
194     })
195     return self
196   }
197
198   self.example = function (cmd, description) {
199     usage.example(cmd, description)
200     return self
201   }
202
203   self.command = function (cmd, description, builder, handler) {
204     command.addHandler(cmd, description, builder, handler)
205     return self
206   }
207
208   self.commandDir = function (dir, opts) {
209     const req = parentRequire || require
210     command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
211     return self
212   }
213
214   self.string = function (strings) {
215     options.string.push.apply(options.string, [].concat(strings))
216     return self
217   }
218
219   // The 'defaults' alias is deprecated. It will be removed in the next major version.
220   self.default = self.defaults = function (key, value, defaultDescription) {
221     if (typeof key === 'object') {
222       Object.keys(key).forEach(function (k) {
223         self.default(k, key[k])
224       })
225     } else {
226       if (defaultDescription) options.defaultDescription[key] = defaultDescription
227       if (typeof value === 'function') {
228         if (!options.defaultDescription[key]) options.defaultDescription[key] = usage.functionDescription(value)
229         value = value.call()
230       }
231       options.default[key] = value
232     }
233     return self
234   }
235
236   self.alias = function (x, y) {
237     if (typeof x === 'object') {
238       Object.keys(x).forEach(function (key) {
239         self.alias(key, x[key])
240       })
241     } else {
242       options.alias[x] = (options.alias[x] || []).concat(y)
243     }
244     return self
245   }
246
247   self.count = function (counts) {
248     options.count.push.apply(options.count, [].concat(counts))
249     return self
250   }
251
252   self.demand = self.required = self.require = function (keys, max, msg) {
253     // you can optionally provide a 'max' key,
254     // which will raise an exception if too many '_'
255     // options are provided.
256
257     if (Array.isArray(max)) {
258       max.forEach(function (key) {
259         self.demand(key, msg)
260       })
261       max = Infinity
262     } else if (typeof max !== 'number') {
263       msg = max
264       max = Infinity
265     }
266
267     if (typeof keys === 'number') {
268       if (!options.demanded._) options.demanded._ = { count: 0, msg: null, max: max }
269       options.demanded._.count = keys
270       options.demanded._.msg = msg
271     } else if (Array.isArray(keys)) {
272       keys.forEach(function (key) {
273         self.demand(key, msg)
274       })
275     } else {
276       if (typeof msg === 'string') {
277         options.demanded[keys] = { msg: msg }
278       } else if (msg === true || typeof msg === 'undefined') {
279         options.demanded[keys] = { msg: undefined }
280       }
281     }
282
283     return self
284   }
285
286   self.getDemanded = function () {
287     return options.demanded
288   }
289
290   self.requiresArg = function (requiresArgs) {
291     options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs))
292     return self
293   }
294
295   self.skipValidation = function (skipValidations) {
296     options.skipValidation.push.apply(options.skipValidation, [].concat(skipValidations))
297     return self
298   }
299
300   self.implies = function (key, value) {
301     validation.implies(key, value)
302     return self
303   }
304
305   self.usage = function (msg, opts) {
306     if (!opts && typeof msg === 'object') {
307       opts = msg
308       msg = null
309     }
310
311     usage.usage(msg)
312
313     if (opts) self.options(opts)
314
315     return self
316   }
317
318   self.epilogue = self.epilog = function (msg) {
319     usage.epilog(msg)
320     return self
321   }
322
323   self.fail = function (f) {
324     usage.failFn(f)
325     return self
326   }
327
328   self.check = function (f) {
329     validation.check(f)
330     return self
331   }
332
333   self.describe = function (key, desc) {
334     options.key[key] = true
335     usage.describe(key, desc)
336     return self
337   }
338
339   self.global = function (globals) {
340     options.global.push.apply(options.global, [].concat(globals))
341     return self
342   }
343
344   self.pkgConf = function (key, path) {
345     var conf = null
346
347     var obj = pkgUp(path)
348
349     // If an object exists in the key, add it to options.configObjects
350     if (obj[key] && typeof obj[key] === 'object') {
351       conf = obj[key]
352       options.configObjects = (options.configObjects || []).concat(conf)
353     }
354
355     return self
356   }
357
358   var pkgs = {}
359   function pkgUp (path) {
360     var npath = path || '*'
361     if (pkgs[npath]) return pkgs[npath]
362     const readPkgUp = require('read-pkg-up')
363
364     var obj = {}
365     try {
366       obj = readPkgUp.sync({
367         cwd: path || requireMainFilename(parentRequire || require)
368       })
369     } catch (noop) {}
370
371     pkgs[npath] = obj.pkg || {}
372     return pkgs[npath]
373   }
374
375   self.parse = function (args, shortCircuit) {
376     if (!shortCircuit) processArgs = args
377     return parseArgs(args, shortCircuit)
378   }
379
380   self.option = self.options = function (key, opt) {
381     if (typeof key === 'object') {
382       Object.keys(key).forEach(function (k) {
383         self.options(k, key[k])
384       })
385     } else {
386       assert(typeof opt === 'object', 'second argument to option must be an object')
387
388       options.key[key] = true // track manually set keys.
389
390       if (opt.alias) self.alias(key, opt.alias)
391
392       var demand = opt.demand || opt.required || opt.require
393
394       if (demand) {
395         self.demand(key, demand)
396       } if ('config' in opt) {
397         self.config(key, opt.configParser)
398       } if ('default' in opt) {
399         self.default(key, opt.default)
400       } if ('nargs' in opt) {
401         self.nargs(key, opt.nargs)
402       } if ('normalize' in opt) {
403         self.normalize(key)
404       } if ('choices' in opt) {
405         self.choices(key, opt.choices)
406       } if ('group' in opt) {
407         self.group(key, opt.group)
408       } if (opt.global) {
409         self.global(key)
410       } if (opt.boolean || opt.type === 'boolean') {
411         self.boolean(key)
412         if (opt.alias) self.boolean(opt.alias)
413       } if (opt.array || opt.type === 'array') {
414         self.array(key)
415         if (opt.alias) self.array(opt.alias)
416       } if (opt.number || opt.type === 'number') {
417         self.number(key)
418         if (opt.alias) self.number(opt.alias)
419       } if (opt.string || opt.type === 'string') {
420         self.string(key)
421         if (opt.alias) self.string(opt.alias)
422       } if (opt.count || opt.type === 'count') {
423         self.count(key)
424       } if (opt.defaultDescription) {
425         options.defaultDescription[key] = opt.defaultDescription
426       } if (opt.skipValidation) {
427         self.skipValidation(key)
428       }
429
430       var desc = opt.describe || opt.description || opt.desc
431       if (desc) {
432         self.describe(key, desc)
433       }
434
435       if (opt.requiresArg) {
436         self.requiresArg(key)
437       }
438     }
439
440     return self
441   }
442   self.getOptions = function () {
443     return options
444   }
445
446   self.group = function (opts, groupName) {
447     var existing = preservedGroups[groupName] || groups[groupName]
448     if (preservedGroups[groupName]) {
449       // the preserved group will be moved to the set of explicitly declared
450       // groups
451       delete preservedGroups[groupName]
452     }
453
454     var seen = {}
455     groups[groupName] = (existing || []).concat(opts).filter(function (key) {
456       if (seen[key]) return false
457       return (seen[key] = true)
458     })
459     return self
460   }
461   self.getGroups = function () {
462     // combine explicit and preserved groups. explicit groups should be first
463     return assign({}, groups, preservedGroups)
464   }
465
466   // as long as options.envPrefix is not undefined,
467   // parser will apply env vars matching prefix to argv
468   self.env = function (prefix) {
469     if (prefix === false) options.envPrefix = undefined
470     else options.envPrefix = prefix || ''
471     return self
472   }
473
474   self.wrap = function (cols) {
475     usage.wrap(cols)
476     return self
477   }
478
479   var strict = false
480   self.strict = function () {
481     strict = true
482     return self
483   }
484   self.getStrict = function () {
485     return strict
486   }
487
488   self.showHelp = function (level) {
489     if (!self.parsed) parseArgs(processArgs) // run parser, if it has not already been executed.
490     usage.showHelp(level)
491     return self
492   }
493
494   var versionOpt = null
495   self.version = function (opt, msg, ver) {
496     if (arguments.length === 0) {
497       ver = guessVersion()
498       opt = 'version'
499     } else if (arguments.length === 1) {
500       ver = opt
501       opt = 'version'
502     } else if (arguments.length === 2) {
503       ver = msg
504     }
505
506     versionOpt = opt
507     msg = msg || usage.deferY18nLookup('Show version number')
508
509     usage.version(ver || undefined)
510     self.boolean(versionOpt)
511     self.global(versionOpt)
512     self.describe(versionOpt, msg)
513     return self
514   }
515
516   function guessVersion () {
517     var obj = pkgUp()
518
519     return obj.version || 'unknown'
520   }
521
522   var helpOpt = null
523   self.addHelpOpt = self.help = function (opt, msg) {
524     opt = opt || 'help'
525     helpOpt = opt
526     self.boolean(opt)
527     self.global(opt)
528     self.describe(opt, msg || usage.deferY18nLookup('Show help'))
529     return self
530   }
531
532   self.showHelpOnFail = function (enabled, message) {
533     usage.showHelpOnFail(enabled, message)
534     return self
535   }
536
537   var exitProcess = true
538   self.exitProcess = function (enabled) {
539     if (typeof enabled !== 'boolean') {
540       enabled = true
541     }
542     exitProcess = enabled
543     return self
544   }
545   self.getExitProcess = function () {
546     return exitProcess
547   }
548
549   var completionCommand = null
550   self.completion = function (cmd, desc, fn) {
551     // a function to execute when generating
552     // completions can be provided as the second
553     // or third argument to completion.
554     if (typeof desc === 'function') {
555       fn = desc
556       desc = null
557     }
558
559     // register the completion command.
560     completionCommand = cmd || 'completion'
561     if (!desc && desc !== false) {
562       desc = 'generate bash completion script'
563     }
564     self.command(completionCommand, desc)
565
566     // a function can be provided
567     if (fn) completion.registerFunction(fn)
568
569     return self
570   }
571
572   self.showCompletionScript = function ($0) {
573     $0 = $0 || self.$0
574     console.log(completion.generateCompletionScript($0))
575     return self
576   }
577
578   self.getCompletion = function (args, done) {
579     completion.getCompletion(args, done)
580   }
581
582   self.locale = function (locale) {
583     if (arguments.length === 0) {
584       guessLocale()
585       return y18n.getLocale()
586     }
587     detectLocale = false
588     y18n.setLocale(locale)
589     return self
590   }
591
592   self.updateStrings = self.updateLocale = function (obj) {
593     detectLocale = false
594     y18n.updateLocale(obj)
595     return self
596   }
597
598   var detectLocale = true
599   self.detectLocale = function (detect) {
600     detectLocale = detect
601     return self
602   }
603   self.getDetectLocale = function () {
604     return detectLocale
605   }
606
607   self.getUsageInstance = function () {
608     return usage
609   }
610
611   self.getValidationInstance = function () {
612     return validation
613   }
614
615   self.getCommandInstance = function () {
616     return command
617   }
618
619   self.terminalWidth = function () {
620     return require('window-size').width
621   }
622
623   Object.defineProperty(self, 'argv', {
624     get: function () {
625       var args = null
626
627       try {
628         args = parseArgs(processArgs)
629       } catch (err) {
630         usage.fail(err.message, err)
631       }
632
633       return args
634     },
635     enumerable: true
636   })
637
638   function parseArgs (args, shortCircuit) {
639     options.__ = y18n.__
640     options.configuration = pkgUp(cwd)['yargs'] || {}
641     const parsed = Parser.detailed(args, options)
642     const argv = parsed.argv
643     var aliases = parsed.aliases
644
645     argv.$0 = self.$0
646     self.parsed = parsed
647
648     guessLocale() // guess locale lazily, so that it can be turned off in chain.
649
650     // while building up the argv object, there
651     // are two passes through the parser. If completion
652     // is being performed short-circuit on the first pass.
653     if (shortCircuit) {
654       return argv
655     }
656
657     // if there's a handler associated with a
658     // command defer processing to it.
659     var handlerKeys = command.getCommands()
660     for (var i = 0, cmd; (cmd = argv._[i]) !== undefined; i++) {
661       if (~handlerKeys.indexOf(cmd) && cmd !== completionCommand) {
662         setPlaceholderKeys(argv)
663         return command.runCommand(cmd, self, parsed)
664       }
665     }
666
667     // generate a completion script for adding to ~/.bashrc.
668     if (completionCommand && ~argv._.indexOf(completionCommand) && !argv[completion.completionKey]) {
669       if (exitProcess) setBlocking(true)
670       self.showCompletionScript()
671       if (exitProcess) {
672         process.exit(0)
673       }
674     }
675
676     // we must run completions first, a user might
677     // want to complete the --help or --version option.
678     if (completion.completionKey in argv) {
679       if (exitProcess) setBlocking(true)
680
681       // we allow for asynchronous completions,
682       // e.g., loading in a list of commands from an API.
683       var completionArgs = args.slice(args.indexOf('--' + completion.completionKey) + 1)
684       completion.getCompletion(completionArgs, function (completions) {
685         ;(completions || []).forEach(function (completion) {
686           console.log(completion)
687         })
688
689         if (exitProcess) {
690           process.exit(0)
691         }
692       })
693       return
694     }
695
696     var skipValidation = false
697
698     // Handle 'help' and 'version' options
699     Object.keys(argv).forEach(function (key) {
700       if (key === helpOpt && argv[key]) {
701         if (exitProcess) setBlocking(true)
702
703         skipValidation = true
704         self.showHelp('log')
705         if (exitProcess) {
706           process.exit(0)
707         }
708       } else if (key === versionOpt && argv[key]) {
709         if (exitProcess) setBlocking(true)
710
711         skipValidation = true
712         usage.showVersion()
713         if (exitProcess) {
714           process.exit(0)
715         }
716       }
717     })
718
719     // Check if any of the options to skip validation were provided
720     if (!skipValidation && options.skipValidation.length > 0) {
721       skipValidation = Object.keys(argv).some(function (key) {
722         return options.skipValidation.indexOf(key) >= 0
723       })
724     }
725
726     // If the help or version options where used and exitProcess is false,
727     // or if explicitly skipped, we won't run validations
728     if (!skipValidation) {
729       if (parsed.error) throw parsed.error
730
731       // if we're executed via bash completion, don't
732       // bother with validation.
733       if (!argv[completion.completionKey]) {
734         validation.nonOptionCount(argv)
735         validation.missingArgumentValue(argv)
736         validation.requiredArguments(argv)
737         if (strict) validation.unknownArguments(argv, aliases)
738         validation.customChecks(argv, aliases)
739         validation.limitedChoices(argv)
740         validation.implications(argv)
741       }
742     }
743
744     setPlaceholderKeys(argv)
745
746     return argv
747   }
748
749   function guessLocale () {
750     if (!detectLocale) return
751
752     try {
753       const osLocale = require('os-locale')
754       self.locale(osLocale.sync({ spawn: false }))
755     } catch (err) {
756       // if we explode looking up locale just noop
757       // we'll keep using the default language 'en'.
758     }
759   }
760
761   function setPlaceholderKeys (argv) {
762     Object.keys(options.key).forEach(function (key) {
763       // don't set placeholder keys for dot
764       // notation options 'foo.bar'.
765       if (~key.indexOf('.')) return
766       if (typeof argv[key] === 'undefined') argv[key] = undefined
767     })
768   }
769
770   return self
771 }
772
773 // rebase an absolute path to a relative one with respect to a base directory
774 // exported for tests
775 exports.rebase = rebase
776 function rebase (base, dir) {
777   return path.relative(base, dir)
778 }