@jil/args
Version:
A convention based argument parsing and formatting library, with strict validation checks
219 lines • 9.54 kB
JavaScript
;
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