summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal/util/parse_args/parse_args.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/polyfills/internal/util/parse_args/parse_args.js')
-rw-r--r--ext/node/polyfills/internal/util/parse_args/parse_args.js345
1 files changed, 345 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal/util/parse_args/parse_args.js b/ext/node/polyfills/internal/util/parse_args/parse_args.js
new file mode 100644
index 000000000..798039620
--- /dev/null
+++ b/ext/node/polyfills/internal/util/parse_args/parse_args.js
@@ -0,0 +1,345 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+const primordials = globalThis.__bootstrap.primordials;
+const {
+ ArrayPrototypeForEach,
+ ArrayPrototypeIncludes,
+ ArrayPrototypePushApply,
+ ArrayPrototypeShift,
+ ArrayPrototypeSlice,
+ ArrayPrototypePush,
+ ArrayPrototypeUnshiftApply,
+ ObjectHasOwn,
+ ObjectEntries,
+ StringPrototypeCharAt,
+ StringPrototypeIndexOf,
+ StringPrototypeSlice,
+} = primordials;
+
+import {
+ validateArray,
+ validateBoolean,
+ validateObject,
+ validateString,
+ validateUnion,
+} from "ext:deno_node/internal/validators.mjs";
+
+import {
+ findLongOptionForShort,
+ isLoneLongOption,
+ isLoneShortOption,
+ isLongOptionAndValue,
+ isOptionLikeValue,
+ isOptionValue,
+ isShortOptionAndValue,
+ isShortOptionGroup,
+ objectGetOwn,
+ optionsGetOwn,
+} 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;
+
+function getMainArgs() {
+ // Work out where to slice process.argv for user supplied arguments.
+
+ // Check node options for scenarios where user CLI args follow executable.
+ const execArgv = process.execArgv;
+ if (
+ ArrayPrototypeIncludes(execArgv, "-e") ||
+ ArrayPrototypeIncludes(execArgv, "--eval") ||
+ ArrayPrototypeIncludes(execArgv, "-p") ||
+ ArrayPrototypeIncludes(execArgv, "--print")
+ ) {
+ return ArrayPrototypeSlice(process.argv, 1);
+ }
+
+ // Normally first two arguments are executable and script, then CLI arguments
+ return ArrayPrototypeSlice(process.argv, 2);
+}
+
+/**
+ * In strict mode, throw for possible usage errors like --foo --bar
+ *
+ * @param {string} longOption - long option name e.g. 'foo'
+ * @param {string|undefined} optionValue - value from user args
+ * @param {string} shortOrLong - option used, with dashes e.g. `-l` or `--long`
+ * @param {boolean} strict - show errors, from parseArgs({ strict })
+ */
+function checkOptionLikeValue(longOption, optionValue, shortOrLong, strict) {
+ if (strict && isOptionLikeValue(optionValue)) {
+ // Only show short example if user used short option.
+ const example = (shortOrLong.length === 2)
+ ? `'--${longOption}=-XYZ' or '${shortOrLong}-XYZ'`
+ : `'--${longOption}=-XYZ'`;
+ const errorMessage = `Option '${shortOrLong}' argument is ambiguous.
+Did you forget to specify the option argument for '${shortOrLong}'?
+To specify an option argument starting with a dash use ${example}.`;
+ throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(errorMessage);
+ }
+}
+
+/**
+ * In strict mode, throw for usage errors.
+ *
+ * @param {string} longOption - long option name e.g. 'foo'
+ * @param {string|undefined} optionValue - value from user args
+ * @param {object} options - option configs, from parseArgs({ options })
+ * @param {string} shortOrLong - option used, with dashes e.g. `-l` or `--long`
+ * @param {boolean} strict - show errors, from parseArgs({ strict })
+ * @param {boolean} allowPositionals - from parseArgs({ allowPositionals })
+ */
+function checkOptionUsage(
+ longOption,
+ optionValue,
+ options,
+ shortOrLong,
+ strict,
+ allowPositionals,
+) {
+ // Strict and options are used from local context.
+ if (!strict) return;
+
+ if (!ObjectHasOwn(options, longOption)) {
+ throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(shortOrLong, allowPositionals);
+ }
+
+ const short = optionsGetOwn(options, longOption, "short");
+ const shortAndLong = short ? `-${short}, --${longOption}` : `--${longOption}`;
+ const type = optionsGetOwn(options, longOption, "type");
+ if (type === "string" && typeof optionValue !== "string") {
+ throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(
+ `Option '${shortAndLong} <value>' argument missing`,
+ );
+ }
+ // (Idiomatic test for undefined||null, expecting undefined.)
+ if (type === "boolean" && optionValue != null) {
+ throw new ERR_PARSE_ARGS_INVALID_OPTION_VALUE(
+ `Option '${shortAndLong}' does not take an argument`,
+ );
+ }
+}
+
+/**
+ * Store the option value in `values`.
+ *
+ * @param {string} longOption - long option name e.g. 'foo'
+ * @param {string|undefined} optionValue - value from user args
+ * @param {object} options - option configs, from parseArgs({ options })
+ * @param {object} values - option values returned in `values` by parseArgs
+ */
+function storeOption(longOption, optionValue, options, values) {
+ if (longOption === "__proto__") {
+ return; // No. Just no.
+ }
+
+ // We store based on the option value rather than option type,
+ // preserving the users intent for author to deal with.
+ const newValue = optionValue ?? true;
+ if (optionsGetOwn(options, longOption, "multiple")) {
+ // Always store value in array, including for boolean.
+ // values[longOption] starts out not present,
+ // first value is added as new array [newValue],
+ // subsequent values are pushed to existing array.
+ // (note: values has null prototype, so simpler usage)
+ if (values[longOption]) {
+ ArrayPrototypePush(values[longOption], newValue);
+ } else {
+ values[longOption] = [newValue];
+ }
+ } else {
+ values[longOption] = newValue;
+ }
+}
+
+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 options = objectGetOwn(config, "options") ?? { __proto__: null };
+
+ // Validate input configuration.
+ validateArray(args, "args");
+ validateBoolean(strict, "strict");
+ validateBoolean(allowPositionals, "allowPositionals");
+ validateObject(options, "options");
+ ArrayPrototypeForEach(
+ ObjectEntries(options),
+ ({ 0: longOption, 1: optionConfig }) => {
+ validateObject(optionConfig, `options.${longOption}`);
+
+ // type is required
+ validateUnion(
+ objectGetOwn(optionConfig, "type"),
+ `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",
+ );
+ }
+ }
+
+ if (ObjectHasOwn(optionConfig, "multiple")) {
+ validateBoolean(
+ optionConfig.multiple,
+ `options.${longOption}.multiple`,
+ );
+ }
+ },
+ );
+
+ const result = {
+ values: { __proto__: null },
+ positionals: [],
+ };
+
+ const remainingArgs = ArrayPrototypeSlice(args);
+ while (remainingArgs.length > 0) {
+ const arg = ArrayPrototypeShift(remainingArgs);
+ const nextArg = remainingArgs[0];
+
+ // Check if `arg` is an options terminator.
+ // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
+ if (arg === "--") {
+ if (!allowPositionals && remainingArgs.length > 0) {
+ throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(nextArg);
+ }
+
+ // Everything after a bare '--' is considered a positional argument.
+ ArrayPrototypePushApply(
+ result.positionals,
+ remainingArgs,
+ );
+ break; // Finished processing args, leave while loop.
+ }
+
+ if (isLoneShortOption(arg)) {
+ // e.g. '-f'
+ const shortOption = StringPrototypeCharAt(arg, 1);
+ const longOption = findLongOptionForShort(shortOption, options);
+ let optionValue;
+ if (
+ optionsGetOwn(options, longOption, "type") === "string" &&
+ isOptionValue(nextArg)
+ ) {
+ // e.g. '-f', 'bar'
+ optionValue = ArrayPrototypeShift(remainingArgs);
+ checkOptionLikeValue(longOption, optionValue, arg, strict);
+ }
+ checkOptionUsage(
+ longOption,
+ optionValue,
+ options,
+ arg,
+ strict,
+ allowPositionals,
+ );
+ storeOption(longOption, optionValue, options, result.values);
+ continue;
+ }
+
+ if (isShortOptionGroup(arg, options)) {
+ // Expand -fXzy to -f -X -z -y
+ 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
+ ) {
+ // Boolean option, or last short in group. Well formed.
+ ArrayPrototypePush(expanded, `-${shortOption}`);
+ } else {
+ // String option in middle. Yuck.
+ // Expand -abfFILE to -a -b -fFILE
+ ArrayPrototypePush(expanded, `-${StringPrototypeSlice(arg, index)}`);
+ break; // finished short group
+ }
+ }
+ ArrayPrototypeUnshiftApply(remainingArgs, expanded);
+ continue;
+ }
+
+ if (isShortOptionAndValue(arg, options)) {
+ // e.g. -fFILE
+ const shortOption = StringPrototypeCharAt(arg, 1);
+ const longOption = findLongOptionForShort(shortOption, options);
+ const optionValue = StringPrototypeSlice(arg, 2);
+ checkOptionUsage(
+ longOption,
+ optionValue,
+ options,
+ `-${shortOption}`,
+ strict,
+ allowPositionals,
+ );
+ storeOption(longOption, optionValue, options, result.values);
+ continue;
+ }
+
+ if (isLoneLongOption(arg)) {
+ // e.g. '--foo'
+ const longOption = StringPrototypeSlice(arg, 2);
+ let optionValue;
+ if (
+ optionsGetOwn(options, longOption, "type") === "string" &&
+ isOptionValue(nextArg)
+ ) {
+ // e.g. '--foo', 'bar'
+ optionValue = ArrayPrototypeShift(remainingArgs);
+ checkOptionLikeValue(longOption, optionValue, arg, strict);
+ }
+ checkOptionUsage(
+ longOption,
+ optionValue,
+ options,
+ arg,
+ strict,
+ allowPositionals,
+ );
+ storeOption(longOption, optionValue, options, result.values);
+ continue;
+ }
+
+ if (isLongOptionAndValue(arg)) {
+ // e.g. --foo=bar
+ const index = StringPrototypeIndexOf(arg, "=");
+ const longOption = StringPrototypeSlice(arg, 2, index);
+ const optionValue = StringPrototypeSlice(arg, index + 1);
+ checkOptionUsage(
+ longOption,
+ optionValue,
+ options,
+ `--${longOption}`,
+ strict,
+ allowPositionals,
+ );
+ storeOption(longOption, optionValue, options, result.values);
+ continue;
+ }
+
+ // Anything left is a positional
+ if (!allowPositionals) {
+ throw new ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL(arg);
+ }
+
+ ArrayPrototypePush(result.positionals, arg);
+ }
+
+ return result;
+};