1'use strict'; 2 3const { 4 ArrayPrototypeFind, 5 ObjectEntries, 6 ObjectPrototypeHasOwnProperty: ObjectHasOwn, 7 StringPrototypeCharAt, 8 StringPrototypeIncludes, 9 StringPrototypeStartsWith, 10} = require('./internal/primordials'); 11 12const { 13 validateObject, 14} = require('./internal/validators'); 15 16// These are internal utilities to make the parsing logic easier to read, and 17// add lots of detail for the curious. They are in a separate file to allow 18// unit testing, although that is not essential (this could be rolled into 19// main file and just tested implicitly via API). 20// 21// These routines are for internal use, not for export to client. 22 23/** 24 * Return the named property, but only if it is an own property. 25 */ 26function objectGetOwn(obj, prop) { 27 if (ObjectHasOwn(obj, prop)) 28 return obj[prop]; 29} 30 31/** 32 * Return the named options property, but only if it is an own property. 33 */ 34function optionsGetOwn(options, longOption, prop) { 35 if (ObjectHasOwn(options, longOption)) 36 return objectGetOwn(options[longOption], prop); 37} 38 39/** 40 * Determines if the argument may be used as an option value. 41 * @example 42 * isOptionValue('V') // returns true 43 * isOptionValue('-v') // returns true (greedy) 44 * isOptionValue('--foo') // returns true (greedy) 45 * isOptionValue(undefined) // returns false 46 */ 47function isOptionValue(value) { 48 if (value == null) return false; 49 50 // Open Group Utility Conventions are that an option-argument 51 // is the argument after the option, and may start with a dash. 52 return true; // greedy! 53} 54 55/** 56 * Detect whether there is possible confusion and user may have omitted 57 * the option argument, like `--port --verbose` when `port` of type:string. 58 * In strict mode we throw errors if value is option-like. 59 */ 60function isOptionLikeValue(value) { 61 if (value == null) return false; 62 63 return value.length > 1 && StringPrototypeCharAt(value, 0) === '-'; 64} 65 66/** 67 * Determines if `arg` is just a short option. 68 * @example '-f' 69 */ 70function isLoneShortOption(arg) { 71 return arg.length === 2 && 72 StringPrototypeCharAt(arg, 0) === '-' && 73 StringPrototypeCharAt(arg, 1) !== '-'; 74} 75 76/** 77 * Determines if `arg` is a lone long option. 78 * @example 79 * isLoneLongOption('a') // returns false 80 * isLoneLongOption('-a') // returns false 81 * isLoneLongOption('--foo') // returns true 82 * isLoneLongOption('--foo=bar') // returns false 83 */ 84function isLoneLongOption(arg) { 85 return arg.length > 2 && 86 StringPrototypeStartsWith(arg, '--') && 87 !StringPrototypeIncludes(arg, '=', 3); 88} 89 90/** 91 * Determines if `arg` is a long option and value in the same argument. 92 * @example 93 * isLongOptionAndValue('--foo') // returns false 94 * isLongOptionAndValue('--foo=bar') // returns true 95 */ 96function isLongOptionAndValue(arg) { 97 return arg.length > 2 && 98 StringPrototypeStartsWith(arg, '--') && 99 StringPrototypeIncludes(arg, '=', 3); 100} 101 102/** 103 * Determines if `arg` is a short option group. 104 * 105 * See Guideline 5 of the [Open Group Utility Conventions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). 106 * One or more options without option-arguments, followed by at most one 107 * option that takes an option-argument, should be accepted when grouped 108 * behind one '-' delimiter. 109 * @example 110 * isShortOptionGroup('-a', {}) // returns false 111 * isShortOptionGroup('-ab', {}) // returns true 112 * // -fb is an option and a value, not a short option group 113 * isShortOptionGroup('-fb', { 114 * options: { f: { type: 'string' } } 115 * }) // returns false 116 * isShortOptionGroup('-bf', { 117 * options: { f: { type: 'string' } } 118 * }) // returns true 119 * // -bfb is an edge case, return true and caller sorts it out 120 * isShortOptionGroup('-bfb', { 121 * options: { f: { type: 'string' } } 122 * }) // returns true 123 */ 124function isShortOptionGroup(arg, options) { 125 if (arg.length <= 2) return false; 126 if (StringPrototypeCharAt(arg, 0) !== '-') return false; 127 if (StringPrototypeCharAt(arg, 1) === '-') return false; 128 129 const firstShort = StringPrototypeCharAt(arg, 1); 130 const longOption = findLongOptionForShort(firstShort, options); 131 return optionsGetOwn(options, longOption, 'type') !== 'string'; 132} 133 134/** 135 * Determine if arg is a short string option followed by its value. 136 * @example 137 * isShortOptionAndValue('-a', {}); // returns false 138 * isShortOptionAndValue('-ab', {}); // returns false 139 * isShortOptionAndValue('-fFILE', { 140 * options: { foo: { short: 'f', type: 'string' }} 141 * }) // returns true 142 */ 143function isShortOptionAndValue(arg, options) { 144 validateObject(options, 'options'); 145 146 if (arg.length <= 2) return false; 147 if (StringPrototypeCharAt(arg, 0) !== '-') return false; 148 if (StringPrototypeCharAt(arg, 1) === '-') return false; 149 150 const shortOption = StringPrototypeCharAt(arg, 1); 151 const longOption = findLongOptionForShort(shortOption, options); 152 return optionsGetOwn(options, longOption, 'type') === 'string'; 153} 154 155/** 156 * Find the long option associated with a short option. Looks for a configured 157 * `short` and returns the short option itself if a long option is not found. 158 * @example 159 * findLongOptionForShort('a', {}) // returns 'a' 160 * findLongOptionForShort('b', { 161 * options: { bar: { short: 'b' } } 162 * }) // returns 'bar' 163 */ 164function findLongOptionForShort(shortOption, options) { 165 validateObject(options, 'options'); 166 const longOptionEntry = ArrayPrototypeFind( 167 ObjectEntries(options), 168 ({ 1: optionConfig }) => objectGetOwn(optionConfig, 'short') === shortOption 169 ); 170 return longOptionEntry?.[0] ?? shortOption; 171} 172 173/** 174 * Check if the given option includes a default value 175 * and that option has not been set by the input args. 176 * 177 * @param {string} longOption - long option name e.g. 'foo' 178 * @param {object} optionConfig - the option configuration properties 179 * @param {object} values - option values returned in `values` by parseArgs 180 */ 181function useDefaultValueOption(longOption, optionConfig, values) { 182 return objectGetOwn(optionConfig, 'default') !== undefined && 183 values[longOption] === undefined; 184} 185 186module.exports = { 187 findLongOptionForShort, 188 isLoneLongOption, 189 isLoneShortOption, 190 isLongOptionAndValue, 191 isOptionValue, 192 isOptionLikeValue, 193 isShortOptionAndValue, 194 isShortOptionGroup, 195 useDefaultValueOption, 196 objectGetOwn, 197 optionsGetOwn, 198}; 199