diff options
author | Yoshiya Hinosawa <stibium121@gmail.com> | 2023-11-29 15:42:58 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-29 15:42:58 +0900 |
commit | e332fa4a83d36605771844745738c001f276b390 (patch) | |
tree | 9d21beacdcad06bb49cc31539ed3d8374e27506f /ext/node/polyfills/internal/util/parse_args/utils.js | |
parent | 75ec650f080ac66e98d8b848118dc2349ca70aa8 (diff) |
fix(ext/node): add util.parseArgs (#21342)
Diffstat (limited to 'ext/node/polyfills/internal/util/parse_args/utils.js')
-rw-r--r-- | ext/node/polyfills/internal/util/parse_args/utils.js | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal/util/parse_args/utils.js b/ext/node/polyfills/internal/util/parse_args/utils.js new file mode 100644 index 000000000..d101dd3fd --- /dev/null +++ b/ext/node/polyfills/internal/util/parse_args/utils.js @@ -0,0 +1,187 @@ +// 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 { + ArrayPrototypeFind, + ObjectEntries, + ObjectHasOwn, + StringPrototypeCharAt, + StringPrototypeIncludes, + StringPrototypeStartsWith, +} = primordials; + +import { validateObject } from "ext:deno_node/internal/validators.mjs"; + +// These are internal utilities to make the parsing logic easier to read, and +// add lots of detail for the curious. They are in a separate file to allow +// unit testing, although that is not essential (this could be rolled into +// main file and just tested implicitly via API). +// +// These routines are for internal use, not for export to client. + +/** + * Return the named property, but only if it is an own property. + */ +function objectGetOwn(obj, prop) { + if (ObjectHasOwn(obj, prop)) { + return obj[prop]; + } +} + +/** + * Return the named options property, but only if it is an own property. + */ +function optionsGetOwn(options, longOption, prop) { + if (ObjectHasOwn(options, longOption)) { + return objectGetOwn(options[longOption], prop); + } +} + +/** + * Determines if the argument may be used as an option value. + * @example + * isOptionValue('V') // returns true + * isOptionValue('-v') // returns true (greedy) + * isOptionValue('--foo') // returns true (greedy) + * isOptionValue(undefined) // returns false + */ +function isOptionValue(value) { + if (value == null) return false; + + // Open Group Utility Conventions are that an option-argument + // is the argument after the option, and may start with a dash. + return true; // greedy! +} + +/** + * Detect whether there is possible confusion and user may have omitted + * the option argument, like `--port --verbose` when `port` of type:string. + * In strict mode we throw errors if value is option-like. + */ +function isOptionLikeValue(value) { + if (value == null) return false; + + return value.length > 1 && StringPrototypeCharAt(value, 0) === "-"; +} + +/** + * Determines if `arg` is just a short option. + * @example '-f' + */ +function isLoneShortOption(arg) { + return arg.length === 2 && + StringPrototypeCharAt(arg, 0) === "-" && + StringPrototypeCharAt(arg, 1) !== "-"; +} + +/** + * Determines if `arg` is a lone long option. + * @example + * isLoneLongOption('a') // returns false + * isLoneLongOption('-a') // returns false + * isLoneLongOption('--foo') // returns true + * isLoneLongOption('--foo=bar') // returns false + */ +function isLoneLongOption(arg) { + return arg.length > 2 && + StringPrototypeStartsWith(arg, "--") && + !StringPrototypeIncludes(arg, "=", 3); +} + +/** + * Determines if `arg` is a long option and value in the same argument. + * @example + * isLongOptionAndValue('--foo') // returns false + * isLongOptionAndValue('--foo=bar') // returns true + */ +function isLongOptionAndValue(arg) { + return arg.length > 2 && + StringPrototypeStartsWith(arg, "--") && + StringPrototypeIncludes(arg, "=", 3); +} + +/** + * Determines if `arg` is a short option group. + * + * See Guideline 5 of the [Open Group Utility Conventions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html). + * One or more options without option-arguments, followed by at most one + * option that takes an option-argument, should be accepted when grouped + * behind one '-' delimiter. + * @example + * isShortOptionGroup('-a', {}) // returns false + * isShortOptionGroup('-ab', {}) // returns true + * // -fb is an option and a value, not a short option group + * isShortOptionGroup('-fb', { + * options: { f: { type: 'string' } } + * }) // returns false + * isShortOptionGroup('-bf', { + * options: { f: { type: 'string' } } + * }) // returns true + * // -bfb is an edge case, return true and caller sorts it out + * isShortOptionGroup('-bfb', { + * options: { f: { type: 'string' } } + * }) // returns true + */ +function isShortOptionGroup(arg, options) { + if (arg.length <= 2) return false; + if (StringPrototypeCharAt(arg, 0) !== "-") return false; + if (StringPrototypeCharAt(arg, 1) === "-") return false; + + const firstShort = StringPrototypeCharAt(arg, 1); + const longOption = findLongOptionForShort(firstShort, options); + return optionsGetOwn(options, longOption, "type") !== "string"; +} + +/** + * Determine if arg is a short string option followed by its value. + * @example + * isShortOptionAndValue('-a', {}); // returns false + * isShortOptionAndValue('-ab', {}); // returns false + * isShortOptionAndValue('-fFILE', { + * options: { foo: { short: 'f', type: 'string' }} + * }) // returns true + */ +function isShortOptionAndValue(arg, options) { + validateObject(options, "options"); + + if (arg.length <= 2) return false; + if (StringPrototypeCharAt(arg, 0) !== "-") return false; + if (StringPrototypeCharAt(arg, 1) === "-") return false; + + const shortOption = StringPrototypeCharAt(arg, 1); + const longOption = findLongOptionForShort(shortOption, options); + return optionsGetOwn(options, longOption, "type") === "string"; +} + +/** + * Find the long option associated with a short option. Looks for a configured + * `short` and returns the short option itself if a long option is not found. + * @example + * findLongOptionForShort('a', {}) // returns 'a' + * findLongOptionForShort('b', { + * options: { bar: { short: 'b' } } + * }) // returns 'bar' + */ +function findLongOptionForShort(shortOption, options) { + validateObject(options, "options"); + const longOptionEntry = ArrayPrototypeFind( + ObjectEntries(options), + ({ 1: optionConfig }) => + objectGetOwn(optionConfig, "short") === shortOption, + ); + return longOptionEntry?.[0] ?? shortOption; +} + +export { + findLongOptionForShort, + isLoneLongOption, + isLoneShortOption, + isLongOptionAndValue, + isOptionLikeValue, + isOptionValue, + isShortOptionAndValue, + isShortOptionGroup, + objectGetOwn, + optionsGetOwn, +}; |