UNPKG

@jil/args

Version:

A convention based argument parsing and formatting library, with strict validation checks

219 lines 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = void 0; const Checker_1 = require("./Checker"); const constants_1 = require("./constants"); const debug_1 = require("./debug"); const errors_1 = require("./errors"); const castValue_1 = require("./utils/castValue"); const createScope_1 = require("./utils/createScope"); const expandShortOption_1 = require("./utils/expandShortOption"); const formatValue_1 = require("./utils/formatValue"); const getDefaultValue_1 = require("./utils/getDefaultValue"); const isCommand_1 = require("./utils/isCommand"); const isLongOption_1 = require("./utils/isLongOption"); const isOptionLike_1 = require("./utils/isOptionLike"); const isShortOption_1 = require("./utils/isShortOption"); const isShortOptionGroup_1 = require("./utils/isShortOptionGroup"); const mapParserOptions_1 = require("./utils/mapParserOptions"); const processShortOptionGroup_1 = require("./utils/processShortOptionGroup"); // TERMINOLOGY // command line - The entire line that encompasses the following parts. // arg - Each type of argument (or part) passed on the command line, separated by a space. // command - An optional "command" being ran that allows for branching functionality. // Sub-commands are separated with ":". // option - An optional argument that requires a value(s). Starts with "--" (long) or "-" (short). // flag - A specialized option that only supports booleans. Can be toggled on an off (default). // param - An optional or required argument, that is not an option or option value, // Supports any raw value, and enforces a defined order. // rest - All remaining arguments that appear after a stand alone "--". // Usually passed to subsequent scripts. // scope - Argument currently being parsed. // FEATURES // Short name - A short name (single character) for an existing option or flag: --verbose, -v // Option grouping - When multiple short options are passed under a single option: -abc // Inline values - Option values that are immediately set using an equals sign: --foo=bar // Group count - Increment a number each time a short option is found in a group: -vvv // Arity count - Required number of argument values to consume for multiples. // Choices - List of valid values to choose from. Errors otherwise. /** * Parse a list of command line arguments (typically from `process.argv`) into an arguments * object. Will extract commands, options, flags, and params based on the defined parser options. */ function parse(argv, parserOptions) { const { commands: commandConfigs = [], loose: looseMode = false, options: optionConfigs, params: paramConfigs = [], unknown: allowUnknown = false, variadic: allowVariadic = true, } = parserOptions; const checker = new Checker_1.Checker(optionConfigs); const options = {}; const params = []; const rest = []; const unknown = {}; const mapping = {}; let command = ''; let currentScope = null; (0, debug_1.debug)('Parsing arguments: %s', argv.join(' ')); function commitScope() { if (!currentScope) { return; } const { name, value, finalValue } = currentScope; // Support loose mode if (looseMode) { if (value === undefined) { options[name] = !currentScope.negated; } else { options[name] = finalValue; } // Set an unknown value } else if (currentScope.unknown) { if (allowUnknown) { unknown[name] = value === undefined ? constants_1.DEFAULT_STRING_VALUE : String(finalValue); } // Set and cast value if defined } else if (value !== undefined) { options[name] = finalValue; } currentScope = null; } // Run validations and map defaults checker.validateParamOrder(paramConfigs); (0, mapParserOptions_1.mapParserOptions)(parserOptions, options, params, { onCommand(cmd) { checker.validateCommandFormat(cmd); }, onOption(config, value, name) { const { short } = config; if (short) { checker.validateUniqueShortName(name, short, mapping); mapping[short] = name; } options[name] = (0, getDefaultValue_1.getDefaultValue)(config); checker.validateDefaultValue(name, options[name], config); checker.validateNumberCount(name, config); }, onParam(config) { checker.validateRequiredParamNoDefault(config); }, }); // Process each argument for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; checker.arg = arg; checker.argIndex = i; // Rest arguments found, extract remaining and exit if (arg === '--') { rest.push(...argv.slice(i + 1)); break; } try { // Options if ((0, isOptionLike_1.isOptionLike)(arg)) { let optionName = arg; let inlineValue; // Commit previous scope commitScope(); // Extract option and inline value if (optionName.includes('=')) { [optionName, inlineValue] = optionName.split('=', 2); } // Short option group "-frl" if ((0, isShortOptionGroup_1.isShortOptionGroup)(optionName)) { checker.checkNoInlineValue(inlineValue); (0, processShortOptionGroup_1.processShortOptionGroup)(optionName.slice(1), optionConfigs, options, mapping, looseMode); continue; // Short option "-f" } else if ((0, isShortOption_1.isShortOption)(optionName)) { optionName = (0, expandShortOption_1.expandShortOption)(optionName.slice(1), mapping, looseMode); // Long option "--foo" } else if ((0, isLongOption_1.isLongOption)(optionName)) { optionName = optionName.slice(2); } // Parse and create next scope const scope = (0, createScope_1.createScope)(optionName, optionConfigs, options); // Unknown option found, handle accordingly if (scope.unknown && !allowUnknown && !looseMode) { checker.checkUnknownOption(arg); // Flag found, so set value immediately and discard scope } else if (scope.flag) { options[scope.name] = !scope.negated; checker.checkNoInlineValue(inlineValue); // Otherwise keep scope open, to capture next value } else { currentScope = scope; // Update scope value if an inline value exists if (inlineValue !== undefined) { currentScope.captureValue(inlineValue, commitScope); } } // Option values } else if (currentScope) { currentScope.captureValue(arg, commitScope); // Commands } else if ((0, isCommand_1.isCommand)(arg, commandConfigs)) { checker.checkCommandOrder(arg, command, params.length); if (!command) { command = arg; } // Params } else if (paramConfigs[params.length]) { const config = paramConfigs[params.length]; params.push((0, formatValue_1.formatValue)((0, castValue_1.castValue)(arg, config.type), config.format)); } else if (allowVariadic) { params.push(arg); } else { throw new errors_1.ArgsError('PARAM_UNKNOWN', [arg]); } } catch (error) { currentScope = null; checker.logFailure(error.message); } } // Commit final scope commitScope(); // Fill missing params for (let i = params.length; i < paramConfigs.length; i += 1) { const config = paramConfigs[i]; if (config.required) { break; } params.push((0, getDefaultValue_1.getDefaultValue)(config)); } // Run final checks (0, mapParserOptions_1.mapParserOptions)(parserOptions, options, params, { onOption(config, value, name) { checker.validateParsedOption(name, config, value); checker.validateArityIsMet(name, config, value); checker.validateChoiceIsMet(name, config, value); // Since default values avoid scope, // they are not cast. Do it manually after parsing. if (value === (0, getDefaultValue_1.getDefaultValue)(config)) { options[name] = (0, castValue_1.castValue)(value, config.type, config.multiple); } }, onParam(config, value) { checker.validateParsedParam(config, value); }, }); return { command: command === '' ? [] : command.split(':'), errors: [...checker.parseErrors, ...checker.validationErrors], options: options, params: params, rest, unknown, }; } exports.parse = parse; //# sourceMappingURL=parse.js.map