867 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			867 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | var camelCase = require('camelcase') | ||
|  | var decamelize = require('decamelize') | ||
|  | var path = require('path') | ||
|  | var tokenizeArgString = require('./lib/tokenize-arg-string') | ||
|  | var util = require('util') | ||
|  | 
 | ||
|  | function parse (args, opts) { | ||
|  |   if (!opts) opts = {} | ||
|  |   // allow a string argument to be passed in rather
 | ||
|  |   // than an argv array.
 | ||
|  |   args = tokenizeArgString(args) | ||
|  |   // aliases might have transitive relationships, normalize this.
 | ||
|  |   var aliases = combineAliases(opts.alias || {}) | ||
|  |   var configuration = assign({ | ||
|  |     'short-option-groups': true, | ||
|  |     'camel-case-expansion': true, | ||
|  |     'dot-notation': true, | ||
|  |     'parse-numbers': true, | ||
|  |     'boolean-negation': true, | ||
|  |     'negation-prefix': 'no-', | ||
|  |     'duplicate-arguments-array': true, | ||
|  |     'flatten-duplicate-arrays': true, | ||
|  |     'populate--': false, | ||
|  |     'combine-arrays': false, | ||
|  |     'set-placeholder-key': false, | ||
|  |     'halt-at-non-option': false | ||
|  |   }, opts.configuration) | ||
|  |   var defaults = opts.default || {} | ||
|  |   var configObjects = opts.configObjects || [] | ||
|  |   var envPrefix = opts.envPrefix | ||
|  |   var notFlagsOption = configuration['populate--'] | ||
|  |   var notFlagsArgv = notFlagsOption ? '--' : '_' | ||
|  |   var newAliases = {} | ||
|  |   // allow a i18n handler to be passed in, default to a fake one (util.format).
 | ||
|  |   var __ = opts.__ || function (str) { | ||
|  |     return util.format.apply(util, Array.prototype.slice.call(arguments)) | ||
|  |   } | ||
|  |   var error = null | ||
|  |   var flags = { | ||
|  |     aliases: {}, | ||
|  |     arrays: {}, | ||
|  |     bools: {}, | ||
|  |     strings: {}, | ||
|  |     numbers: {}, | ||
|  |     counts: {}, | ||
|  |     normalize: {}, | ||
|  |     configs: {}, | ||
|  |     defaulted: {}, | ||
|  |     nargs: {}, | ||
|  |     coercions: {}, | ||
|  |     keys: [] | ||
|  |   } | ||
|  |   var negative = /^-[0-9]+(\.[0-9]+)?/ | ||
|  |   var negatedBoolean = new RegExp('^--' + configuration['negation-prefix'] + '(.+)') | ||
|  | 
 | ||
|  |   ;[].concat(opts.array).filter(Boolean).forEach(function (opt) { | ||
|  |     var key = opt.key || opt | ||
|  | 
 | ||
|  |     // assign to flags[bools|strings|numbers]
 | ||
|  |     const assignment = Object.keys(opt).map(function (key) { | ||
|  |       return ({ | ||
|  |         boolean: 'bools', | ||
|  |         string: 'strings', | ||
|  |         number: 'numbers' | ||
|  |       })[key] | ||
|  |     }).filter(Boolean).pop() | ||
|  | 
 | ||
|  |     // assign key to be coerced
 | ||
|  |     if (assignment) { | ||
|  |       flags[assignment][key] = true | ||
|  |     } | ||
|  | 
 | ||
|  |     flags.arrays[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   ;[].concat(opts.boolean).filter(Boolean).forEach(function (key) { | ||
|  |     flags.bools[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   ;[].concat(opts.string).filter(Boolean).forEach(function (key) { | ||
|  |     flags.strings[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   ;[].concat(opts.number).filter(Boolean).forEach(function (key) { | ||
|  |     flags.numbers[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   ;[].concat(opts.count).filter(Boolean).forEach(function (key) { | ||
|  |     flags.counts[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   ;[].concat(opts.normalize).filter(Boolean).forEach(function (key) { | ||
|  |     flags.normalize[key] = true | ||
|  |     flags.keys.push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   Object.keys(opts.narg || {}).forEach(function (k) { | ||
|  |     flags.nargs[k] = opts.narg[k] | ||
|  |     flags.keys.push(k) | ||
|  |   }) | ||
|  | 
 | ||
|  |   Object.keys(opts.coerce || {}).forEach(function (k) { | ||
|  |     flags.coercions[k] = opts.coerce[k] | ||
|  |     flags.keys.push(k) | ||
|  |   }) | ||
|  | 
 | ||
|  |   if (Array.isArray(opts.config) || typeof opts.config === 'string') { | ||
|  |     ;[].concat(opts.config).filter(Boolean).forEach(function (key) { | ||
|  |       flags.configs[key] = true | ||
|  |     }) | ||
|  |   } else { | ||
|  |     Object.keys(opts.config || {}).forEach(function (k) { | ||
|  |       flags.configs[k] = opts.config[k] | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   // create a lookup table that takes into account all
 | ||
|  |   // combinations of aliases: {f: ['foo'], foo: ['f']}
 | ||
|  |   extendAliases(opts.key, aliases, opts.default, flags.arrays) | ||
|  | 
 | ||
|  |   // apply default values to all aliases.
 | ||
|  |   Object.keys(defaults).forEach(function (key) { | ||
|  |     (flags.aliases[key] || []).forEach(function (alias) { | ||
|  |       defaults[alias] = defaults[key] | ||
|  |     }) | ||
|  |   }) | ||
|  | 
 | ||
|  |   var argv = { _: [] } | ||
|  | 
 | ||
|  |   Object.keys(flags.bools).forEach(function (key) { | ||
|  |     if (Object.prototype.hasOwnProperty.call(defaults, key)) { | ||
|  |       setArg(key, defaults[key]) | ||
|  |       setDefaulted(key) | ||
|  |     } | ||
|  |   }) | ||
|  | 
 | ||
|  |   var notFlags = [] | ||
|  | 
 | ||
|  |   for (var i = 0; i < args.length; i++) { | ||
|  |     var arg = args[i] | ||
|  |     var broken | ||
|  |     var key | ||
|  |     var letters | ||
|  |     var m | ||
|  |     var next | ||
|  |     var value | ||
|  | 
 | ||
|  |     // -- separated by =
 | ||
|  |     if (arg.match(/^--.+=/) || ( | ||
|  |       !configuration['short-option-groups'] && arg.match(/^-.+=/) | ||
|  |     )) { | ||
|  |       // Using [\s\S] instead of . because js doesn't support the
 | ||
|  |       // 'dotall' regex modifier. See:
 | ||
|  |       // http://stackoverflow.com/a/1068308/13216
 | ||
|  |       m = arg.match(/^--?([^=]+)=([\s\S]*)$/) | ||
|  | 
 | ||
|  |       // nargs format = '--f=monkey washing cat'
 | ||
|  |       if (checkAllAliases(m[1], flags.nargs)) { | ||
|  |         args.splice(i + 1, 0, m[2]) | ||
|  |         i = eatNargs(i, m[1], args) | ||
|  |       // arrays format = '--f=a b c'
 | ||
|  |       } else if (checkAllAliases(m[1], flags.arrays) && args.length > i + 1) { | ||
|  |         args.splice(i + 1, 0, m[2]) | ||
|  |         i = eatArray(i, m[1], args) | ||
|  |       } else { | ||
|  |         setArg(m[1], m[2]) | ||
|  |       } | ||
|  |     } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) { | ||
|  |       key = arg.match(negatedBoolean)[1] | ||
|  |       setArg(key, false) | ||
|  | 
 | ||
|  |     // -- seperated by space.
 | ||
|  |     } else if (arg.match(/^--.+/) || ( | ||
|  |       !configuration['short-option-groups'] && arg.match(/^-.+/) | ||
|  |     )) { | ||
|  |       key = arg.match(/^--?(.+)/)[1] | ||
|  | 
 | ||
|  |       // nargs format = '--foo a b c'
 | ||
|  |       if (checkAllAliases(key, flags.nargs)) { | ||
|  |         i = eatNargs(i, key, args) | ||
|  |       // array format = '--foo a b c'
 | ||
|  |       } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { | ||
|  |         i = eatArray(i, key, args) | ||
|  |       } else { | ||
|  |         next = args[i + 1] | ||
|  | 
 | ||
|  |         if (next !== undefined && (!next.match(/^-/) || | ||
|  |           next.match(negative)) && | ||
|  |           !checkAllAliases(key, flags.bools) && | ||
|  |           !checkAllAliases(key, flags.counts)) { | ||
|  |           setArg(key, next) | ||
|  |           i++ | ||
|  |         } else if (/^(true|false)$/.test(next)) { | ||
|  |           setArg(key, next) | ||
|  |           i++ | ||
|  |         } else { | ||
|  |           setArg(key, defaultForType(guessType(key, flags))) | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |     // dot-notation flag seperated by '='.
 | ||
|  |     } else if (arg.match(/^-.\..+=/)) { | ||
|  |       m = arg.match(/^-([^=]+)=([\s\S]*)$/) | ||
|  |       setArg(m[1], m[2]) | ||
|  | 
 | ||
|  |     // dot-notation flag seperated by space.
 | ||
|  |     } else if (arg.match(/^-.\..+/)) { | ||
|  |       next = args[i + 1] | ||
|  |       key = arg.match(/^-(.\..+)/)[1] | ||
|  | 
 | ||
|  |       if (next !== undefined && !next.match(/^-/) && | ||
|  |         !checkAllAliases(key, flags.bools) && | ||
|  |         !checkAllAliases(key, flags.counts)) { | ||
|  |         setArg(key, next) | ||
|  |         i++ | ||
|  |       } else { | ||
|  |         setArg(key, defaultForType(guessType(key, flags))) | ||
|  |       } | ||
|  |     } else if (arg.match(/^-[^-]+/) && !arg.match(negative)) { | ||
|  |       letters = arg.slice(1, -1).split('') | ||
|  |       broken = false | ||
|  | 
 | ||
|  |       for (var j = 0; j < letters.length; j++) { | ||
|  |         next = arg.slice(j + 2) | ||
|  | 
 | ||
|  |         if (letters[j + 1] && letters[j + 1] === '=') { | ||
|  |           value = arg.slice(j + 3) | ||
|  |           key = letters[j] | ||
|  | 
 | ||
|  |           // nargs format = '-f=monkey washing cat'
 | ||
|  |           if (checkAllAliases(key, flags.nargs)) { | ||
|  |             args.splice(i + 1, 0, value) | ||
|  |             i = eatNargs(i, key, args) | ||
|  |           // array format = '-f=a b c'
 | ||
|  |           } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { | ||
|  |             args.splice(i + 1, 0, value) | ||
|  |             i = eatArray(i, key, args) | ||
|  |           } else { | ||
|  |             setArg(key, value) | ||
|  |           } | ||
|  | 
 | ||
|  |           broken = true | ||
|  |           break | ||
|  |         } | ||
|  | 
 | ||
|  |         if (next === '-') { | ||
|  |           setArg(letters[j], next) | ||
|  |           continue | ||
|  |         } | ||
|  | 
 | ||
|  |         // current letter is an alphabetic character and next value is a number
 | ||
|  |         if (/[A-Za-z]/.test(letters[j]) && | ||
|  |           /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) { | ||
|  |           setArg(letters[j], next) | ||
|  |           broken = true | ||
|  |           break | ||
|  |         } | ||
|  | 
 | ||
|  |         if (letters[j + 1] && letters[j + 1].match(/\W/)) { | ||
|  |           setArg(letters[j], next) | ||
|  |           broken = true | ||
|  |           break | ||
|  |         } else { | ||
|  |           setArg(letters[j], defaultForType(guessType(letters[j], flags))) | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       key = arg.slice(-1)[0] | ||
|  | 
 | ||
|  |       if (!broken && key !== '-') { | ||
|  |         // nargs format = '-f a b c'
 | ||
|  |         if (checkAllAliases(key, flags.nargs)) { | ||
|  |           i = eatNargs(i, key, args) | ||
|  |         // array format = '-f a b c'
 | ||
|  |         } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) { | ||
|  |           i = eatArray(i, key, args) | ||
|  |         } else { | ||
|  |           next = args[i + 1] | ||
|  | 
 | ||
|  |           if (next !== undefined && (!/^(-|--)[^-]/.test(next) || | ||
|  |             next.match(negative)) && | ||
|  |             !checkAllAliases(key, flags.bools) && | ||
|  |             !checkAllAliases(key, flags.counts)) { | ||
|  |             setArg(key, next) | ||
|  |             i++ | ||
|  |           } else if (/^(true|false)$/.test(next)) { | ||
|  |             setArg(key, next) | ||
|  |             i++ | ||
|  |           } else { | ||
|  |             setArg(key, defaultForType(guessType(key, flags))) | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } else if (arg === '--') { | ||
|  |       notFlags = args.slice(i + 1) | ||
|  |       break | ||
|  |     } else if (configuration['halt-at-non-option']) { | ||
|  |       notFlags = args.slice(i) | ||
|  |       break | ||
|  |     } else { | ||
|  |       argv._.push(maybeCoerceNumber('_', arg)) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // order of precedence:
 | ||
|  |   // 1. command line arg
 | ||
|  |   // 2. value from env var
 | ||
|  |   // 3. value from config file
 | ||
|  |   // 4. value from config objects
 | ||
|  |   // 5. configured default value
 | ||
|  |   applyEnvVars(argv, true) // special case: check env vars that point to config file
 | ||
|  |   applyEnvVars(argv, false) | ||
|  |   setConfig(argv) | ||
|  |   setConfigObjects() | ||
|  |   applyDefaultsAndAliases(argv, flags.aliases, defaults) | ||
|  |   applyCoercions(argv) | ||
|  |   if (configuration['set-placeholder-key']) setPlaceholderKeys(argv) | ||
|  | 
 | ||
|  |   // for any counts either not in args or without an explicit default, set to 0
 | ||
|  |   Object.keys(flags.counts).forEach(function (key) { | ||
|  |     if (!hasKey(argv, key.split('.'))) setArg(key, 0) | ||
|  |   }) | ||
|  | 
 | ||
|  |   // '--' defaults to undefined.
 | ||
|  |   if (notFlagsOption && notFlags.length) argv[notFlagsArgv] = [] | ||
|  |   notFlags.forEach(function (key) { | ||
|  |     argv[notFlagsArgv].push(key) | ||
|  |   }) | ||
|  | 
 | ||
|  |   // how many arguments should we consume, based
 | ||
|  |   // on the nargs option?
 | ||
|  |   function eatNargs (i, key, args) { | ||
|  |     var ii | ||
|  |     const toEat = checkAllAliases(key, flags.nargs) | ||
|  | 
 | ||
|  |     // nargs will not consume flag arguments, e.g., -abc, --foo,
 | ||
|  |     // and terminates when one is observed.
 | ||
|  |     var available = 0 | ||
|  |     for (ii = i + 1; ii < args.length; ii++) { | ||
|  |       if (!args[ii].match(/^-[^0-9]/)) available++ | ||
|  |       else break | ||
|  |     } | ||
|  | 
 | ||
|  |     if (available < toEat) error = Error(__('Not enough arguments following: %s', key)) | ||
|  | 
 | ||
|  |     const consumed = Math.min(available, toEat) | ||
|  |     for (ii = i + 1; ii < (consumed + i + 1); ii++) { | ||
|  |       setArg(key, args[ii]) | ||
|  |     } | ||
|  | 
 | ||
|  |     return (i + consumed) | ||
|  |   } | ||
|  | 
 | ||
|  |   // if an option is an array, eat all non-hyphenated arguments
 | ||
|  |   // following it... YUM!
 | ||
|  |   // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
 | ||
|  |   function eatArray (i, key, args) { | ||
|  |     var start = i + 1 | ||
|  |     var argsToSet = [] | ||
|  |     var multipleArrayFlag = i > 0 | ||
|  |     for (var ii = i + 1; ii < args.length; ii++) { | ||
|  |       if (/^-/.test(args[ii]) && !negative.test(args[ii])) { | ||
|  |         if (ii === start) { | ||
|  |           setArg(key, defaultForType('array')) | ||
|  |         } | ||
|  |         multipleArrayFlag = true | ||
|  |         break | ||
|  |       } | ||
|  |       i = ii | ||
|  |       argsToSet.push(args[ii]) | ||
|  |     } | ||
|  |     if (multipleArrayFlag) { | ||
|  |       setArg(key, argsToSet.map(function (arg) { | ||
|  |         return processValue(key, arg) | ||
|  |       })) | ||
|  |     } else { | ||
|  |       argsToSet.forEach(function (arg) { | ||
|  |         setArg(key, arg) | ||
|  |       }) | ||
|  |     } | ||
|  | 
 | ||
|  |     return i | ||
|  |   } | ||
|  | 
 | ||
|  |   function setArg (key, val) { | ||
|  |     unsetDefaulted(key) | ||
|  | 
 | ||
|  |     if (/-/.test(key) && configuration['camel-case-expansion']) { | ||
|  |       var alias = key.split('.').map(function (prop) { | ||
|  |         return camelCase(prop) | ||
|  |       }).join('.') | ||
|  |       addNewAlias(key, alias) | ||
|  |     } | ||
|  | 
 | ||
|  |     var value = processValue(key, val) | ||
|  | 
 | ||
|  |     var splitKey = key.split('.') | ||
|  |     setKey(argv, splitKey, value) | ||
|  | 
 | ||
|  |     // handle populating aliases of the full key
 | ||
|  |     if (flags.aliases[key]) { | ||
|  |       flags.aliases[key].forEach(function (x) { | ||
|  |         x = x.split('.') | ||
|  |         setKey(argv, x, value) | ||
|  |       }) | ||
|  |     } | ||
|  | 
 | ||
|  |     // handle populating aliases of the first element of the dot-notation key
 | ||
|  |     if (splitKey.length > 1 && configuration['dot-notation']) { | ||
|  |       ;(flags.aliases[splitKey[0]] || []).forEach(function (x) { | ||
|  |         x = x.split('.') | ||
|  | 
 | ||
|  |         // expand alias with nested objects in key
 | ||
|  |         var a = [].concat(splitKey) | ||
|  |         a.shift() // nuke the old key.
 | ||
|  |         x = x.concat(a) | ||
|  | 
 | ||
|  |         setKey(argv, x, value) | ||
|  |       }) | ||
|  |     } | ||
|  | 
 | ||
|  |     // Set normalize getter and setter when key is in 'normalize' but isn't an array
 | ||
|  |     if (checkAllAliases(key, flags.normalize) && !checkAllAliases(key, flags.arrays)) { | ||
|  |       var keys = [key].concat(flags.aliases[key] || []) | ||
|  |       keys.forEach(function (key) { | ||
|  |         argv.__defineSetter__(key, function (v) { | ||
|  |           val = path.normalize(v) | ||
|  |         }) | ||
|  | 
 | ||
|  |         argv.__defineGetter__(key, function () { | ||
|  |           return typeof val === 'string' ? path.normalize(val) : val | ||
|  |         }) | ||
|  |       }) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   function addNewAlias (key, alias) { | ||
|  |     if (!(flags.aliases[key] && flags.aliases[key].length)) { | ||
|  |       flags.aliases[key] = [alias] | ||
|  |       newAliases[alias] = true | ||
|  |     } | ||
|  |     if (!(flags.aliases[alias] && flags.aliases[alias].length)) { | ||
|  |       addNewAlias(alias, key) | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   function processValue (key, val) { | ||
|  |     // handle parsing boolean arguments --foo=true --bar false.
 | ||
|  |     if (checkAllAliases(key, flags.bools) || checkAllAliases(key, flags.counts)) { | ||
|  |       if (typeof val === 'string') val = val === 'true' | ||
|  |     } | ||
|  | 
 | ||
|  |     var value = maybeCoerceNumber(key, val) | ||
|  | 
 | ||
|  |     // increment a count given as arg (either no value or value parsed as boolean)
 | ||
|  |     if (checkAllAliases(key, flags.counts) && (isUndefined(value) || typeof value === 'boolean')) { | ||
|  |       value = increment | ||
|  |     } | ||
|  | 
 | ||
|  |     // Set normalized value when key is in 'normalize' and in 'arrays'
 | ||
|  |     if (checkAllAliases(key, flags.normalize) && checkAllAliases(key, flags.arrays)) { | ||
|  |       if (Array.isArray(val)) value = val.map(path.normalize) | ||
|  |       else value = path.normalize(val) | ||
|  |     } | ||
|  |     return value | ||
|  |   } | ||
|  | 
 | ||
|  |   function maybeCoerceNumber (key, value) { | ||
|  |     if (!checkAllAliases(key, flags.strings) && !checkAllAliases(key, flags.coercions)) { | ||
|  |       const shouldCoerceNumber = isNumber(value) && configuration['parse-numbers'] && ( | ||
|  |         Number.isSafeInteger(Math.floor(value)) | ||
|  |       ) | ||
|  |       if (shouldCoerceNumber || (!isUndefined(value) && checkAllAliases(key, flags.numbers))) value = Number(value) | ||
|  |     } | ||
|  |     return value | ||
|  |   } | ||
|  | 
 | ||
|  |   // set args from config.json file, this should be
 | ||
|  |   // applied last so that defaults can be applied.
 | ||
|  |   function setConfig (argv) { | ||
|  |     var configLookup = {} | ||
|  | 
 | ||
|  |     // expand defaults/aliases, in-case any happen to reference
 | ||
|  |     // the config.json file.
 | ||
|  |     applyDefaultsAndAliases(configLookup, flags.aliases, defaults) | ||
|  | 
 | ||
|  |     Object.keys(flags.configs).forEach(function (configKey) { | ||
|  |       var configPath = argv[configKey] || configLookup[configKey] | ||
|  |       if (configPath) { | ||
|  |         try { | ||
|  |           var config = null | ||
|  |           var resolvedConfigPath = path.resolve(process.cwd(), configPath) | ||
|  | 
 | ||
|  |           if (typeof flags.configs[configKey] === 'function') { | ||
|  |             try { | ||
|  |               config = flags.configs[configKey](resolvedConfigPath) | ||
|  |             } catch (e) { | ||
|  |               config = e | ||
|  |             } | ||
|  |             if (config instanceof Error) { | ||
|  |               error = config | ||
|  |               return | ||
|  |             } | ||
|  |           } else { | ||
|  |             config = require(resolvedConfigPath) | ||
|  |           } | ||
|  | 
 | ||
|  |           setConfigObject(config) | ||
|  |         } catch (ex) { | ||
|  |           if (argv[configKey]) error = Error(__('Invalid JSON config file: %s', configPath)) | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   // set args from config object.
 | ||
|  |   // it recursively checks nested objects.
 | ||
|  |   function setConfigObject (config, prev) { | ||
|  |     Object.keys(config).forEach(function (key) { | ||
|  |       var value = config[key] | ||
|  |       var fullKey = prev ? prev + '.' + key : key | ||
|  | 
 | ||
|  |       // if the value is an inner object and we have dot-notation
 | ||
|  |       // enabled, treat inner objects in config the same as
 | ||
|  |       // heavily nested dot notations (foo.bar.apple).
 | ||
|  |       if (typeof value === 'object' && value !== null && !Array.isArray(value) && configuration['dot-notation']) { | ||
|  |         // if the value is an object but not an array, check nested object
 | ||
|  |         setConfigObject(value, fullKey) | ||
|  |       } else { | ||
|  |         // setting arguments via CLI takes precedence over
 | ||
|  |         // values within the config file.
 | ||
|  |         if (!hasKey(argv, fullKey.split('.')) || (flags.defaulted[fullKey]) || (flags.arrays[fullKey] && configuration['combine-arrays'])) { | ||
|  |           setArg(fullKey, value) | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   // set all config objects passed in opts
 | ||
|  |   function setConfigObjects () { | ||
|  |     if (typeof configObjects === 'undefined') return | ||
|  |     configObjects.forEach(function (configObject) { | ||
|  |       setConfigObject(configObject) | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   function applyEnvVars (argv, configOnly) { | ||
|  |     if (typeof envPrefix === 'undefined') return | ||
|  | 
 | ||
|  |     var prefix = typeof envPrefix === 'string' ? envPrefix : '' | ||
|  |     Object.keys(process.env).forEach(function (envVar) { | ||
|  |       if (prefix === '' || envVar.lastIndexOf(prefix, 0) === 0) { | ||
|  |         // get array of nested keys and convert them to camel case
 | ||
|  |         var keys = envVar.split('__').map(function (key, i) { | ||
|  |           if (i === 0) { | ||
|  |             key = key.substring(prefix.length) | ||
|  |           } | ||
|  |           return camelCase(key) | ||
|  |         }) | ||
|  | 
 | ||
|  |         if (((configOnly && flags.configs[keys.join('.')]) || !configOnly) && (!hasKey(argv, keys) || flags.defaulted[keys.join('.')])) { | ||
|  |           setArg(keys.join('.'), process.env[envVar]) | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   function applyCoercions (argv) { | ||
|  |     var coerce | ||
|  |     var applied = {} | ||
|  |     Object.keys(argv).forEach(function (key) { | ||
|  |       if (!applied.hasOwnProperty(key)) { // If we haven't already coerced this option via one of its aliases
 | ||
|  |         coerce = checkAllAliases(key, flags.coercions) | ||
|  |         if (typeof coerce === 'function') { | ||
|  |           try { | ||
|  |             var value = coerce(argv[key]) | ||
|  |             ;([].concat(flags.aliases[key] || [], key)).forEach(ali => { | ||
|  |               applied[ali] = argv[ali] = value | ||
|  |             }) | ||
|  |           } catch (err) { | ||
|  |             error = err | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   function setPlaceholderKeys (argv) { | ||
|  |     flags.keys.forEach((key) => { | ||
|  |       // don't set placeholder keys for dot notation options 'foo.bar'.
 | ||
|  |       if (~key.indexOf('.')) return | ||
|  |       if (typeof argv[key] === 'undefined') argv[key] = undefined | ||
|  |     }) | ||
|  |     return argv | ||
|  |   } | ||
|  | 
 | ||
|  |   function applyDefaultsAndAliases (obj, aliases, defaults) { | ||
|  |     Object.keys(defaults).forEach(function (key) { | ||
|  |       if (!hasKey(obj, key.split('.'))) { | ||
|  |         setKey(obj, key.split('.'), defaults[key]) | ||
|  | 
 | ||
|  |         ;(aliases[key] || []).forEach(function (x) { | ||
|  |           if (hasKey(obj, x.split('.'))) return | ||
|  |           setKey(obj, x.split('.'), defaults[key]) | ||
|  |         }) | ||
|  |       } | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   function hasKey (obj, keys) { | ||
|  |     var o = obj | ||
|  | 
 | ||
|  |     if (!configuration['dot-notation']) keys = [keys.join('.')] | ||
|  | 
 | ||
|  |     keys.slice(0, -1).forEach(function (key) { | ||
|  |       o = (o[key] || {}) | ||
|  |     }) | ||
|  | 
 | ||
|  |     var key = keys[keys.length - 1] | ||
|  | 
 | ||
|  |     if (typeof o !== 'object') return false | ||
|  |     else return key in o | ||
|  |   } | ||
|  | 
 | ||
|  |   function setKey (obj, keys, value) { | ||
|  |     var o = obj | ||
|  | 
 | ||
|  |     if (!configuration['dot-notation']) keys = [keys.join('.')] | ||
|  | 
 | ||
|  |     keys.slice(0, -1).forEach(function (key, index) { | ||
|  |       if (typeof o === 'object' && o[key] === undefined) { | ||
|  |         o[key] = {} | ||
|  |       } | ||
|  | 
 | ||
|  |       if (typeof o[key] !== 'object' || Array.isArray(o[key])) { | ||
|  |         // ensure that o[key] is an array, and that the last item is an empty object.
 | ||
|  |         if (Array.isArray(o[key])) { | ||
|  |           o[key].push({}) | ||
|  |         } else { | ||
|  |           o[key] = [o[key], {}] | ||
|  |         } | ||
|  | 
 | ||
|  |         // we want to update the empty object at the end of the o[key] array, so set o to that object
 | ||
|  |         o = o[key][o[key].length - 1] | ||
|  |       } else { | ||
|  |         o = o[key] | ||
|  |       } | ||
|  |     }) | ||
|  | 
 | ||
|  |     var key = keys[keys.length - 1] | ||
|  | 
 | ||
|  |     var isTypeArray = checkAllAliases(keys.join('.'), flags.arrays) | ||
|  |     var isValueArray = Array.isArray(value) | ||
|  |     var duplicate = configuration['duplicate-arguments-array'] | ||
|  | 
 | ||
|  |     if (value === increment) { | ||
|  |       o[key] = increment(o[key]) | ||
|  |     } else if (Array.isArray(o[key])) { | ||
|  |       if (duplicate && isTypeArray && isValueArray) { | ||
|  |         o[key] = configuration['flatten-duplicate-arrays'] ? o[key].concat(value) : (Array.isArray(o[key][0]) ? o[key] : [o[key]]).concat([value]) | ||
|  |       } else if (!duplicate && Boolean(isTypeArray) === Boolean(isValueArray)) { | ||
|  |         o[key] = value | ||
|  |       } else { | ||
|  |         o[key] = o[key].concat([value]) | ||
|  |       } | ||
|  |     } else if (o[key] === undefined && isTypeArray) { | ||
|  |       o[key] = isValueArray ? value : [value] | ||
|  |     } else if (duplicate && !(o[key] === undefined || checkAllAliases(key, flags.bools) || checkAllAliases(keys.join('.'), flags.bools) || checkAllAliases(key, flags.counts))) { | ||
|  |       o[key] = [ o[key], value ] | ||
|  |     } else { | ||
|  |       o[key] = value | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // extend the aliases list with inferred aliases.
 | ||
|  |   function extendAliases () { | ||
|  |     Array.prototype.slice.call(arguments).forEach(function (obj) { | ||
|  |       Object.keys(obj || {}).forEach(function (key) { | ||
|  |         // short-circuit if we've already added a key
 | ||
|  |         // to the aliases array, for example it might
 | ||
|  |         // exist in both 'opts.default' and 'opts.key'.
 | ||
|  |         if (flags.aliases[key]) return | ||
|  | 
 | ||
|  |         flags.aliases[key] = [].concat(aliases[key] || []) | ||
|  |         // For "--option-name", also set argv.optionName
 | ||
|  |         flags.aliases[key].concat(key).forEach(function (x) { | ||
|  |           if (/-/.test(x) && configuration['camel-case-expansion']) { | ||
|  |             var c = camelCase(x) | ||
|  |             if (c !== key && flags.aliases[key].indexOf(c) === -1) { | ||
|  |               flags.aliases[key].push(c) | ||
|  |               newAliases[c] = true | ||
|  |             } | ||
|  |           } | ||
|  |         }) | ||
|  |         // For "--optionName", also set argv['option-name']
 | ||
|  |         flags.aliases[key].concat(key).forEach(function (x) { | ||
|  |           if (x.length > 1 && /[A-Z]/.test(x) && configuration['camel-case-expansion']) { | ||
|  |             var c = decamelize(x, '-') | ||
|  |             if (c !== key && flags.aliases[key].indexOf(c) === -1) { | ||
|  |               flags.aliases[key].push(c) | ||
|  |               newAliases[c] = true | ||
|  |             } | ||
|  |           } | ||
|  |         }) | ||
|  |         flags.aliases[key].forEach(function (x) { | ||
|  |           flags.aliases[x] = [key].concat(flags.aliases[key].filter(function (y) { | ||
|  |             return x !== y | ||
|  |           })) | ||
|  |         }) | ||
|  |       }) | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   // check if a flag is set for any of a key's aliases.
 | ||
|  |   function checkAllAliases (key, flag) { | ||
|  |     var isSet = false | ||
|  |     var toCheck = [].concat(flags.aliases[key] || [], key) | ||
|  | 
 | ||
|  |     toCheck.forEach(function (key) { | ||
|  |       if (flag[key]) isSet = flag[key] | ||
|  |     }) | ||
|  | 
 | ||
|  |     return isSet | ||
|  |   } | ||
|  | 
 | ||
|  |   function setDefaulted (key) { | ||
|  |     [].concat(flags.aliases[key] || [], key).forEach(function (k) { | ||
|  |       flags.defaulted[k] = true | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   function unsetDefaulted (key) { | ||
|  |     [].concat(flags.aliases[key] || [], key).forEach(function (k) { | ||
|  |       delete flags.defaulted[k] | ||
|  |     }) | ||
|  |   } | ||
|  | 
 | ||
|  |   // return a default value, given the type of a flag.,
 | ||
|  |   // e.g., key of type 'string' will default to '', rather than 'true'.
 | ||
|  |   function defaultForType (type) { | ||
|  |     var def = { | ||
|  |       boolean: true, | ||
|  |       string: '', | ||
|  |       number: undefined, | ||
|  |       array: [] | ||
|  |     } | ||
|  | 
 | ||
|  |     return def[type] | ||
|  |   } | ||
|  | 
 | ||
|  |   // given a flag, enforce a default type.
 | ||
|  |   function guessType (key, flags) { | ||
|  |     var type = 'boolean' | ||
|  | 
 | ||
|  |     if (checkAllAliases(key, flags.strings)) type = 'string' | ||
|  |     else if (checkAllAliases(key, flags.numbers)) type = 'number' | ||
|  |     else if (checkAllAliases(key, flags.arrays)) type = 'array' | ||
|  | 
 | ||
|  |     return type | ||
|  |   } | ||
|  | 
 | ||
|  |   function isNumber (x) { | ||
|  |     if (typeof x === 'number') return true | ||
|  |     if (/^0x[0-9a-f]+$/i.test(x)) return true | ||
|  |     return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x) | ||
|  |   } | ||
|  | 
 | ||
|  |   function isUndefined (num) { | ||
|  |     return num === undefined | ||
|  |   } | ||
|  | 
 | ||
|  |   return { | ||
|  |     argv: argv, | ||
|  |     error: error, | ||
|  |     aliases: flags.aliases, | ||
|  |     newAliases: newAliases, | ||
|  |     configuration: configuration | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | // if any aliases reference each other, we should
 | ||
|  | // merge them together.
 | ||
|  | function combineAliases (aliases) { | ||
|  |   var aliasArrays = [] | ||
|  |   var change = true | ||
|  |   var combined = {} | ||
|  | 
 | ||
|  |   // turn alias lookup hash {key: ['alias1', 'alias2']} into
 | ||
|  |   // a simple array ['key', 'alias1', 'alias2']
 | ||
|  |   Object.keys(aliases).forEach(function (key) { | ||
|  |     aliasArrays.push( | ||
|  |       [].concat(aliases[key], key) | ||
|  |     ) | ||
|  |   }) | ||
|  | 
 | ||
|  |   // combine arrays until zero changes are
 | ||
|  |   // made in an iteration.
 | ||
|  |   while (change) { | ||
|  |     change = false | ||
|  |     for (var i = 0; i < aliasArrays.length; i++) { | ||
|  |       for (var ii = i + 1; ii < aliasArrays.length; ii++) { | ||
|  |         var intersect = aliasArrays[i].filter(function (v) { | ||
|  |           return aliasArrays[ii].indexOf(v) !== -1 | ||
|  |         }) | ||
|  | 
 | ||
|  |         if (intersect.length) { | ||
|  |           aliasArrays[i] = aliasArrays[i].concat(aliasArrays[ii]) | ||
|  |           aliasArrays.splice(ii, 1) | ||
|  |           change = true | ||
|  |           break | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // map arrays back to the hash-lookup (de-dupe while
 | ||
|  |   // we're at it).
 | ||
|  |   aliasArrays.forEach(function (aliasArray) { | ||
|  |     aliasArray = aliasArray.filter(function (v, i, self) { | ||
|  |       return self.indexOf(v) === i | ||
|  |     }) | ||
|  |     combined[aliasArray.pop()] = aliasArray | ||
|  |   }) | ||
|  | 
 | ||
|  |   return combined | ||
|  | } | ||
|  | 
 | ||
|  | function assign (defaults, configuration) { | ||
|  |   var o = {} | ||
|  |   configuration = configuration || {} | ||
|  | 
 | ||
|  |   Object.keys(defaults).forEach(function (k) { | ||
|  |     o[k] = defaults[k] | ||
|  |   }) | ||
|  |   Object.keys(configuration).forEach(function (k) { | ||
|  |     o[k] = configuration[k] | ||
|  |   }) | ||
|  | 
 | ||
|  |   return o | ||
|  | } | ||
|  | 
 | ||
|  | // this function should only be called when a count is given as an arg
 | ||
|  | // it is NOT called to set a default value
 | ||
|  | // thus we can start the count at 1 instead of 0
 | ||
|  | function increment (orig) { | ||
|  |   return orig !== undefined ? orig + 1 : 1 | ||
|  | } | ||
|  | 
 | ||
|  | function Parser (args, opts) { | ||
|  |   var result = parse(args.slice(), opts) | ||
|  | 
 | ||
|  |   return result.argv | ||
|  | } | ||
|  | 
 | ||
|  | // parse arguments and return detailed
 | ||
|  | // meta information, aliases, etc.
 | ||
|  | Parser.detailed = function (args, opts) { | ||
|  |   return parse(args.slice(), opts) | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = Parser |