mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1399 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1399 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| //
 | |
| // modified for use in audiobookshelf (removed camelCase opt)
 | |
| // Source: https://github.com/75lb/command-line-args
 | |
| //
 | |
| 
 | |
| /**
 | |
|  * Takes any input and guarantees an array back.
 | |
|  *
 | |
|  * - Converts array-like objects (e.g. `arguments`, `Set`) to a real array.
 | |
|  * - Converts `undefined` to an empty array.
 | |
|  * - Converts any another other, singular value (including `null`, objects and iterables other than `Set`) into an array containing that value.
 | |
|  * - Ignores input which is already an array.
 | |
|  *
 | |
|  * @module array-back
 | |
|  * @example
 | |
|  * > const arrayify = require('array-back')
 | |
|  *
 | |
|  * > arrayify(undefined)
 | |
|  * []
 | |
|  *
 | |
|  * > arrayify(null)
 | |
|  * [ null ]
 | |
|  *
 | |
|  * > arrayify(0)
 | |
|  * [ 0 ]
 | |
|  *
 | |
|  * > arrayify([ 1, 2 ])
 | |
|  * [ 1, 2 ]
 | |
|  *
 | |
|  * > arrayify(new Set([ 1, 2 ]))
 | |
|  * [ 1, 2 ]
 | |
|  *
 | |
|  * > function f(){ return arrayify(arguments); }
 | |
|  * > f(1,2,3)
 | |
|  * [ 1, 2, 3 ]
 | |
|  */
 | |
| 
 | |
| function isObject(input) {
 | |
|   return typeof input === 'object' && input !== null
 | |
| }
 | |
| 
 | |
| function isArrayLike(input) {
 | |
|   return isObject(input) && typeof input.length === 'number'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {*} - The input value to convert to an array
 | |
|  * @returns {Array}
 | |
|  * @alias module:array-back
 | |
|  */
 | |
| function arrayify(input) {
 | |
|   if (Array.isArray(input)) {
 | |
|     return input
 | |
|   }
 | |
| 
 | |
|   if (input === undefined) {
 | |
|     return []
 | |
|   }
 | |
| 
 | |
|   if (isArrayLike(input) || input instanceof Set) {
 | |
|     return Array.from(input)
 | |
|   }
 | |
| 
 | |
|   return [input]
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Takes any input and guarantees an array back.
 | |
|  *
 | |
|  * - converts array-like objects (e.g. `arguments`) to a real array
 | |
|  * - converts `undefined` to an empty array
 | |
|  * - converts any another other, singular value (including `null`) into an array containing that value
 | |
|  * - ignores input which is already an array
 | |
|  *
 | |
|  * @module array-back
 | |
|  * @example
 | |
|  * > const arrayify = require('array-back')
 | |
|  *
 | |
|  * > arrayify(undefined)
 | |
|  * []
 | |
|  *
 | |
|  * > arrayify(null)
 | |
|  * [ null ]
 | |
|  *
 | |
|  * > arrayify(0)
 | |
|  * [ 0 ]
 | |
|  *
 | |
|  * > arrayify([ 1, 2 ])
 | |
|  * [ 1, 2 ]
 | |
|  *
 | |
|  * > function f(){ return arrayify(arguments); }
 | |
|  * > f(1,2,3)
 | |
|  * [ 1, 2, 3 ]
 | |
|  */
 | |
| 
 | |
| function isObject$1(input) {
 | |
|   return typeof input === 'object' && input !== null
 | |
| }
 | |
| 
 | |
| function isArrayLike$1(input) {
 | |
|   return isObject$1(input) && typeof input.length === 'number'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {*} - the input value to convert to an array
 | |
|  * @returns {Array}
 | |
|  * @alias module:array-back
 | |
|  */
 | |
| function arrayify$1(input) {
 | |
|   if (Array.isArray(input)) {
 | |
|     return input
 | |
|   } else {
 | |
|     if (input === undefined) {
 | |
|       return []
 | |
|     } else if (isArrayLike$1(input)) {
 | |
|       return Array.prototype.slice.call(input)
 | |
|     } else {
 | |
|       return [input]
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Find and either replace or remove items in an array.
 | |
|  *
 | |
|  * @module find-replace
 | |
|  * @example
 | |
|  * > const findReplace = require('find-replace')
 | |
|  * > const numbers = [ 1, 2, 3]
 | |
|  *
 | |
|  * > findReplace(numbers, n => n === 2, 'two')
 | |
|  * [ 1, 'two', 3 ]
 | |
|  *
 | |
|  * > findReplace(numbers, n => n === 2, [ 'two', 'zwei' ])
 | |
|  * [ 1, [ 'two', 'zwei' ], 3 ]
 | |
|  *
 | |
|  * > findReplace(numbers, n => n === 2, 'two', 'zwei')
 | |
|  * [ 1, 'two', 'zwei', 3 ]
 | |
|  *
 | |
|  * > findReplace(numbers, n => n === 2) // no replacement, so remove
 | |
|  * [ 1, 3 ]
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @param {array} - The input array
 | |
|  * @param {testFn} - A predicate function which, if returning `true` causes the current item to be operated on.
 | |
|  * @param [replaceWith] {...any} - If specified, found values will be replaced with these values, else removed.
 | |
|  * @returns {array}
 | |
|  * @alias module:find-replace
 | |
|  */
 | |
| function findReplace(array, testFn) {
 | |
|   const found = [];
 | |
|   const replaceWiths = arrayify$1(arguments);
 | |
|   replaceWiths.splice(0, 2);
 | |
| 
 | |
|   arrayify$1(array).forEach((value, index) => {
 | |
|     let expanded = [];
 | |
|     replaceWiths.forEach(replaceWith => {
 | |
|       if (typeof replaceWith === 'function') {
 | |
|         expanded = expanded.concat(replaceWith(value));
 | |
|       } else {
 | |
|         expanded.push(replaceWith);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     if (testFn(value)) {
 | |
|       found.push({
 | |
|         index: index,
 | |
|         replaceWithValue: expanded
 | |
|       });
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   found.reverse().forEach(item => {
 | |
|     const spliceArgs = [item.index, 1].concat(item.replaceWithValue);
 | |
|     array.splice.apply(array, spliceArgs);
 | |
|   });
 | |
| 
 | |
|   return array
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Some useful tools for working with `process.argv`.
 | |
|  *
 | |
|  * @module argv-tools
 | |
|  * @typicalName argvTools
 | |
|  * @example
 | |
|  * const argvTools = require('argv-tools')
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Regular expressions for matching option formats.
 | |
|  * @static
 | |
|  */
 | |
| const re = {
 | |
|   short: /^-([^\d-])$/,
 | |
|   long: /^--(\S+)/,
 | |
|   combinedShort: /^-[^\d-]{2,}$/,
 | |
|   optEquals: /^(--\S+?)=(.*)/
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Array subclass encapsulating common operations on `process.argv`.
 | |
|  * @static
 | |
|  */
 | |
| class ArgvArray extends Array {
 | |
|   /**
 | |
|    * Clears the array has loads the supplied input.
 | |
|    * @param {string[]} argv - The argv list to load. Defaults to `process.argv`.
 | |
|    */
 | |
|   load(argv) {
 | |
|     this.clear();
 | |
|     if (argv && argv !== process.argv) {
 | |
|       argv = arrayify(argv);
 | |
|     } else {
 | |
|       /* if no argv supplied, assume we are parsing process.argv */
 | |
|       argv = process.argv.slice(0);
 | |
|       const deleteCount = process.execArgv.some(isExecArg) ? 1 : 2;
 | |
|       argv.splice(0, deleteCount);
 | |
|     }
 | |
|     argv.forEach(arg => this.push(String(arg)));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clear the array.
 | |
|    */
 | |
|   clear() {
 | |
|     this.length = 0;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * expand ``--option=value` style args.
 | |
|    */
 | |
|   expandOptionEqualsNotation() {
 | |
|     if (this.some(arg => re.optEquals.test(arg))) {
 | |
|       const expandedArgs = [];
 | |
|       this.forEach(arg => {
 | |
|         const matches = arg.match(re.optEquals);
 | |
|         if (matches) {
 | |
|           expandedArgs.push(matches[1], matches[2]);
 | |
|         } else {
 | |
|           expandedArgs.push(arg);
 | |
|         }
 | |
|       });
 | |
|       this.clear();
 | |
|       this.load(expandedArgs);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * expand getopt-style combinedShort options.
 | |
|    */
 | |
|   expandGetoptNotation() {
 | |
|     if (this.hasCombinedShortOptions()) {
 | |
|       findReplace(this, re.combinedShort, expandCombinedShortArg);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns true if the array contains combined short options (e.g. `-ab`).
 | |
|    * @returns {boolean}
 | |
|    */
 | |
|   hasCombinedShortOptions() {
 | |
|     return this.some(arg => re.combinedShort.test(arg))
 | |
|   }
 | |
| 
 | |
|   static from(argv) {
 | |
|     const result = new this();
 | |
|     result.load(argv);
 | |
|     return result
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Expand a combined short option.
 | |
|  * @param {string} - the string to expand, e.g. `-ab`
 | |
|  * @returns {string[]}
 | |
|  * @static
 | |
|  */
 | |
| function expandCombinedShortArg(arg) {
 | |
|   /* remove initial hypen */
 | |
|   arg = arg.slice(1);
 | |
|   return arg.split('').map(letter => '-' + letter)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the supplied arg matches `--option=value` notation.
 | |
|  * @param {string} - the arg to test, e.g. `--one=something`
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isOptionEqualsNotation(arg) {
 | |
|   return re.optEquals.test(arg)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the supplied arg is in either long (`--one`) or short (`-o`) format.
 | |
|  * @param {string} - the arg to test, e.g. `--one`
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isOption(arg) {
 | |
|   return (re.short.test(arg) || re.long.test(arg)) && !re.optEquals.test(arg)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the supplied arg is in long (`--one`) format.
 | |
|  * @param {string} - the arg to test, e.g. `--one`
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isLongOption(arg) {
 | |
|   return re.long.test(arg) && !isOptionEqualsNotation(arg)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns the name from a long, short or `--options=value` arg.
 | |
|  * @param {string} - the arg to inspect, e.g. `--one`
 | |
|  * @returns {string}
 | |
|  * @static
 | |
|  */
 | |
| function getOptionName(arg) {
 | |
|   if (re.short.test(arg)) {
 | |
|     return arg.match(re.short)[1]
 | |
|   } else if (isLongOption(arg)) {
 | |
|     return arg.match(re.long)[1]
 | |
|   } else if (isOptionEqualsNotation(arg)) {
 | |
|     return arg.match(re.optEquals)[1].replace(/^--/, '')
 | |
|   } else {
 | |
|     return null
 | |
|   }
 | |
| }
 | |
| 
 | |
| function isValue(arg) {
 | |
|   return !(isOption(arg) || re.combinedShort.test(arg) || re.optEquals.test(arg))
 | |
| }
 | |
| 
 | |
| function isExecArg(arg) {
 | |
|   return ['--eval', '-e'].indexOf(arg) > -1 || arg.startsWith('--eval=')
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * For type-checking Javascript values.
 | |
|  * @module typical
 | |
|  * @typicalname t
 | |
|  * @example
 | |
|  * const t = require('typical')
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Returns true if input is a number
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  * @example
 | |
|  * > t.isNumber(0)
 | |
|  * true
 | |
|  * > t.isNumber(1)
 | |
|  * true
 | |
|  * > t.isNumber(1.1)
 | |
|  * true
 | |
|  * > t.isNumber(0xff)
 | |
|  * true
 | |
|  * > t.isNumber(0644)
 | |
|  * true
 | |
|  * > t.isNumber(6.2e5)
 | |
|  * true
 | |
|  * > t.isNumber(NaN)
 | |
|  * false
 | |
|  * > t.isNumber(Infinity)
 | |
|  * false
 | |
|  */
 | |
| function isNumber(n) {
 | |
|   return !isNaN(parseFloat(n)) && isFinite(n)
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A plain object is a simple object literal, it is not an instance of a class. Returns true if the input `typeof` is `object` and directly decends from `Object`.
 | |
|  *
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  * @example
 | |
|  * > t.isPlainObject({ something: 'one' })
 | |
|  * true
 | |
|  * > t.isPlainObject(new Date())
 | |
|  * false
 | |
|  * > t.isPlainObject([ 0, 1 ])
 | |
|  * false
 | |
|  * > t.isPlainObject(/test/)
 | |
|  * false
 | |
|  * > t.isPlainObject(1)
 | |
|  * false
 | |
|  * > t.isPlainObject('one')
 | |
|  * false
 | |
|  * > t.isPlainObject(null)
 | |
|  * false
 | |
|  * > t.isPlainObject((function * () {})())
 | |
|  * false
 | |
|  * > t.isPlainObject(function * () {})
 | |
|  * false
 | |
|  */
 | |
| function isPlainObject(input) {
 | |
|   return input !== null && typeof input === 'object' && input.constructor === Object
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * An array-like value has all the properties of an array, but is not an array instance. Examples in the `arguments` object. Returns true if the input value is an object, not null and has a `length` property with a numeric value.
 | |
|  *
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  * @example
 | |
|  * function sum(x, y){
 | |
|  *     console.log(t.isArrayLike(arguments))
 | |
|  *     // prints `true`
 | |
|  * }
 | |
|  */
 | |
| function isArrayLike$2(input) {
 | |
|   return isObject$2(input) && typeof input.length === 'number'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * returns true if the typeof input is `'object'`, but not null!
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isObject$2(input) {
 | |
|   return typeof input === 'object' && input !== null
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input value is defined
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isDefined(input) {
 | |
|   return typeof input !== 'undefined'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input value is a string
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isString(input) {
 | |
|   return typeof input === 'string'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input value is a boolean
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isBoolean(input) {
 | |
|   return typeof input === 'boolean'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input value is a function
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isFunction(input) {
 | |
|   return typeof input === 'function'
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input value is an es2015 `class`.
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isClass(input) {
 | |
|   if (isFunction(input)) {
 | |
|     return /^class /.test(Function.prototype.toString.call(input))
 | |
|   } else {
 | |
|     return false
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input is a string, number, symbol, boolean, null or undefined value.
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isPrimitive(input) {
 | |
|   if (input === null) return true
 | |
|   switch (typeof input) {
 | |
|     case 'string':
 | |
|     case 'number':
 | |
|     case 'symbol':
 | |
|     case 'undefined':
 | |
|     case 'boolean':
 | |
|       return true
 | |
|     default:
 | |
|       return false
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input is a Promise.
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  */
 | |
| function isPromise(input) {
 | |
|   if (input) {
 | |
|     const isPromise = isDefined(Promise) && input instanceof Promise;
 | |
|     const isThenable = input.then && typeof input.then === 'function';
 | |
|     return !!(isPromise || isThenable)
 | |
|   } else {
 | |
|     return false
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns true if the input is an iterable (`Map`, `Set`, `Array`, Generator etc.).
 | |
|  * @param {*} - the input to test
 | |
|  * @returns {boolean}
 | |
|  * @static
 | |
|  * @example
 | |
|  * > t.isIterable('string')
 | |
|  * true
 | |
|  * > t.isIterable(new Map())
 | |
|  * true
 | |
|  * > t.isIterable([])
 | |
|  * true
 | |
|  * > t.isIterable((function * () {})())
 | |
|  * true
 | |
|  * > t.isIterable(Promise.resolve())
 | |
|  * false
 | |
|  * > t.isIterable(Promise)
 | |
|  * false
 | |
|  * > t.isIterable(true)
 | |
|  * false
 | |
|  * > t.isIterable({})
 | |
|  * false
 | |
|  * > t.isIterable(0)
 | |
|  * false
 | |
|  * > t.isIterable(1.1)
 | |
|  * false
 | |
|  * > t.isIterable(NaN)
 | |
|  * false
 | |
|  * > t.isIterable(Infinity)
 | |
|  * false
 | |
|  * > t.isIterable(function () {})
 | |
|  * false
 | |
|  * > t.isIterable(Date)
 | |
|  * false
 | |
|  * > t.isIterable()
 | |
|  * false
 | |
|  * > t.isIterable({ then: function () {} })
 | |
|  * false
 | |
|  */
 | |
| function isIterable(input) {
 | |
|   if (input === null || !isDefined(input)) {
 | |
|     return false
 | |
|   } else {
 | |
|     return (
 | |
|       typeof input[Symbol.iterator] === 'function' ||
 | |
|       typeof input[Symbol.asyncIterator] === 'function'
 | |
|     )
 | |
|   }
 | |
| }
 | |
| 
 | |
| var t = {
 | |
|   isNumber,
 | |
|   isString,
 | |
|   isBoolean,
 | |
|   isPlainObject,
 | |
|   isArrayLike: isArrayLike$2,
 | |
|   isObject: isObject$2,
 | |
|   isDefined,
 | |
|   isFunction,
 | |
|   isClass,
 | |
|   isPrimitive,
 | |
|   isPromise,
 | |
|   isIterable
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @module option-definition
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Describes a command-line option. Additionally, if generating a usage guide with [command-line-usage](https://github.com/75lb/command-line-usage) you could optionally add `description` and `typeLabel` properties to each definition.
 | |
|  *
 | |
|  * @alias module:option-definition
 | |
|  * @typicalname option
 | |
|  */
 | |
| class OptionDefinition {
 | |
|   constructor(definition) {
 | |
|     /**
 | |
|     * The only required definition property is `name`, so the simplest working example is
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'file' },
 | |
|     *   { name: 'depth' }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     * Where a `type` property is not specified it will default to `String`.
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | -------------------- | ------------ |
 | |
|     * | 1   | `--file` | `{ file: null }` |
 | |
|     * | 2   | `--file lib.js` | `{ file: 'lib.js' }` |
 | |
|     * | 3   | `--depth 2` | `{ depth: '2' }` |
 | |
|     *
 | |
|     * Unicode option names and aliases are valid, for example:
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'один' },
 | |
|     *   { name: '两' },
 | |
|     *   { name: 'три', alias: 'т' }
 | |
|     * ]
 | |
|     * ```
 | |
|     * @type {string}
 | |
|     */
 | |
|     this.name = definition.name;
 | |
| 
 | |
|     /**
 | |
|     * The `type` value is a setter function (you receive the output from this), enabling you to be specific about the type and value received.
 | |
|     *
 | |
|     * The most common values used are `String` (the default), `Number` and `Boolean` but you can use a custom function, for example:
 | |
|     *
 | |
|     * ```js
 | |
|     * const fs = require('fs')
 | |
|     *
 | |
|     * class FileDetails {
 | |
|     *   constructor (filename) {
 | |
|     *     this.filename = filename
 | |
|     *     this.exists = fs.existsSync(filename)
 | |
|     *   }
 | |
|     * }
 | |
|     *
 | |
|     * const cli = commandLineArgs([
 | |
|     *   { name: 'file', type: filename => new FileDetails(filename) },
 | |
|     *   { name: 'depth', type: Number }
 | |
|     * ])
 | |
|     * ```
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ----------------- | ------------ |
 | |
|     * | 1   | `--file asdf.txt` | `{ file: { filename: 'asdf.txt', exists: false } }` |
 | |
|     *
 | |
|     * The `--depth` option expects a `Number`. If no value was set, you will receive `null`.
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ----------------- | ------------ |
 | |
|     * | 2   | `--depth` | `{ depth: null }` |
 | |
|     * | 3   | `--depth 2` | `{ depth: 2 }` |
 | |
|     *
 | |
|     * @type {function}
 | |
|     * @default String
 | |
|     */
 | |
|     this.type = definition.type || String;
 | |
| 
 | |
|     /**
 | |
|     * getopt-style short option names. Can be any single character (unicode included) except a digit or hyphen.
 | |
|     *
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'hot', alias: 'h', type: Boolean },
 | |
|     *   { name: 'discount', alias: 'd', type: Boolean },
 | |
|     *   { name: 'courses', alias: 'c' , type: Number }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ------------ | ------------ |
 | |
|     * | 1   | `-hcd` | `{ hot: true, courses: null, discount: true }` |
 | |
|     * | 2   | `-hdc 3` | `{ hot: true, discount: true, courses: 3 }` |
 | |
|     *
 | |
|     * @type {string}
 | |
|     */
 | |
|     this.alias = definition.alias;
 | |
| 
 | |
|     /**
 | |
|     * Set this flag if the option takes a list of values. You will receive an array of values, each passed through the `type` function (if specified).
 | |
|     *
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'files', type: String, multiple: true }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     * Note, examples 1 and 3 below demonstrate "greedy" parsing which can be disabled by using `lazyMultiple`.
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ------------ | ------------ |
 | |
|     * | 1   | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     * | 2   | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     * | 3   | `--files *` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     *
 | |
|     * @type {boolean}
 | |
|     */
 | |
|     this.multiple = definition.multiple;
 | |
| 
 | |
|     /**
 | |
|      * Identical to `multiple` but with greedy parsing disabled.
 | |
|      *
 | |
|      * ```js
 | |
|      * const optionDefinitions = [
 | |
|      *   { name: 'files', lazyMultiple: true },
 | |
|      *   { name: 'verbose', alias: 'v', type: Boolean, lazyMultiple: true }
 | |
|      * ]
 | |
|      * ```
 | |
|      *
 | |
|      * | #   | argv input | commandLineArgs() output |
 | |
|      * | --- | ------------ | ------------ |
 | |
|      * | 1   | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|      * | 2   | `-vvv` | `{ verbose: [ true, true, true ] }` |
 | |
|      *
 | |
|      * @type {boolean}
 | |
|      */
 | |
|     this.lazyMultiple = definition.lazyMultiple;
 | |
| 
 | |
|     /**
 | |
|     * Any values unaccounted for by an option definition will be set on the `defaultOption`. This flag is typically set on the most commonly-used option to make for more concise usage (i.e. `$ example *.js` instead of `$ example --files *.js`).
 | |
|     *
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'files', multiple: true, defaultOption: true }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ------------ | ------------ |
 | |
|     * | 1   | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     * | 2   | `one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     * | 3   | `*` | `{ files: [ 'one.js', 'two.js' ] }` |
 | |
|     *
 | |
|     * @type {boolean}
 | |
|     */
 | |
|     this.defaultOption = definition.defaultOption;
 | |
| 
 | |
|     /**
 | |
|     * An initial value for the option.
 | |
|     *
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'files', multiple: true, defaultValue: [ 'one.js' ] },
 | |
|     *   { name: 'max', type: Number, defaultValue: 3 }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     * | #   | argv input | commandLineArgs() output |
 | |
|     * | --- | ------------ | ------------ |
 | |
|     * | 1   |  | `{ files: [ 'one.js' ], max: 3 }` |
 | |
|     * | 2   | `--files two.js` | `{ files: [ 'two.js' ], max: 3 }` |
 | |
|     * | 3   | `--max 4` | `{ files: [ 'one.js' ], max: 4 }` |
 | |
|     *
 | |
|     * @type {*}
 | |
|     */
 | |
|     this.defaultValue = definition.defaultValue;
 | |
| 
 | |
|     /**
 | |
|     * When your app has a large amount of options it makes sense to organise them in groups.
 | |
|     *
 | |
|     * There are two automatic groups: `_all` (contains all options) and `_none` (contains options without a `group` specified in their definition).
 | |
|     *
 | |
|     * ```js
 | |
|     * const optionDefinitions = [
 | |
|     *   { name: 'verbose', group: 'standard' },
 | |
|     *   { name: 'help', group: [ 'standard', 'main' ] },
 | |
|     *   { name: 'compress', group: [ 'server', 'main' ] },
 | |
|     *   { name: 'static', group: 'server' },
 | |
|     *   { name: 'debug' }
 | |
|     * ]
 | |
|     * ```
 | |
|     *
 | |
|     *<table>
 | |
|     *  <tr>
 | |
|     *    <th>#</th><th>Command Line</th><th>commandLineArgs() output</th>
 | |
|     *  </tr>
 | |
|     *  <tr>
 | |
|     *    <td>1</td><td><code>--verbose</code></td><td><pre><code>
 | |
|     *{
 | |
|     *  _all: { verbose: true },
 | |
|     *  standard: { verbose: true }
 | |
|     *}
 | |
|     *</code></pre></td>
 | |
|     *  </tr>
 | |
|     *  <tr>
 | |
|     *    <td>2</td><td><code>--debug</code></td><td><pre><code>
 | |
|     *{
 | |
|     *  _all: { debug: true },
 | |
|     *  _none: { debug: true }
 | |
|     *}
 | |
|     *</code></pre></td>
 | |
|     *  </tr>
 | |
|     *  <tr>
 | |
|     *    <td>3</td><td><code>--verbose --debug --compress</code></td><td><pre><code>
 | |
|     *{
 | |
|     *  _all: {
 | |
|     *    verbose: true,
 | |
|     *    debug: true,
 | |
|     *    compress: true
 | |
|     *  },
 | |
|     *  standard: { verbose: true },
 | |
|     *  server: { compress: true },
 | |
|     *  main: { compress: true },
 | |
|     *  _none: { debug: true }
 | |
|     *}
 | |
|     *</code></pre></td>
 | |
|     *  </tr>
 | |
|     *  <tr>
 | |
|     *    <td>4</td><td><code>--compress</code></td><td><pre><code>
 | |
|     *{
 | |
|     *  _all: { compress: true },
 | |
|     *  server: { compress: true },
 | |
|     *  main: { compress: true }
 | |
|     *}
 | |
|     *</code></pre></td>
 | |
|     *  </tr>
 | |
|     *</table>
 | |
|     *
 | |
|     * @type {string|string[]}
 | |
|     */
 | |
|     this.group = definition.group;
 | |
| 
 | |
|     /* pick up any remaining properties */
 | |
|     for (const prop in definition) {
 | |
|       if (!this[prop]) this[prop] = definition[prop];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   isBoolean() {
 | |
|     return this.type === Boolean || (t.isFunction(this.type) && this.type.name === 'Boolean')
 | |
|   }
 | |
| 
 | |
|   isMultiple() {
 | |
|     return this.multiple || this.lazyMultiple
 | |
|   }
 | |
| 
 | |
|   static create(def) {
 | |
|     const result = new this(def);
 | |
|     return result
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @module option-definitions
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @alias module:option-definitions
 | |
|  */
 | |
| class Definitions extends Array {
 | |
|   /**
 | |
|    * validate option definitions
 | |
|    * @param {boolean} [caseInsensitive=false] - whether arguments will be parsed in a case insensitive manner
 | |
|    * @returns {string}
 | |
|    */
 | |
|   validate(caseInsensitive) {
 | |
|     const someHaveNoName = this.some(def => !def.name);
 | |
|     if (someHaveNoName) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Invalid option definitions: the `name` property is required on each definition'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const someDontHaveFunctionType = this.some(def => def.type && typeof def.type !== 'function');
 | |
|     if (someDontHaveFunctionType) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Invalid option definitions: the `type` property must be a setter fuction (default: `Boolean`)'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     let invalidOption;
 | |
| 
 | |
|     const numericAlias = this.some(def => {
 | |
|       invalidOption = def;
 | |
|       return t.isDefined(def.alias) && t.isNumber(def.alias)
 | |
|     });
 | |
|     if (numericAlias) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Invalid option definition: to avoid ambiguity an alias cannot be numeric [--' + invalidOption.name + ' alias is -' + invalidOption.alias + ']'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const multiCharacterAlias = this.some(def => {
 | |
|       invalidOption = def;
 | |
|       return t.isDefined(def.alias) && def.alias.length !== 1
 | |
|     });
 | |
|     if (multiCharacterAlias) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Invalid option definition: an alias must be a single character'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const hypenAlias = this.some(def => {
 | |
|       invalidOption = def;
 | |
|       return def.alias === '-'
 | |
|     });
 | |
|     if (hypenAlias) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Invalid option definition: an alias cannot be "-"'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const duplicateName = hasDuplicates(this.map(def => caseInsensitive ? def.name.toLowerCase() : def.name));
 | |
|     if (duplicateName) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Two or more option definitions have the same name'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const duplicateAlias = hasDuplicates(this.map(def => caseInsensitive && t.isDefined(def.alias) ? def.alias.toLowerCase() : def.alias));
 | |
|     if (duplicateAlias) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Two or more option definitions have the same alias'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const duplicateDefaultOption = this.filter(def => def.defaultOption === true).length > 1;
 | |
|     if (duplicateDefaultOption) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         'Only one option definition can be the defaultOption'
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     const defaultBoolean = this.some(def => {
 | |
|       invalidOption = def;
 | |
|       return def.isBoolean() && def.defaultOption
 | |
|     });
 | |
|     if (defaultBoolean) {
 | |
|       halt(
 | |
|         'INVALID_DEFINITIONS',
 | |
|         `A boolean option ["${invalidOption.name}"] can not also be the defaultOption.`
 | |
|       );
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get definition by option arg (e.g. `--one` or `-o`)
 | |
|    * @param {string} [arg] the argument name to get the definition for
 | |
|    * @param {boolean} [caseInsensitive] whether to use case insensitive comparisons when finding the appropriate definition
 | |
|    * @returns {Definition}
 | |
|    */
 | |
|   get(arg, caseInsensitive) {
 | |
|     if (isOption(arg)) {
 | |
|       if (re.short.test(arg)) {
 | |
|         const shortOptionName = getOptionName(arg);
 | |
|         if (caseInsensitive) {
 | |
|           const lowercaseShortOptionName = shortOptionName.toLowerCase();
 | |
|           return this.find(def => t.isDefined(def.alias) && def.alias.toLowerCase() === lowercaseShortOptionName)
 | |
|         } else {
 | |
|           return this.find(def => def.alias === shortOptionName)
 | |
|         }
 | |
|       } else {
 | |
|         const optionName = getOptionName(arg);
 | |
|         if (caseInsensitive) {
 | |
|           const lowercaseOptionName = optionName.toLowerCase();
 | |
|           return this.find(def => def.name.toLowerCase() === lowercaseOptionName)
 | |
|         } else {
 | |
|           return this.find(def => def.name === optionName)
 | |
|         }
 | |
|       }
 | |
|     } else {
 | |
|       return this.find(def => def.name === arg)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   getDefault() {
 | |
|     return this.find(def => def.defaultOption === true)
 | |
|   }
 | |
| 
 | |
|   isGrouped() {
 | |
|     return this.some(def => def.group)
 | |
|   }
 | |
| 
 | |
|   whereGrouped() {
 | |
|     return this.filter(containsValidGroup)
 | |
|   }
 | |
| 
 | |
|   whereNotGrouped() {
 | |
|     return this.filter(def => !containsValidGroup(def))
 | |
|   }
 | |
| 
 | |
|   whereDefaultValueSet() {
 | |
|     return this.filter(def => t.isDefined(def.defaultValue))
 | |
|   }
 | |
| 
 | |
|   static from(definitions, caseInsensitive) {
 | |
|     if (definitions instanceof this) return definitions
 | |
|     const result = super.from(arrayify(definitions), def => OptionDefinition.create(def));
 | |
|     result.validate(caseInsensitive);
 | |
|     return result
 | |
|   }
 | |
| }
 | |
| 
 | |
| function halt(name, message) {
 | |
|   const err = new Error(message);
 | |
|   err.name = name;
 | |
|   throw err
 | |
| }
 | |
| 
 | |
| function containsValidGroup(def) {
 | |
|   return arrayify(def.group).some(group => group)
 | |
| }
 | |
| 
 | |
| function hasDuplicates(array) {
 | |
|   const items = {};
 | |
|   for (let i = 0; i < array.length; i++) {
 | |
|     const value = array[i];
 | |
|     if (items[value]) {
 | |
|       return true
 | |
|     } else {
 | |
|       if (t.isDefined(value)) items[value] = true;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @module argv-parser
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @alias module:argv-parser
 | |
|  */
 | |
| class ArgvParser {
 | |
|   /**
 | |
|    * @param {OptionDefinitions} - Definitions array
 | |
|    * @param {object} [options] - Options
 | |
|    * @param {string[]} [options.argv] - Overrides `process.argv`
 | |
|    * @param {boolean} [options.stopAtFirstUnknown] -
 | |
|    * @param {boolean} [options.caseInsensitive] - Arguments will be parsed in a case insensitive manner. Defaults to false.
 | |
|    */
 | |
|   constructor(definitions, options) {
 | |
|     this.options = Object.assign({}, options);
 | |
|     /**
 | |
|      * Option Definitions
 | |
|      */
 | |
|     this.definitions = Definitions.from(definitions, this.options.caseInsensitive);
 | |
| 
 | |
|     /**
 | |
|      * Argv
 | |
|      */
 | |
|     this.argv = ArgvArray.from(this.options.argv);
 | |
|     if (this.argv.hasCombinedShortOptions()) {
 | |
|       findReplace(this.argv, re.combinedShort.test.bind(re.combinedShort), arg => {
 | |
|         arg = arg.slice(1);
 | |
|         return arg.split('').map(letter => ({ origArg: `-${arg}`, arg: '-' + letter }))
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Yields one `{ event, name, value, arg, def }` argInfo object for each arg in `process.argv` (or `options.argv`).
 | |
|    */
 | |
|   *[Symbol.iterator]() {
 | |
|     const definitions = this.definitions;
 | |
| 
 | |
|     let def;
 | |
|     let value;
 | |
|     let name;
 | |
|     let event;
 | |
|     let singularDefaultSet = false;
 | |
|     let unknownFound = false;
 | |
|     let origArg;
 | |
| 
 | |
|     for (let arg of this.argv) {
 | |
|       if (t.isPlainObject(arg)) {
 | |
|         origArg = arg.origArg;
 | |
|         arg = arg.arg;
 | |
|       }
 | |
| 
 | |
|       if (unknownFound && this.options.stopAtFirstUnknown) {
 | |
|         yield { event: 'unknown_value', arg, name: '_unknown', value: undefined };
 | |
|         continue
 | |
|       }
 | |
| 
 | |
|       /* handle long or short option */
 | |
|       if (isOption(arg)) {
 | |
|         def = definitions.get(arg, this.options.caseInsensitive);
 | |
|         value = undefined;
 | |
|         if (def) {
 | |
|           value = def.isBoolean() ? true : null;
 | |
|           event = 'set';
 | |
|         } else {
 | |
|           event = 'unknown_option';
 | |
|         }
 | |
| 
 | |
|         /* handle --option-value notation */
 | |
|       } else if (isOptionEqualsNotation(arg)) {
 | |
|         const matches = arg.match(re.optEquals);
 | |
|         def = definitions.get(matches[1], this.options.caseInsensitive);
 | |
|         if (def) {
 | |
|           if (def.isBoolean()) {
 | |
|             yield { event: 'unknown_value', arg, name: '_unknown', value, def };
 | |
|             event = 'set';
 | |
|             value = true;
 | |
|           } else {
 | |
|             event = 'set';
 | |
|             value = matches[2];
 | |
|           }
 | |
|         } else {
 | |
|           event = 'unknown_option';
 | |
|         }
 | |
| 
 | |
|         /* handle value */
 | |
|       } else if (isValue(arg)) {
 | |
|         if (def) {
 | |
|           value = arg;
 | |
|           event = 'set';
 | |
|         } else {
 | |
|           /* get the defaultOption */
 | |
|           def = this.definitions.getDefault();
 | |
|           if (def && !singularDefaultSet) {
 | |
|             value = arg;
 | |
|             event = 'set';
 | |
|           } else {
 | |
|             event = 'unknown_value';
 | |
|             def = undefined;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       name = def ? def.name : '_unknown';
 | |
|       const argInfo = { event, arg, name, value, def };
 | |
|       if (origArg) {
 | |
|         argInfo.subArg = arg;
 | |
|         argInfo.arg = origArg;
 | |
|       }
 | |
|       yield argInfo;
 | |
| 
 | |
|       /* unknownFound logic */
 | |
|       if (name === '_unknown') unknownFound = true;
 | |
| 
 | |
|       /* singularDefaultSet logic */
 | |
|       if (def && def.defaultOption && !def.isMultiple() && event === 'set') singularDefaultSet = true;
 | |
| 
 | |
|       /* reset values once consumed and yielded */
 | |
|       if (def && def.isBoolean()) def = undefined;
 | |
|       /* reset the def if it's a singular which has been set */
 | |
|       if (def && !def.multiple && t.isDefined(value) && value !== null) {
 | |
|         def = undefined;
 | |
|       }
 | |
|       value = undefined;
 | |
|       event = undefined;
 | |
|       name = undefined;
 | |
|       origArg = undefined;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| const _value = new WeakMap();
 | |
| 
 | |
| /**
 | |
|  * Encapsulates behaviour (defined by an OptionDefinition) when setting values
 | |
|  */
 | |
| class Option {
 | |
|   constructor(definition) {
 | |
|     this.definition = new OptionDefinition(definition);
 | |
|     this.state = null; /* set or default */
 | |
|     this.resetToDefault();
 | |
|   }
 | |
| 
 | |
|   get() {
 | |
|     return _value.get(this)
 | |
|   }
 | |
| 
 | |
|   set(val) {
 | |
|     this._set(val, 'set');
 | |
|   }
 | |
| 
 | |
|   _set(val, state) {
 | |
|     const def = this.definition;
 | |
|     if (def.isMultiple()) {
 | |
|       /* don't add null or undefined to a multiple */
 | |
|       if (val !== null && val !== undefined) {
 | |
|         const arr = this.get();
 | |
|         if (this.state === 'default') arr.length = 0;
 | |
|         arr.push(def.type(val));
 | |
|         this.state = state;
 | |
|       }
 | |
|     } else {
 | |
|       /* throw if already set on a singlar defaultOption */
 | |
|       if (!def.isMultiple() && this.state === 'set') {
 | |
|         const err = new Error(`Singular option already set [${this.definition.name}=${this.get()}]`);
 | |
|         err.name = 'ALREADY_SET';
 | |
|         err.value = val;
 | |
|         err.optionName = def.name;
 | |
|         throw err
 | |
|       } else if (val === null || val === undefined) {
 | |
|         _value.set(this, val);
 | |
|         // /* required to make 'partial: defaultOption with value equal to defaultValue 2' pass */
 | |
|         // if (!(def.defaultOption && !def.isMultiple())) {
 | |
|         //   this.state = state
 | |
|         // }
 | |
|       } else {
 | |
|         _value.set(this, def.type(val));
 | |
|         this.state = state;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   resetToDefault() {
 | |
|     if (t.isDefined(this.definition.defaultValue)) {
 | |
|       if (this.definition.isMultiple()) {
 | |
|         _value.set(this, arrayify(this.definition.defaultValue).slice());
 | |
|       } else {
 | |
|         _value.set(this, this.definition.defaultValue);
 | |
|       }
 | |
|     } else {
 | |
|       if (this.definition.isMultiple()) {
 | |
|         _value.set(this, []);
 | |
|       } else {
 | |
|         _value.set(this, null);
 | |
|       }
 | |
|     }
 | |
|     this.state = 'default';
 | |
|   }
 | |
| 
 | |
|   static create(definition) {
 | |
|     definition = new OptionDefinition(definition);
 | |
|     if (definition.isBoolean()) {
 | |
|       return FlagOption.create(definition)
 | |
|     } else {
 | |
|       return new this(definition)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class FlagOption extends Option {
 | |
|   set(val) {
 | |
|     super.set(true);
 | |
|   }
 | |
| 
 | |
|   static create(def) {
 | |
|     return new this(def)
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A map of { DefinitionNameString: Option }. By default, an Output has an `_unknown` property and any options with defaultValues.
 | |
|  */
 | |
| class Output extends Map {
 | |
|   constructor(definitions) {
 | |
|     super();
 | |
|     /**
 | |
|      * @type {OptionDefinitions}
 | |
|      */
 | |
|     this.definitions = Definitions.from(definitions);
 | |
| 
 | |
|     /* by default, an Output has an `_unknown` property and any options with defaultValues */
 | |
|     this.set('_unknown', Option.create({ name: '_unknown', multiple: true }));
 | |
|     for (const def of this.definitions.whereDefaultValueSet()) {
 | |
|       this.set(def.name, Option.create(def));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   toObject(options) {
 | |
|     options = options || {};
 | |
|     const output = {};
 | |
|     for (const item of this) {
 | |
|       const name = item[0];
 | |
|       const option = item[1];
 | |
|       if (name === '_unknown' && !option.get().length) continue
 | |
|       output[name] = option.get();
 | |
|     }
 | |
| 
 | |
|     if (options.skipUnknown) delete output._unknown;
 | |
|     return output
 | |
|   }
 | |
| }
 | |
| 
 | |
| class GroupedOutput extends Output {
 | |
|   toObject(options) {
 | |
|     const superOutputNoCamel = super.toObject({ skipUnknown: options.skipUnknown });
 | |
|     const superOutput = super.toObject(options);
 | |
|     const unknown = superOutput._unknown;
 | |
|     delete superOutput._unknown;
 | |
|     const grouped = {
 | |
|       _all: superOutput
 | |
|     };
 | |
|     if (unknown && unknown.length) grouped._unknown = unknown;
 | |
| 
 | |
|     this.definitions.whereGrouped().forEach(def => {
 | |
|       const name = def.name;
 | |
|       const outputValue = superOutputNoCamel[def.name];
 | |
|       for (const groupName of arrayify(def.group)) {
 | |
|         grouped[groupName] = grouped[groupName] || {};
 | |
|         if (t.isDefined(outputValue)) {
 | |
|           grouped[groupName][name] = outputValue;
 | |
|         }
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     this.definitions.whereNotGrouped().forEach(def => {
 | |
|       const name = def.name;
 | |
|       const outputValue = superOutputNoCamel[def.name];
 | |
|       if (t.isDefined(outputValue)) {
 | |
|         if (!grouped._none) grouped._none = {};
 | |
|         grouped._none[name] = outputValue;
 | |
|       }
 | |
|     });
 | |
|     return grouped
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @module command-line-args
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Returns an object containing all option values set on the command line. By default it parses the global  [`process.argv`](https://nodejs.org/api/process.html#process_process_argv) array.
 | |
|  *
 | |
|  * Parsing is strict by default - an exception is thrown if the user sets a singular option more than once or sets an unknown value or option (one without a valid [definition](https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md)). To be more permissive, enabling [partial](https://github.com/75lb/command-line-args/wiki/Partial-mode-example) or [stopAtFirstUnknown](https://github.com/75lb/command-line-args/wiki/stopAtFirstUnknown) modes will return known options in the usual manner while collecting unknown arguments in a separate `_unknown` property.
 | |
|  *
 | |
|  * @param {Array<OptionDefinition>} - An array of [OptionDefinition](https://github.com/75lb/command-line-args/blob/master/doc/option-definition.md) objects
 | |
|  * @param {object} [options] - Options.
 | |
|  * @param {string[]} [options.argv] - An array of strings which, if present will be parsed instead  of `process.argv`.
 | |
|  * @param {boolean} [options.partial] - If `true`, an array of unknown arguments is returned in the `_unknown` property of the output.
 | |
|  * @param {boolean} [options.stopAtFirstUnknown] - If `true`, parsing will stop at the first unknown argument and the remaining arguments returned in `_unknown`. When set, `partial: true` is also implied.
 | |
|  * @param {boolean} [options.caseInsensitive] - If `true`, the case of each option name or alias parsed is insignificant. In other words, both `--Verbose` and `--verbose`, `-V` and `-v` would be equivalent. Defaults to false.
 | |
|  * @returns {object}
 | |
|  * @throws `UNKNOWN_OPTION` If `options.partial` is false and the user set an undefined option. The `err.optionName` property contains the arg that specified an unknown option, e.g. `--one`.
 | |
|  * @throws `UNKNOWN_VALUE` If `options.partial` is false and the user set a value unaccounted for by an option definition. The `err.value` property contains the unknown value, e.g. `5`.
 | |
|  * @throws `ALREADY_SET` If a user sets a singular, non-multiple option more than once. The `err.optionName` property contains the option name that has already been set, e.g. `one`.
 | |
|  * @throws `INVALID_DEFINITIONS`
 | |
|  *   - If an option definition is missing the required `name` property
 | |
|  *   - If an option definition has a `type` value that's not a function
 | |
|  *   - If an alias is numeric, a hyphen or a length other than 1
 | |
|  *   - If an option definition name was used more than once
 | |
|  *   - If an option definition alias was used more than once
 | |
|  *   - If more than one option definition has `defaultOption: true`
 | |
|  *   - If a `Boolean` option is also set as the `defaultOption`.
 | |
|  * @alias module:command-line-args
 | |
|  */
 | |
| function commandLineArgs(optionDefinitions, options) {
 | |
|   options = options || {};
 | |
|   if (options.stopAtFirstUnknown) options.partial = true;
 | |
|   optionDefinitions = Definitions.from(optionDefinitions, options.caseInsensitive);
 | |
| 
 | |
|   const parser = new ArgvParser(optionDefinitions, {
 | |
|     argv: options.argv,
 | |
|     stopAtFirstUnknown: options.stopAtFirstUnknown,
 | |
|     caseInsensitive: options.caseInsensitive
 | |
|   });
 | |
| 
 | |
|   const OutputClass = optionDefinitions.isGrouped() ? GroupedOutput : Output;
 | |
|   const output = new OutputClass(optionDefinitions);
 | |
| 
 | |
|   /* Iterate the parser setting each known value to the output. Optionally, throw on unknowns. */
 | |
|   for (const argInfo of parser) {
 | |
|     const arg = argInfo.subArg || argInfo.arg;
 | |
|     if (!options.partial) {
 | |
|       if (argInfo.event === 'unknown_value') {
 | |
|         const err = new Error(`Unknown value: ${arg}`);
 | |
|         err.name = 'UNKNOWN_VALUE';
 | |
|         err.value = arg;
 | |
|         throw err
 | |
|       } else if (argInfo.event === 'unknown_option') {
 | |
|         const err = new Error(`Unknown option: ${arg}`);
 | |
|         err.name = 'UNKNOWN_OPTION';
 | |
|         err.optionName = arg;
 | |
|         throw err
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let option;
 | |
|     if (output.has(argInfo.name)) {
 | |
|       option = output.get(argInfo.name);
 | |
|     } else {
 | |
|       option = Option.create(argInfo.def);
 | |
|       output.set(argInfo.name, option);
 | |
|     }
 | |
| 
 | |
|     if (argInfo.name === '_unknown') {
 | |
|       option.set(arg);
 | |
|     } else {
 | |
|       option.set(argInfo.value);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return output.toObject({ skipUnknown: !options.partial })
 | |
| }
 | |
| 
 | |
| module.exports = commandLineArgs; |