import { primordials } from "ext:core/mod.js";
const {
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
ArrayPrototypePushApply,
ArrayPrototypeShift,
ArrayPrototypeSlice,
ArrayPrototypePush,
ArrayPrototypeUnshiftApply,
ObjectHasOwn,
ObjectEntries,
StringPrototypeCharAt,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;
import {
validateArray,
validateBoolean,
validateBooleanArray,
validateObject,
validateString,
validateStringArray,
validateUnion,
} from "ext:deno_node/internal/validators.mjs";
import {
findLongOptionForShort,
isLoneLongOption,
isLoneShortOption,
isLongOptionAndValue,
isOptionLikeValue,
isOptionValue,
isShortOptionAndValue,
isShortOptionGroup,
objectGetOwn,
optionsGetOwn,
useDefaultValueOption,
} from "ext:deno_node/internal/util/parse_args/utils.js";
import { codes } from "ext:deno_node/internal/error_codes.ts";
const {
ERR_INVALID_ARG_VALUE,
ERR_PARSE_ARGS_INVALID_OPTION_VALUE,
ERR_PARSE_ARGS_UNKNOWN_OPTION,
ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL,
} = codes;
import process from "node:process";
function getMainArgs() {
const execArgv = process.execArgv;
if (
ArrayPrototypeIncludes(execArgv, "-e") ||
ArrayPrototypeIncludes(execArgv, "--eval") ||
ArrayPrototypeIncludes(execArgv, "-p") ||
ArrayPrototypeIncludes(execArgv, "--print")
) {
return ArrayPrototypeSlice(process.argv, 1);
}
return ArrayPrototypeSlice(process.argv, 2);
}
function checkOptionLikeValue(token) {
if (!token.inlineValue && isOptionLikeValue(token.value)) {
const example = StringPrototypeStartsWith(token.rawName, "--")
? `'${token.rawName}=-XYZ'`
: `'--${token.name}=-XYZ' or '${token.rawName}-XYZ'`;
const errorMessage = `Option '${token.rawName}' argument is ambiguous.
Did you forget to specify the option argument for '${token.rawName}'?
To specify an option argument starting with a dash use ${example}.`;
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage);
}
}
function checkOptionUsage(config, token) {
if (!ObjectHasOwn(config.options, token.name)) {
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
token.rawName,
config.allowPositionals,
);
}
const short = optionsGetOwn(config.options, token.name, "short");
const shortAndLong = `${short ? `-${short}, ` : ""}--${token.name}`;
const type = optionsGetOwn(config.options, token.name, "type");
if (type === "string" && typeof token.value !== "string") {
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(
`Option '${shortAndLong} <value>' argument missing`,
);
}
if (type === "boolean" && token.value != null) {
throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(
`Option '${shortAndLong}' does not take an argument`,
);
}
}
function storeOption(longOption, optionValue, options, values) {
if (longOption === "__proto__") {
return; }
const newValue = optionValue ?? true;
if (optionsGetOwn(options, longOption, "multiple")) {
if (values[longOption]) {
ArrayPrototypePush(values[longOption], newValue);
} else {
values[longOption] = [newValue];
}
} else {
values[longOption] = newValue;
}
}
function storeDefaultOption(longOption, optionValue, values) {
if (longOption === "__proto__") {
return; }
values[longOption] = optionValue;
}
function argsToTokens(args, options) {
const tokens = [];
let index = -1;
let groupCount = 0;
const remainingArgs = ArrayPrototypeSlice(args);
while (remainingArgs.length > 0) {
const arg = ArrayPrototypeShift(remainingArgs);
const nextArg = remainingArgs[0];
if (groupCount > 0) {
groupCount--;
} else {
index++;
}
if (arg === "--") {
ArrayPrototypePush(tokens, { kind: "option-terminator", index });
ArrayPrototypePushApply(
tokens,
ArrayPrototypeMap(remainingArgs, (arg) => {
return { kind: "positional", index: ++index, value: arg };
}),
);
break; }
if (isLoneShortOption(arg)) {
const shortOption = StringPrototypeCharAt(arg, 1);
const longOption = findLongOptionForShort(shortOption, options);
let value;
let inlineValue;
if (
optionsGetOwn(options, longOption, "type") === "string" &&
isOptionValue(nextArg)
) {
value = ArrayPrototypeShift(remainingArgs);
inlineValue = false;
}
ArrayPrototypePush(
tokens,
{
kind: "option",
name: longOption,
rawName: arg,
index,
value,
inlineValue,
},
);
if (value != null) ++index;
continue;
}
if (isShortOptionGroup(arg, options)) {
const expanded = [];
for (let index = 1; index < arg.length; index++) {
const shortOption = StringPrototypeCharAt(arg, index);
const longOption = findLongOptionForShort(shortOption, options);
if (
optionsGetOwn(options, longOption, "type") !== "string" ||
index === arg.length - 1
) {
ArrayPrototypePush(expanded, `-${shortOption}`);
} else {
ArrayPrototypePush(expanded, `-${StringPrototypeSlice(arg, index)}`);
break; }
}
ArrayPrototypeUnshiftApply(remainingArgs, expanded);
groupCount = expanded.length;
continue;
}
if (isShortOptionAndValue(arg, options)) {
const shortOption = StringPrototypeCharAt(arg, 1);
const longOption = findLongOptionForShort(shortOption, options);
const value = StringPrototypeSlice(arg, 2);
ArrayPrototypePush(
tokens,
{
kind: "option",
name: longOption,
rawName: `-${shortOption}`,
index,
value,
inlineValue: true,
},
);
continue;
}
if (isLoneLongOption(arg)) {
const longOption = StringPrototypeSlice(arg, 2);
let value;
let inlineValue;
if (
optionsGetOwn(options, longOption, "type") === "string" &&
isOptionValue(nextArg)
) {
value = ArrayPrototypeShift(remainingArgs);
inlineValue = false;
}
ArrayPrototypePush(
tokens,
{
kind: "option",
name: longOption,
rawName: arg,
index,
value,
inlineValue,
},
);
if (value != null) ++index;
continue;
}
if (isLongOptionAndValue(arg)) {
const equalIndex = StringPrototypeIndexOf(arg, "=");
const longOption = StringPrototypeSlice(arg, 2, equalIndex);
const value = StringPrototypeSlice(arg, equalIndex + 1);
ArrayPrototypePush(
tokens,
{
kind: "option",
name: longOption,
rawName: `--${longOption}`,
index,
value,
inlineValue: true,
},
);
continue;
}
ArrayPrototypePush(tokens, { kind: "positional", index, value: arg });
}
return tokens;
}
export const parseArgs = (config = { __proto__: null }) => {
const args = objectGetOwn(config, "args") ?? getMainArgs();
const strict = objectGetOwn(config, "strict") ?? true;
const allowPositionals = objectGetOwn(config, "allowPositionals") ?? !strict;
const returnTokens = objectGetOwn(config, "tokens") ?? false;
const options = objectGetOwn(config, "options") ?? { __proto__: null };
const allowNegative = objectGetOwn(config, "allowNegative") ?? false;
const parseConfig = {
args,
strict,
options,
allowPositionals,
allowNegative,
};
validateArray(args, "args");
validateBoolean(strict, "strict");
validateBoolean(allowPositionals, "allowPositionals");
validateBoolean(returnTokens, "tokens");
validateObject(options, "options");
ArrayPrototypeForEach(
ObjectEntries(options),
({ 0: longOption, 1: optionConfig }) => {
validateObject(optionConfig, `options.${longOption}`);
const optionType = objectGetOwn(optionConfig, "type");
validateUnion(optionType, `options.${longOption}.type`, [
"string",
"boolean",
]);
if (ObjectHasOwn(optionConfig, "short")) {
const shortOption = optionConfig.short;
validateString(shortOption, `options.${longOption}.short`);
if (shortOption.length !== 1) {
throw new ERR_INVALID_ARG_VALUE(
`options.${longOption}.short`,
shortOption,
"must be a single character",
);
}
}
const multipleOption = objectGetOwn(optionConfig, "multiple");
if (ObjectHasOwn(optionConfig, "multiple")) {
validateBoolean(multipleOption, `options.${longOption}.multiple`);
}
const defaultValue = objectGetOwn(optionConfig, "default");
if (defaultValue !== undefined) {
let validator;
switch (optionType) {
case "string":
validator = multipleOption ? validateStringArray : validateString;
break;
case "boolean":
validator = multipleOption ? validateBooleanArray : validateBoolean;
break;
}
validator(defaultValue, `options.${longOption}.default`);
}
},
);
const tokens = argsToTokens(args, options);
const result = {
values: { __proto__: null },
positionals: [],
};
if (returnTokens) {
result.tokens = tokens;
}
ArrayPrototypeForEach(tokens, (token) => {
if (token.kind === "option") {
let name = token.name;
let value = token.value;
if (allowNegative && StringPrototypeStartsWith(token.rawName, "--no-")) {
name = StringPrototypeSlice(token.rawName, 5);
if (!ObjectHasOwn(options, name)) {
if (strict) {
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
token.rawName,
allowPositionals,
);
}
value = false;
} else if (optionsGetOwn(options, name, "type") !== "boolean") {
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
token.rawName,
allowPositionals,
);
} else {
value = false;
}
}
const checkToken = {
...token,
name: name, };
if (strict) {
checkOptionUsage(parseConfig, checkToken);
checkOptionLikeValue(checkToken);
}
storeOption(name, value, options, result.values);
} else if (token.kind === "positional") {
if (!allowPositionals) {
throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(token.value);
}
ArrayPrototypePush(result.positionals, token.value);
}
});
ArrayPrototypeForEach(
ObjectEntries(options),
({ 0: longOption, 1: optionConfig }) => {
const mustSetDefault = useDefaultValueOption(
longOption,
optionConfig,
result.values,
);
if (mustSetDefault) {
storeDefaultOption(
longOption,
objectGetOwn(optionConfig, "default"),
result.values,
);
}
},
);
return result;
};