Pathologic was missing because of a .git folder inside.
[yaffs-website] / node_modules / yargs-parser / index.js
1 var assign = require('lodash.assign')
2 var camelCase = require('camelcase')
3 var path = require('path')
4 var tokenizeArgString = require('./lib/tokenize-arg-string')
5 var util = require('util')
6
7 function parse (args, opts) {
8   if (!opts) opts = {}
9   // allow a string argument to be passed in rather
10   // than an argv array.
11   args = tokenizeArgString(args)
12   // aliases might have transitive relationships, normalize this.
13   var aliases = combineAliases(opts.alias || {})
14   var configuration = assign({}, {
15     'short-option-groups': true,
16     'camel-case-expansion': true,
17     'dot-notation': true,
18     'parse-numbers': true,
19     'boolean-negation': true
20   }, opts.configuration)
21   var defaults = opts.default || {}
22   var configObjects = opts.configObjects || []
23   var envPrefix = opts.envPrefix
24   var newAliases = {}
25   // allow a i18n handler to be passed in, default to a fake one (util.format).
26   var __ = opts.__ || function (str) {
27     return util.format.apply(util, Array.prototype.slice.call(arguments))
28   }
29   var error = null
30   var flags = {
31     aliases: {},
32     arrays: {},
33     bools: {},
34     strings: {},
35     numbers: {},
36     counts: {},
37     normalize: {},
38     configs: {},
39     defaulted: {},
40     nargs: {}
41   }
42
43   ;[].concat(opts.array).filter(Boolean).forEach(function (key) {
44     flags.arrays[key] = true
45   })
46
47   ;[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
48     flags.bools[key] = true
49   })
50
51   ;[].concat(opts.string).filter(Boolean).forEach(function (key) {
52     flags.strings[key] = true
53   })
54
55   ;[].concat(opts.number).filter(Boolean).forEach(function (key) {
56     flags.numbers[key] = true
57   })
58
59   ;[].concat(opts.count).filter(Boolean).forEach(function (key) {
60     flags.counts[key] = true
61   })
62
63   ;[].concat(opts.normalize).filter(Boolean).forEach(function (key) {
64     flags.normalize[key] = true
65   })
66
67   Object.keys(opts.narg || {}).forEach(function (k) {
68     flags.nargs[k] = opts.narg[k]
69   })
70
71   if (Array.isArray(opts.config) || typeof opts.config === 'string') {
72     ;[].concat(opts.config).filter(Boolean).forEach(function (key) {
73       flags.configs[key] = true
74     })
75   } else {
76     Object.keys(opts.config || {}).forEach(function (k) {
77       flags.configs[k] = opts.config[k]
78     })
79   }
80
81   // create a lookup table that takes into account all
82   // combinations of aliases: {f: ['foo'], foo: ['f']}
83   extendAliases(opts.key, aliases, opts.default, flags.arrays)
84
85   // apply default values to all aliases.
86   Object.keys(defaults).forEach(function (key) {
87     (flags.aliases[key] || []).forEach(function (alias) {
88       defaults[alias] = defaults[key]
89     })
90   })
91
92   var argv = { _: [] }
93
94   Object.keys(flags.bools).forEach(function (key) {
95     setArg(key, !(key in defaults) ? false : defaults[key])
96     setDefaulted(key)
97   })
98
99   var notFlags = []
100   if (args.indexOf('--') !== -1) {
101     notFlags = args.slice(args.indexOf('--') + 1)
102     args = args.slice(0, args.indexOf('--'))
103   }
104
105   for (var i = 0; i < args.length; i++) {
106     var arg = args[i]
107     var broken
108     var key
109     var letters
110     var m
111     var next
112     var value
113
114     // -- seperated by =
115     if (arg.match(/^--.+=/) || (
116       !configuration['short-option-groups'] && arg.match(/^-.+=/)
117     )) {
118       // Using [\s\S] instead of . because js doesn't support the
119       // 'dotall' regex modifier. See:
120       // http://stackoverflow.com/a/1068308/13216
121       m = arg.match(/^--?([^=]+)=([\s\S]*)$/)
122
123       // nargs format = '--f=monkey washing cat'
124       if (checkAllAliases(m[1], flags.nargs)) {
125         args.splice(i + 1, 0, m[2])
126         i = eatNargs(i, m[1], args)
127       // arrays format = '--f=a b c'
128       } else if (checkAllAliases(m[1], flags.arrays) && args.length > i + 1) {
129         args.splice(i + 1, 0, m[2])
130         i = eatArray(i, m[1], args)
131       } else {
132         setArg(m[1], m[2])
133       }
134     } else if (arg.match(/^--no-.+/) && configuration['boolean-negation']) {
135       key = arg.match(/^--no-(.+)/)[1]
136       setArg(key, false)
137
138     // -- seperated by space.
139     } else if (arg.match(/^--.+/) || (
140       !configuration['short-option-groups'] && arg.match(/^-.+/)
141     )) {
142       key = arg.match(/^--?(.+)/)[1]
143
144       // nargs format = '--foo a b c'
145       if (checkAllAliases(key, flags.nargs)) {
146         i = eatNargs(i, key, args)
147       // array format = '--foo a b c'
148       } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
149         i = eatArray(i, key, args)
150       } else {
151         next = args[i + 1]
152
153         if (next !== undefined && !next.match(/^-/) &&
154           !checkAllAliases(key, flags.bools) &&
155           !checkAllAliases(key, flags.counts)) {
156           setArg(key, next)
157           i++
158         } else if (/^(true|false)$/.test(next)) {
159           setArg(key, next)
160           i++
161         } else {
162           setArg(key, defaultForType(guessType(key, flags)))
163         }
164       }
165
166     // dot-notation flag seperated by '='.
167     } else if (arg.match(/^-.\..+=/)) {
168       m = arg.match(/^-([^=]+)=([\s\S]*)$/)
169       setArg(m[1], m[2])
170
171     // dot-notation flag seperated by space.
172     } else if (arg.match(/^-.\..+/)) {
173       next = args[i + 1]
174       key = arg.match(/^-(.\..+)/)[1]
175
176       if (next !== undefined && !next.match(/^-/) &&
177         !checkAllAliases(key, flags.bools) &&
178         !checkAllAliases(key, flags.counts)) {
179         setArg(key, next)
180         i++
181       } else {
182         setArg(key, defaultForType(guessType(key, flags)))
183       }
184     } else if (arg.match(/^-[^-]+/)) {
185       letters = arg.slice(1, -1).split('')
186       broken = false
187
188       for (var j = 0; j < letters.length; j++) {
189         next = arg.slice(j + 2)
190
191         if (letters[j + 1] && letters[j + 1] === '=') {
192           value = arg.slice(j + 3)
193           key = letters[j]
194
195           // nargs format = '-f=monkey washing cat'
196           if (checkAllAliases(key, flags.nargs)) {
197             args.splice(i + 1, 0, value)
198             i = eatNargs(i, key, args)
199           // array format = '-f=a b c'
200           } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
201             args.splice(i + 1, 0, value)
202             i = eatArray(i, key, args)
203           } else {
204             setArg(key, value)
205           }
206
207           broken = true
208           break
209         }
210
211         if (next === '-') {
212           setArg(letters[j], next)
213           continue
214         }
215
216         if (/[A-Za-z]/.test(letters[j]) &&
217           /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
218           setArg(letters[j], next)
219           broken = true
220           break
221         }
222
223         if (letters[j + 1] && letters[j + 1].match(/\W/)) {
224           setArg(letters[j], next)
225           broken = true
226           break
227         } else {
228           setArg(letters[j], defaultForType(guessType(letters[j], flags)))
229         }
230       }
231
232       key = arg.slice(-1)[0]
233
234       if (!broken && key !== '-') {
235         // nargs format = '-f a b c'
236         if (checkAllAliases(key, flags.nargs)) {
237           i = eatNargs(i, key, args)
238         // array format = '-f a b c'
239         } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
240           i = eatArray(i, key, args)
241         } else {
242           next = args[i + 1]
243
244           if (next !== undefined && !/^(-|--)[^-]/.test(next) &&
245             !checkAllAliases(key, flags.bools) &&
246             !checkAllAliases(key, flags.counts)) {
247             setArg(key, next)
248             i++
249           } else if (/^(true|false)$/.test(next)) {
250             setArg(key, next)
251             i++
252           } else {
253             setArg(key, defaultForType(guessType(key, flags)))
254           }
255         }
256       }
257     } else {
258       argv._.push(
259         flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
260       )
261     }
262   }
263
264   // order of precedence:
265   // 1. command line arg
266   // 2. value from config file
267   // 3. value from config objects
268   // 4. value from env var
269   // 5. configured default value
270   applyEnvVars(argv, true) // special case: check env vars that point to config file
271   setConfig(argv)
272   setConfigObjects()
273   applyEnvVars(argv, false)
274   applyDefaultsAndAliases(argv, flags.aliases, defaults)
275
276   // for any counts either not in args or without an explicit default, set to 0
277   Object.keys(flags.counts).forEach(function (key) {
278     if (!hasKey(argv, key.split('.'))) setArg(key, 0)
279   })
280
281   notFlags.forEach(function (key) {
282     argv._.push(key)
283   })
284
285   // how many arguments should we consume, based
286   // on the nargs option?
287   function eatNargs (i, key, args) {
288     var toEat = checkAllAliases(key, flags.nargs)
289
290     if (args.length - (i + 1) < toEat) error = Error(__('Not enough arguments following: %s', key))
291
292     for (var ii = i + 1; ii < (toEat + i + 1); ii++) {
293       setArg(key, args[ii])
294     }
295
296     return (i + toEat)
297   }
298
299   // if an option is an array, eat all non-hyphenated arguments
300   // following it... YUM!
301   // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
302   function eatArray (i, key, args) {
303     var start = i + 1
304     for (var ii = i + 1; ii < args.length; ii++) {
305       if (/^-/.test(args[ii])) {
306         if (ii === start) {
307           setArg(key, defaultForType('array'))
308         }
309         break
310       }
311       i = ii
312       setArg(key, args[ii])
313     }
314
315     return i
316   }
317
318   function setArg (key, val) {
319     unsetDefaulted(key)
320
321     // handle parsing boolean arguments --foo=true --bar false.
322     if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) {
323       if (typeof val === 'string') val = val === 'true'
324     }
325
326     if (/-/.test(key) && !(flags.aliases[key] && flags.aliases[key].length) && configuration['camel-case-expansion']) {
327       var c = camelCase(key)
328       flags.aliases[key] = [c]
329       newAliases[c] = true
330     }
331
332     var value = val
333     if (!checkAllAliases(key, flags.strings)) {
334       if (isNumber(val)) value = Number(val)
335       if (!isUndefined(val) && !isNumber(val) && checkAllAliases(key, flags.numbers)) value = NaN
336     }
337
338     // increment a count given as arg (either no value or value parsed as boolean)
339     if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) {
340       value = increment
341     }
342
343     // Set normalized value when key is in 'normalize' and in 'arrays'
344     if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) {
345       value = path.normalize(val)
346     }
347
348     var splitKey = key.split('.')
349     setKey(argv, splitKey, value)
350
351     // handle populating aliases of the full key
352     if (flags.aliases[key]) {
353       flags.aliases[key].forEach(function (x) {
354         x = x.split('.')
355         setKey(argv, x, value)
356       })
357     }
358
359     // handle populating aliases of the first element of the dot-notation key
360     if (splitKey.length > 1 && configuration['dot-notation']) {
361       ;(flags.aliases[splitKey[0]] || []).forEach(function (x) {
362         x = x.split('.')
363
364         // expand alias with nested objects in key
365         var a = [].concat(splitKey)
366         a.shift() // nuke the old key.
367         x = x.concat(a)
368
369         setKey(argv, x, value)
370       })
371     }
372
373     // Set normalize getter and setter when key is in 'normalize' but isn't an array
374     if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) {
375       var keys = [key].concat(flags.aliases[key] || [])
376       keys.forEach(function (key) {
377         argv.__defineSetter__(key, function (v) {
378           val = path.normalize(v)
379         })
380
381         argv.__defineGetter__(key, function () {
382           return typeof val === 'string' ? path.normalize(val) : val
383         })
384       })
385     }
386   }
387
388   // set args from config.json file, this should be
389   // applied last so that defaults can be applied.
390   function setConfig (argv) {
391     var configLookup = {}
392
393     // expand defaults/aliases, in-case any happen to reference
394     // the config.json file.
395     applyDefaultsAndAliases(configLookup, flags.aliases, defaults)
396
397     Object.keys(flags.configs).forEach(function (configKey) {
398       var configPath = argv[configKey] || configLookup[configKey]
399       if (configPath) {
400         try {
401           var config = null
402           var resolvedConfigPath = path.resolve(process.cwd(), configPath)
403
404           if (typeof flags.configs[configKey] === 'function') {
405             try {
406               config = flags.configs[configKey](resolvedConfigPath)
407             } catch (e) {
408               config = e
409             }
410             if (config instanceof Error) {
411               error = config
412               return
413             }
414           } else {
415             config = require(resolvedConfigPath)
416           }
417
418           setConfigObject(config)
419         } catch (ex) {
420           if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath))
421         }
422       }
423     })
424   }
425
426   // set args from config object.
427   // it recursively checks nested objects.
428   function setConfigObject (config, prev) {
429     Object.keys(config).forEach(function (key) {
430       var value = config[key]
431       var fullKey = prev ? prev + '.' + key : key
432
433       if (Object.prototype.toString.call(value) === '[object Object]') {
434         // if the value is an object but not an array, check nested object
435         setConfigObject(value, fullKey)
436       } else {
437         // setting arguments via CLI takes precedence over
438         // values within the config file.
439         if (!hasKey(argv, fullKey.split('.')) || (flags.defaulted[fullKey])) {
440           setArg(fullKey, value)
441         }
442       }
443     })
444   }
445
446   // set all config objects passed in opts
447   function setConfigObjects () {
448     if (typeof configObjects === 'undefined') return
449     configObjects.forEach(function (configObject) {
450       setConfigObject(configObject)
451     })
452   }
453
454   function applyEnvVars (argv, configOnly) {
455     if (typeof envPrefix === 'undefined') return
456
457     var prefix = typeof envPrefix === 'string' ? envPrefix : ''
458     Object.keys(process.env).forEach(function (envVar) {
459       if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) {
460         // get array of nested keys and convert them to camel case
461         var keys = envVar.split('__').map(function (key, i) {
462           if (i === 0) {
463             key = key.substring(prefix.length)
464           }
465           return camelCase(key)
466         })
467
468         if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && (!hasKey(argv, keys) || flags.defaulted[keys.join('.')])) {
469           setArg(keys.join('.'), process.env[envVar])
470         }
471       }
472     })
473   }
474
475   function applyDefaultsAndAliases (obj, aliases, defaults) {
476     Object.keys(defaults).forEach(function (key) {
477       if (!hasKey(obj, key.split('.'))) {
478         setKey(obj, key.split('.'), defaults[key])
479
480         ;(aliases[key] || []).forEach(function (x) {
481           if (hasKey(obj, x.split('.'))) return
482           setKey(obj, x.split('.'), defaults[key])
483         })
484       }
485     })
486   }
487
488   function hasKey (obj, keys) {
489     var o = obj
490
491     if (!configuration['dot-notation']) keys = [keys.join('.')]
492
493     keys.slice(0, -1).forEach(function (key) {
494       o = (o[key] || {})
495     })
496
497     var key = keys[keys.length - 1]
498
499     if (typeof o !== 'object') return false
500     else return key in o
501   }
502
503   function setKey (obj, keys, value) {
504     var o = obj
505
506     if (!configuration['dot-notation']) keys = [keys.join('.')]
507
508     keys.slice(0, -1).forEach(function (key) {
509       if (o[key] === undefined) o[key] = {}
510       o = o[key]
511     })
512
513     var key = keys[keys.length - 1]
514
515     if (value === increment) {
516       o[key] = increment(o[key])
517     } else if (o[key] === undefined && checkAllAliases(key, flags.arrays)) {
518       o[key] = Array.isArray(value) ? value : [value]
519     } else if (o[key] === undefined || checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) {
520       o[key] = value
521     } else if (Array.isArray(o[key])) {
522       o[key].push(value)
523     } else {
524       o[key] = [ o[key], value ]
525     }
526   }
527
528   // extend the aliases list with inferred aliases.
529   function extendAliases () {
530     Array.prototype.slice.call(arguments).forEach(function (obj) {
531       Object.keys(obj || {}).forEach(function (key) {
532         // short-circuit if we've already added a key
533         // to the aliases array, for example it might
534         // exist in both 'opts.default' and 'opts.key'.
535         if (flags.aliases[key]) return
536
537         flags.aliases[key] = [].concat(aliases[key] || [])
538         // For "--option-name", also set argv.optionName
539         flags.aliases[key].concat(key).forEach(function (x) {
540           if (/-/.test(x) && configuration['camel-case-expansion']) {
541             var c = camelCase(x)
542             flags.aliases[key].push(c)
543             newAliases[c] = true
544           }
545         })
546         flags.aliases[key].forEach(function (x) {
547           flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) {
548             return x !== y
549           }))
550         })
551       })
552     })
553   }
554
555   // check if a flag is set for any of a key's aliases.
556   function checkAllAliases (key, flag) {
557     var isSet = false
558     var toCheck = [].concat(flags.aliases[key] || [], key)
559
560     toCheck.forEach(function (key) {
561       if (flag[key]) isSet = flag[key]
562     })
563
564     return isSet
565   }
566
567   function setDefaulted (key) {
568     [].concat(flags.aliases[key] || [], key).forEach(function (k) {
569       flags.defaulted[k] = true
570     })
571   }
572
573   function unsetDefaulted (key) {
574     [].concat(flags.aliases[key] || [], key).forEach(function (k) {
575       delete flags.defaulted[k]
576     })
577   }
578
579   // return a default value, given the type of a flag.,
580   // e.g., key of type 'string' will default to '', rather than 'true'.
581   function defaultForType (type) {
582     var def = {
583       boolean: true,
584       string: '',
585       number: undefined,
586       array: []
587     }
588
589     return def[type]
590   }
591
592   // given a flag, enforce a default type.
593   function guessType (key, flags) {
594     var type = 'boolean'
595
596     if (flags.strings && flags.strings[key]) type = 'string'
597     else if (flags.numbers && flags.numbers[key]) type = 'number'
598     else if (flags.arrays && flags.arrays[key]) type = 'array'
599
600     return type
601   }
602
603   function isNumber (x) {
604     if (!configuration['parse-numbers']) return false
605     if (typeof x === 'number') return true
606     if (/^0x[0-9a-f]+$/i.test(x)) return true
607     return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x)
608   }
609
610   function isUndefined (num) {
611     return num === undefined
612   }
613
614   return {
615     argv: argv,
616     error: error,
617     aliases: flags.aliases,
618     newAliases: newAliases,
619     configuration: configuration
620   }
621 }
622
623 // if any aliases reference each other, we should
624 // merge them together.
625 function combineAliases (aliases) {
626   var aliasArrays = []
627   var change = true
628   var combined = {}
629
630   // turn alias lookup hash {key: ['alias1', 'alias2']} into
631   // a simple array ['key', 'alias1', 'alias2']
632   Object.keys(aliases).forEach(function (key) {
633     aliasArrays.push(
634       [].concat(aliases[key], key)
635     )
636   })
637
638   // combine arrays until zero changes are
639   // made in an iteration.
640   while (change) {
641     change = false
642     for (var i = 0; i < aliasArrays.length; i++) {
643       for (var ii = i + 1; ii < aliasArrays.length; ii++) {
644         var intersect = aliasArrays[i].filter(function (v) {
645           return aliasArrays[ii].indexOf(v) !== -1
646         })
647
648         if (intersect.length) {
649           aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii])
650           aliasArrays.splice(ii, 1)
651           change = true
652           break
653         }
654       }
655     }
656   }
657
658   // map arrays back to the hash-lookup (de-dupe while
659   // we're at it).
660   aliasArrays.forEach(function (aliasArray) {
661     aliasArray = aliasArray.filter(function (v, i, self) {
662       return self.indexOf(v) === i
663     })
664     combined[aliasArray.pop()] = aliasArray
665   })
666
667   return combined
668 }
669
670 // this function should only be called when a count is given as an arg
671 // it is NOT called to set a default value
672 // thus we can start the count at 1 instead of 0
673 function increment (orig) {
674   return orig !== undefined ? orig + 1 : 1
675 }
676
677 function Parser (args, opts) {
678   var result = parse(args.slice(), opts)
679
680   return result.argv
681 }
682
683 // parse arguments and return detailed
684 // meta information, aliases, etc.
685 Parser.detailed = function (args, opts) {
686   return parse(args.slice(), opts)
687 }
688
689 module.exports = Parser