diff --git a/index.js b/index.js index 0aecd90..77afe49 100644 --- a/index.js +++ b/index.js @@ -210,6 +210,7 @@ function validateAndFillDefaults(opts) { validateOrDefaultIfUnset(opts, 'allowSingularDashLongFlags', 'boolean', false); validateOrDefaultIfUnset(opts, 'lowerCaseFlagValues', 'boolean', false); validateOrDefaultIfUnset(opts, 'lowerCaseInputValues', 'boolean', false); + validateOrDefaultIfUnset(opts, 'allowMultipleFlagValues', 'boolean', false); if(opts.shouldStopParsingFunc !== null) validateOrDefaultIfUnset(opts, 'shouldStopParsingFunc', 'function', null); @@ -825,9 +826,10 @@ function parseShorthandFlags(shorthandFlags, shorthandString) { // TODO: Fix typescript types of returned flags to include opts.resolveFlagValueTypes into it as well as checking if it has a default and if not make it an optional?, and type never if it's not in the flags object /** * @template {FlagsI} Flags - * @template {{ [K in keyof Flags]: Flags[K]['type'] extends 'boolean' ? boolean : Flags[K]['type'] extends 'number' ? number : Flags[K]['type'] extends 'string' ? string : string | boolean | number }} FlagsReturn + * @template {ParserOpts} Opts + * @template {import('./types.d.ts').FlagsReturn} FlagsReturn * @param {string[]} argv - * @param {ParserOpts} opts + * @param {Opts} opts * @returns {{ input: string[], flags: FlagsReturn, unparsed: string[] } }} */ function parser(argv, opts) { @@ -1060,6 +1062,13 @@ function parser(argv, opts) { const flagName = flag.config?.name || flag.key; + if(opts.allowMultipleFlagValues) { + if(!Array.isArray(flagReturnObj[flagName])) + flagReturnObj[flagName] = [flag.value]; + else + flagReturnObj[flagName].push(flag.value); + return + } flagReturnObj[flagName] = flag.value; }) @@ -1067,10 +1076,16 @@ function parser(argv, opts) { Object.entries(opts.flags).forEach(([flagName, flagConfig]) => { if(typeof flagReturnObj[flagName] !== 'undefined') return; + + if(opts.allowMultipleFlagValues) { + if(typeof flagConfig.default === 'undefined') + return flagReturnObj[flagName] = []; + return flagReturnObj[flagName] = [flagConfig.default]; + } /** Not a needed check as you can tell, but present to explicitly highlight that behavior in the code */ if(typeof flagConfig.default === 'undefined') return flagReturnObj[flagName] = undefined; - flagReturnObj[flagName] = flagConfig.default; + return flagReturnObj[flagName] = flagConfig.default; }); /// @ts-ignore - falsly wrong flags type diff --git a/types.d.ts b/types.d.ts index 89d082d..97dd204 100644 --- a/types.d.ts +++ b/types.d.ts @@ -11,6 +11,8 @@ interface FlagT { acceptsNaturalValue?: boolean }; +export type FlagTypes = "string" | "boolean" | "bigint" | "number" | "any"; + export type FlagAny = FlagT | FlagT | FlagT | FlagT | FlagT export interface FlagsI { [key: readonly string]: FlagAny } @@ -49,7 +51,7 @@ export interface ParserOpts { /** Override to change how warnings are emitted @default console.warn */ warningLogger?(log: string): void; /** Default type for either unknown flags or flags without an explicit type being set @default "any" */ - defaultFlagType?: "string" | "boolean" | "bigint" | "number" | "any"; + defaultFlagType?: FlagTypes; /** * Behavior when input does not follow the provided flags constraints (eg: flag assigned value (--flag=value) not being the correct type). * @@ -125,6 +127,24 @@ export interface ParserOpts { * @default null */ parseFilterFunc?(arg: string, index: number, argv: string[], argMeta: argMeta, input: string[]): boolean + /** + * Allows multiple flag values & changes the returned flags to always be arrays, with the default being the first value if the flag was not set, or an + * empty array if the flag does not have a default but was not present. + * @example + * ```js + * const { flags } = parser(["--flag=hello", "--flag=world"], { + * flags: { + * defaultedFlag: { default: "missing", type: "string" }, + * missingFlag: { type: "string" }, + * }, + * allowMultipleFlagValues: true, + * allowUnknownFlags: true, + * }); + * flags === { flag: ["hello", "world"], defaultedFlag: ["missing"], missingFlag: [] } + * ``` + * @default false + */ + allowMultipleFlagValues?: boolean } export type argMeta = { @@ -146,4 +166,26 @@ export type argMeta = { function __type__getType(e: any) { return typeof e }; -export type JSTypes = ReturnType \ No newline at end of file +export type JSTypes = ReturnType; + +export type FlagReturnType = + FlagType extends 'boolean' ? boolean + : FlagType extends 'number' ? number + : FlagType extends 'bigint' ? bigint + : FlagType extends 'string' ? string + : string | boolean | number | bigint; + +type ValueOf = T[keyof T]; +type OptionalKeys = Pick & Partial>; + +type _OptionalIfFlagDefaultNotPresent = OptionalKeys>; +type _ResolveFlagTypes = { + [K in keyof Flags]: allowMultipleFlagValues extends true + ? FlagReturnType[] + : FlagReturnType +} + +export type FlagsReturn = _ResolveFlagTypes< + Opts['allowMultipleFlagValues'] extends true ? Opts['flags'] : _OptionalIfFlagDefaultNotPresent, + Opts['allowMultipleFlagValues'] extends true ? true : false +>; \ No newline at end of file