basant307/AI_Governance_Project
045
1'use strict';2 3const {4 ArrayPrototypeForEach,5 ArrayPrototypeIncludes,6 ArrayPrototypeMap,7 ArrayPrototypePush,8 ArrayPrototypePushApply,9 ArrayPrototypeShift,10 ArrayPrototypeSlice,11 ArrayPrototypeUnshiftApply,12 ObjectEntries,13 ObjectPrototypeHasOwnProperty: ObjectHasOwn,14 StringPrototypeCharAt,15 StringPrototypeIndexOf,16 StringPrototypeSlice,17 StringPrototypeStartsWith,18} = require('./internal/primordials');19 20const {21 validateArray,22 validateBoolean,23 validateBooleanArray,24 validateObject,25 validateString,26 validateStringArray,27 validateUnion,28} = require('./internal/validators');29 30const {31 kEmptyObject,32} = require('./internal/util');33 34const {35 findLongOptionForShort,36 isLoneLongOption,37 isLoneShortOption,38 isLongOptionAndValue,39 isOptionValue,40 isOptionLikeValue,41 isShortOptionAndValue,42 isShortOptionGroup,43 useDefaultValueOption,44 objectGetOwn,45 optionsGetOwn,46} = require('./utils');47 48const {49 codes: {50 ERR_INVALID_ARG_VALUE,51 ERR_PARSE_ARGS_INVALID_OPTION_VALUE,52 ERR_PARSE_ARGS_UNKNOWN_OPTION,53 ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL,54 },55} = require('./internal/errors');56 57function getMainArgs() {58 // Work out where to slice process.argv for user supplied arguments.59 60 // Check node options for scenarios where user CLI args follow executable.61 const execArgv = process.execArgv;62 if (ArrayPrototypeIncludes(execArgv, '-e') ||63 ArrayPrototypeIncludes(execArgv, '--eval') ||64 ArrayPrototypeIncludes(execArgv, '-p') ||65 ArrayPrototypeIncludes(execArgv, '--print')) {66 return ArrayPrototypeSlice(process.argv, 1);67 }68 69 // Normally first two arguments are executable and script, then CLI arguments70 return ArrayPrototypeSlice(process.argv, 2);71}72 73/**74 * In strict mode, throw for possible usage errors like --foo --bar75 *76 * @param {object} token - from tokens as available from parseArgs77 */78function checkOptionLikeValue(token) {79 if (!token.inlineValue && isOptionLikeValue(token.value)) {80 // Only show short example if user used short option.81 const example = StringPrototypeStartsWith(token.rawName, '--') ?82 `'${token.rawName}=-XYZ'` :83 `'--${token.name}=-XYZ' or '${token.rawName}-XYZ'`;84 const errorMessage = `Option '${token.rawName}' argument is ambiguous.85Did you forget to specify the option argument for '${token.rawName}'?86To specify an option argument starting with a dash use ${example}.`;87 throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage);88 }89}90 91/**92 * In strict mode, throw for usage errors.93 *94 * @param {object} config - from config passed to parseArgs95 * @param {object} token - from tokens as available from parseArgs96 */97function checkOptionUsage(config, token) {98 if (!ObjectHasOwn(config.options, token.name)) {99 throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(100 token.rawName, config.allowPositionals);101 }102 103 const short = optionsGetOwn(config.options, token.name, 'short');104 const shortAndLong = `${short ? `-${short}, ` : ''}--${token.name}`;105 const type = optionsGetOwn(config.options, token.name, 'type');106 if (type === 'string' && typeof token.value !== 'string') {107 throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong} <value>' argument missing`);108 }109 // (Idiomatic test for undefined||null, expecting undefined.)110 if (type === 'boolean' && token.value != null) {111 throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(`Option '${shortAndLong}' does not take an argument`);112 }113}114 115 116/**117 * Store the option value in `values`.118 *119 * @param {string} longOption - long option name e.g. 'foo'120 * @param {string|undefined} optionValue - value from user args121 * @param {object} options - option configs, from parseArgs({ options })122 * @param {object} values - option values returned in `values` by parseArgs123 */124function storeOption(longOption, optionValue, options, values) {125 if (longOption === '__proto__') {126 return; // No. Just no.127 }128 129 // We store based on the option value rather than option type,130 // preserving the users intent for author to deal with.131 const newValue = optionValue ?? true;132 if (optionsGetOwn(options, longOption, 'multiple')) {133 // Always store value in array, including for boolean.134 // values[longOption] starts out not present,135 // first value is added as new array [newValue],136 // subsequent values are pushed to existing array.137 // (note: values has null prototype, so simpler usage)138 if (values[longOption]) {139 ArrayPrototypePush(values[longOption], newValue);140 } else {141 values[longOption] = [newValue];142 }143 } else {144 values[longOption] = newValue;145 }146}147 148/**149 * Store the default option value in `values`.150 *151 * @param {string} longOption - long option name e.g. 'foo'152 * @param {string153 * | boolean154 * | string[]155 * | boolean[]} optionValue - default value from option config156 * @param {object} values - option values returned in `values` by parseArgs157 */158function storeDefaultOption(longOption, optionValue, values) {159 if (longOption === '__proto__') {160 return; // No. Just no.161 }162 163 values[longOption] = optionValue;164}165 166/**167 * Process args and turn into identified tokens:168 * - option (along with value, if any)169 * - positional170 * - option-terminator171 *172 * @param {string[]} args - from parseArgs({ args }) or mainArgs173 * @param {object} options - option configs, from parseArgs({ options })174 */175function argsToTokens(args, options) {176 const tokens = [];177 let index = -1;178 let groupCount = 0;179 180 const remainingArgs = ArrayPrototypeSlice(args);181 while (remainingArgs.length > 0) {182 const arg = ArrayPrototypeShift(remainingArgs);183 const nextArg = remainingArgs[0];184 if (groupCount > 0)185 groupCount--;186 else187 index++;188 189 // Check if `arg` is an options terminator.190 // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html191 if (arg === '--') {192 // Everything after a bare '--' is considered a positional argument.193 ArrayPrototypePush(tokens, { kind: 'option-terminator', index });194 ArrayPrototypePushApply(195 tokens, ArrayPrototypeMap(remainingArgs, (arg) => {196 return { kind: 'positional', index: ++index, value: arg };197 })198 );199 break; // Finished processing args, leave while loop.200 }201 202 if (isLoneShortOption(arg)) {203 // e.g. '-f'204 const shortOption = StringPrototypeCharAt(arg, 1);205 const longOption = findLongOptionForShort(shortOption, options);206 let value;207 let inlineValue;208 if (optionsGetOwn(options, longOption, 'type') === 'string' &&209 isOptionValue(nextArg)) {210 // e.g. '-f', 'bar'211 value = ArrayPrototypeShift(remainingArgs);212 inlineValue = false;213 }214 ArrayPrototypePush(215 tokens,216 { kind: 'option', name: longOption, rawName: arg,217 index, value, inlineValue });218 if (value != null) ++index;219 continue;220 }221 222 if (isShortOptionGroup(arg, options)) {223 // Expand -fXzy to -f -X -z -y224 const expanded = [];225 for (let index = 1; index < arg.length; index++) {226 const shortOption = StringPrototypeCharAt(arg, index);227 const longOption = findLongOptionForShort(shortOption, options);228 if (optionsGetOwn(options, longOption, 'type') !== 'string' ||229 index === arg.length - 1) {230 // Boolean option, or last short in group. Well formed.231 ArrayPrototypePush(expanded, `-${shortOption}`);232 } else {233 // String option in middle. Yuck.234 // Expand -abfFILE to -a -b -fFILE235 ArrayPrototypePush(expanded, `-${StringPrototypeSlice(arg, index)}`);236 break; // finished short group237 }238 }239 ArrayPrototypeUnshiftApply(remainingArgs, expanded);240 groupCount = expanded.length;241 continue;242 }243 244 if (isShortOptionAndValue(arg, options)) {245 // e.g. -fFILE246 const shortOption = StringPrototypeCharAt(arg, 1);247 const longOption = findLongOptionForShort(shortOption, options);248 const value = StringPrototypeSlice(arg, 2);249 ArrayPrototypePush(250 tokens,251 { kind: 'option', name: longOption, rawName: `-${shortOption}`,252 index, value, inlineValue: true });253 continue;254 }255 256 if (isLoneLongOption(arg)) {257 // e.g. '--foo'258 const longOption = StringPrototypeSlice(arg, 2);259 let value;260 let inlineValue;261 if (optionsGetOwn(options, longOption, 'type') === 'string' &&262 isOptionValue(nextArg)) {263 // e.g. '--foo', 'bar'264 value = ArrayPrototypeShift(remainingArgs);265 inlineValue = false;266 }267 ArrayPrototypePush(268 tokens,269 { kind: 'option', name: longOption, rawName: arg,270 index, value, inlineValue });271 if (value != null) ++index;272 continue;273 }274 275 if (isLongOptionAndValue(arg)) {276 // e.g. --foo=bar277 const equalIndex = StringPrototypeIndexOf(arg, '=');278 const longOption = StringPrototypeSlice(arg, 2, equalIndex);279 const value = StringPrototypeSlice(arg, equalIndex + 1);280 ArrayPrototypePush(281 tokens,282 { kind: 'option', name: longOption, rawName: `--${longOption}`,283 index, value, inlineValue: true });284 continue;285 }286 287 ArrayPrototypePush(tokens, { kind: 'positional', index, value: arg });288 }289 290 return tokens;291}292 293const parseArgs = (config = kEmptyObject) => {294 const args = objectGetOwn(config, 'args') ?? getMainArgs();295 const strict = objectGetOwn(config, 'strict') ?? true;296 const allowPositionals = objectGetOwn(config, 'allowPositionals') ?? !strict;297 const returnTokens = objectGetOwn(config, 'tokens') ?? false;298 const options = objectGetOwn(config, 'options') ?? { __proto__: null };299 // Bundle these up for passing to strict-mode checks.300 const parseConfig = { args, strict, options, allowPositionals };301 302 // Validate input configuration.303 validateArray(args, 'args');304 validateBoolean(strict, 'strict');305 validateBoolean(allowPositionals, 'allowPositionals');306 validateBoolean(returnTokens, 'tokens');307 validateObject(options, 'options');308 ArrayPrototypeForEach(309 ObjectEntries(options),310 ({ 0: longOption, 1: optionConfig }) => {311 validateObject(optionConfig, `options.${longOption}`);312 313 // type is required314 const optionType = objectGetOwn(optionConfig, 'type');315 validateUnion(optionType, `options.${longOption}.type`, ['string', 'boolean']);316 317 if (ObjectHasOwn(optionConfig, 'short')) {318 const shortOption = optionConfig.short;319 validateString(shortOption, `options.${longOption}.short`);320 if (shortOption.length !== 1) {321 throw new ERR_INVALID_ARG_VALUE(322 `options.${longOption}.short`,323 shortOption,324 'must be a single character'325 );326 }327 }328 329 const multipleOption = objectGetOwn(optionConfig, 'multiple');330 if (ObjectHasOwn(optionConfig, 'multiple')) {331 validateBoolean(multipleOption, `options.${longOption}.multiple`);332 }333 334 const defaultValue = objectGetOwn(optionConfig, 'default');335 if (defaultValue !== undefined) {336 let validator;337 switch (optionType) {338 case 'string':339 validator = multipleOption ? validateStringArray : validateString;340 break;341 342 case 'boolean':343 validator = multipleOption ? validateBooleanArray : validateBoolean;344 break;345 }346 validator(defaultValue, `options.${longOption}.default`);347 }348 }349 );350 351 // Phase 1: identify tokens352 const tokens = argsToTokens(args, options);353 354 // Phase 2: process tokens into parsed option values and positionals355 const result = {356 values: { __proto__: null },357 positionals: [],358 };359 if (returnTokens) {360 result.tokens = tokens;361 }362 ArrayPrototypeForEach(tokens, (token) => {363 if (token.kind === 'option') {364 if (strict) {365 checkOptionUsage(parseConfig, token);366 checkOptionLikeValue(token);367 }368 storeOption(token.name, token.value, options, result.values);369 } else if (token.kind === 'positional') {370 if (!allowPositionals) {371 throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value);372 }373 ArrayPrototypePush(result.positionals, token.value);374 }375 });376 377 // Phase 3: fill in default values for missing args378 ArrayPrototypeForEach(ObjectEntries(options), ({ 0: longOption,379 1: optionConfig }) => {380 const mustSetDefault = useDefaultValueOption(longOption,381 optionConfig,382 result.values);383 if (mustSetDefault) {384 storeDefaultOption(longOption,385 objectGetOwn(optionConfig, 'default'),386 result.values);387 }388 });389 390 391 return result;392};393 394module.exports = {395 parseArgs,396};397 