diff --git a/command/command.ts b/command/command.ts index 6cf2e917..2c9011af 100644 --- a/command/command.ts +++ b/command/command.ts @@ -789,23 +789,6 @@ export class Command< * Don't throw an error if the command was called without arguments. * @param allowEmpty Enable/disable allow empty. */ - // public allowEmpty( - // allowEmpty?: TAllowEmpty, - // ): false extends TAllowEmpty ? this - // : Command< - // Partial, - // TParentCommandTypes, - // Partial, - // TCommandArguments, - // TCommandGlobals, - // TCommandTypes, - // TCommandGlobalTypes, - // TParentCommand - // > { - // this.cmd._allowEmpty = allowEmpty !== false; - // return this; - // } - public allowEmpty( allowEmpty?: TAllowEmpty, ): false extends TAllowEmpty ? this @@ -1859,13 +1842,12 @@ export class Command< allowEmpty: this._allowEmpty, flags: options, ignoreDefaults: ctx.env, - parse: (type: ArgumentValue) => this.parseType(type), - option: (option: Option) => { + option: (option) => { if (!ctx.action && option.action) { ctx.action = option as ActionOption; } }, - }); + }, (type: ArgumentValue) => this.parseType(type)); } /** Parse argument type. */ diff --git a/command/test/command/arguments_test.ts b/command/test/command/arguments_test.ts index 46820b49..bbe23de6 100644 --- a/command/test/command/arguments_test.ts +++ b/command/test/command/arguments_test.ts @@ -4,7 +4,7 @@ import { describe, it, } from "../../../dev_deps.ts"; -import type { ArgumentValue } from "../../../flags/types.ts"; +import type { ArgumentValue } from "../../types.ts"; import { ValidationError } from "../../_errors.ts"; import { Command } from "../../command.ts"; diff --git a/command/types.ts b/command/types.ts index 1b6585ff..e99cc2e0 100644 --- a/command/types.ts +++ b/command/types.ts @@ -3,8 +3,8 @@ import type { ArgumentOptions, ArgumentValue, + BaseFlagOptions, DefaultValue, - FlagOptions, TypeHandler, ValueHandler, } from "../flags/types.ts"; @@ -136,8 +136,6 @@ export interface Argument extends ArgumentOptions { name: string; /** Shell completion action. */ action: string; - /** Arguments type. */ - type: string; } /** Result of `cmd.parse()` method. */ @@ -183,18 +181,6 @@ export type OptionValueHandler = ValueHandler< TReturn >; -type ExcludedCommandOptions = - | "name" - | "args" - | "type" - | "optionalValue" - | "requiredValue" - | "aliases" - | "variadic" - | "list" - | "value" - | "default"; - /** Command option options. */ export interface GlobalOptionOptions< TOptions extends Record | void = any, @@ -215,7 +201,7 @@ export interface GlobalOptionOptions< TParentCommand extends Command | undefined = TOptions extends number ? any : undefined, -> extends Omit { +> extends Omit { override?: boolean; hidden?: boolean; action?: ActionHandler< @@ -230,7 +216,7 @@ export interface GlobalOptionOptions< >; prepend?: boolean; value?: OptionValueHandler; - default?: DefaultValue; + separator?: string; } export interface OptionOptions< @@ -297,12 +283,13 @@ export interface Option< TParentTypes, TParentCommand >, - Omit { + BaseFlagOptions { description: string; flags: Array; typeDefinition?: string; - args: Argument[]; + args: Array; groupName?: string; + separator?: string; } /* ENV VARS TYPES */ @@ -329,6 +316,7 @@ export interface EnvVar extends EnvVarOptions { name: string; names: string[]; description: string; + // @TODO: extend EnvVar from Argument type: string; details: Argument; } @@ -344,7 +332,7 @@ export interface TypeOptions { /** Type settings. */ export interface TypeDef extends TypeOptions { name: string; - handler: Type | TypeHandler; + handler: TypeOrTypeHandler; } /* EXAMPLE TYPES */ @@ -437,6 +425,8 @@ export type CompleteHandler< parent?: Command, ) => CompleteHandlerResult; +/* HELP */ + /** * Help callback method to print the help. * Invoked by the `--help` option and `help` command and the `.getHelp()` and `.showHelp()` methods. diff --git a/examples/flags/custom_option_processing.ts b/examples/flags/custom_option_processing.ts index cd9e9930..196e5e3d 100755 --- a/examples/flags/custom_option_processing.ts +++ b/examples/flags/custom_option_processing.ts @@ -7,19 +7,18 @@ const result = parseFlags(Deno.args, { name: "foo", type: "float", }], - parse: ({ label, name, value, type }: ArgumentValue) => { - switch (type) { - case "float": - if (isNaN(Number(value))) { - throw new Error( - `${label} "${name}" must be of type "${type}", but got "${value}".`, - ); - } - return parseFloat(value); - default: - throw new Error(`Unknown type "${type}".`); - } - }, +}, ({ label, name, value, type }: ArgumentValue) => { + switch (type) { + case "float": + if (isNaN(Number(value))) { + throw new Error( + `${label} "${name}" must be of type "${type}", but got "${value}".`, + ); + } + return parseFloat(value); + default: + throw new Error(`Unknown type "${type}".`); + } }); console.log(result); diff --git a/flags/_utils.ts b/flags/_utils.ts index b1cfb68e..356e61de 100644 --- a/flags/_utils.ts +++ b/flags/_utils.ts @@ -1,4 +1,4 @@ -import type { FlagOptions } from "./types.ts"; +import type { FlagOptions, ValuesFlagOptions } from "./types.ts"; import { distance } from "../_utils/distance.ts"; /** Convert param case string to camel case. */ @@ -141,3 +141,10 @@ export function getDefaultValue(option: FlagOptions): unknown { ? option.default() : option.default; } + +export function isValueFlag( + option: FlagOptions, +): option is ValuesFlagOptions { + return "args" in option && Array.isArray(option.args) && + option.args.length > 0; +} diff --git a/flags/_validate_flags.ts b/flags/_validate_flags.ts index f010ddfa..284a4dbf 100644 --- a/flags/_validate_flags.ts +++ b/flags/_validate_flags.ts @@ -1,4 +1,9 @@ -import { getDefaultValue, getOption, paramCaseToCamelCase } from "./_utils.ts"; +import { + getDefaultValue, + getOption, + isValueFlag, + paramCaseToCamelCase, +} from "./_utils.ts"; import { ConflictingOptionError, DependingOptionError, @@ -7,8 +12,12 @@ import { OptionNotCombinableError, UnknownOptionError, } from "./_errors.ts"; -import { ParseFlagsContext, ParseFlagsOptions } from "./types.ts"; -import type { ArgumentOptions, FlagOptions } from "./types.ts"; +import { + ArgumentOptions, + FlagOptions, + ParseFlagsContext, + ParseFlagsOptions, +} from "./types.ts"; /** * Flags post validation. Validations that are not already done by the parser. @@ -169,7 +178,7 @@ function validateRequiredValues( option: FlagOptions, name: string, ): void { - if (!option.args) { + if (!isValueFlag(option)) { return; } const isArray = option.args.length > 1; diff --git a/flags/flags.ts b/flags/flags.ts index 76095581..54b96f87 100644 --- a/flags/flags.ts +++ b/flags/flags.ts @@ -1,6 +1,7 @@ import { getDefaultValue, getOption, + isValueFlag, matchWildCardOptions, paramCaseToCamelCase, } from "./_utils.ts"; @@ -25,6 +26,7 @@ import type { ParseFlagsContext, ParseFlagsOptions, TypeHandler, + ValuesFlagOptions, } from "./types.ts"; import { boolean } from "./types/boolean.ts"; import { number } from "./types/number.ts"; @@ -75,8 +77,9 @@ export function parseFlags< TFlagOptions extends FlagOptions, TFlagsResult extends ParseFlagsContext, >( - argsOrCtx: string[] | TFlagsResult, + argsOrCtx: Array | TFlagsResult, opts: ParseFlagsOptions = {}, + parse?: TypeHandler, ): TFlagsResult & ParseFlagsContext { let args: Array; let ctx: ParseFlagsContext>; @@ -100,7 +103,7 @@ export function parseFlags< opts.dotted ??= true; validateOptions(opts); - const options = parseArgs(ctx, args, opts); + const options = parseArgs(ctx, args, opts, parse); validateFlags(ctx, opts, options); if (opts.dotted) { @@ -131,6 +134,7 @@ function parseArgs( ctx: ParseFlagsContext>, args: Array, opts: ParseFlagsOptions, + parseCustomType?: TypeHandler, ): Map { /** Option name mapping: propertyName -> option.name */ const optionsMap: Map = new Map(); @@ -230,8 +234,8 @@ function parseArgs( } } - if (option.type && !option.args?.length) { - option.args = [{ + if ("type" in option && option.type && !isValueFlag(option)) { + (option as unknown as ValuesFlagOptions).args = [{ type: option.type, optional: option.optionalValue, variadic: option.variadic, @@ -241,7 +245,7 @@ function parseArgs( } if ( - opts.flags?.length && !option.args?.length && + opts.flags?.length && !isValueFlag(option) && typeof currentValue !== "undefined" ) { throw new UnexpectedOptionValueError(option.name, currentValue); @@ -255,7 +259,7 @@ function parseArgs( parseNext(option); if (typeof ctx.flags[propName] === "undefined") { - if (option.args?.length && !option.args?.[optionArgsIndex].optional) { + if (isValueFlag(option) && !option.args[optionArgsIndex].optional) { throw new MissingOptionValueError(option.name); } else if (typeof option.default !== "undefined") { ctx.flags[propName] = getDefaultValue(option); @@ -285,7 +289,7 @@ function parseArgs( if (negate) { ctx.flags[propName] = false; return; - } else if (!option.args?.length) { + } else if (!isValueFlag(option)) { ctx.flags[propName] = undefined; return; } @@ -296,19 +300,6 @@ function parseArgs( throw new UnknownOptionError(flag, opts.flags ?? []); } - if (!arg.type) { - arg.type = OptionType.BOOLEAN; - } - - // make boolean values optional by default - if ( - !option.args?.length && - arg.type === OptionType.BOOLEAN && - arg.optional === undefined - ) { - arg.optional = true; - } - if (arg.optional) { inOptionalArg = true; } else if (inOptionalArg) { @@ -372,7 +363,7 @@ function parseArgs( /** Check if current option should have an argument. */ function hasNext(arg: ArgumentOptions): boolean { - if (!option.args?.length) { + if (!isValueFlag(option)) { return false; } const nextValue = currentValue ?? args[argsIndex + 1]; @@ -403,18 +394,22 @@ function parseArgs( /** Parse argument value. */ function parseValue( - option: FlagOptions, + option: ValuesFlagOptions, arg: ArgumentOptions, value: string, ): unknown { - const result: unknown = opts.parse - ? opts.parse({ + const result: unknown = parseCustomType + ? parseCustomType({ label: "Option", - type: arg.type || OptionType.STRING, + type: arg.type, name: `--${option.name}`, value, }) - : parseDefaultType(option, arg, value); + : parseDefaultType( + option as ValuesFlagOptions, + arg as ArgumentOptions, + value, + ); if (typeof result !== "undefined") { increase = true; @@ -482,7 +477,7 @@ function splitFlags(flag: string): Array { } function parseDefaultType( - option: FlagOptions, + option: ValuesFlagOptions, arg: ArgumentOptions, value: string, ): unknown { diff --git a/flags/test/option/required_test.ts b/flags/test/option/required_test.ts index 51093273..a7820ef5 100644 --- a/flags/test/option/required_test.ts +++ b/flags/test/option/required_test.ts @@ -8,6 +8,7 @@ const options: ParseFlagsOptions = { name: "required", type: OptionType.STRING, required: true, + optionalValue: true, }, { name: "required-value", type: OptionType.STRING, diff --git a/flags/types.ts b/flags/types.ts index bf4af1eb..ff8579cd 100644 --- a/flags/types.ts +++ b/flags/types.ts @@ -1,9 +1,8 @@ -/** Parser options. */ +/** Options for the `parseFlags` method. */ export interface ParseFlagsOptions< TFlagOptions extends FlagOptions = FlagOptions, > { flags?: Array; - parse?: TypeHandler; option?: (option: TFlagOptions, value?: unknown) => void; stopEarly?: boolean; stopOnUnknown?: boolean; @@ -12,11 +11,9 @@ export interface ParseFlagsOptions< dotted?: boolean; } -/** Flag options. */ -export interface FlagOptions extends Omit { +/** Base flag options. */ +export interface BaseFlagOptions { name: string; - args?: Array; - optionalValue?: boolean; aliases?: string[]; standalone?: boolean; default?: DefaultValue; @@ -28,9 +25,32 @@ export interface FlagOptions extends Omit { equalsSign?: boolean; } +/** Options for a flag with no arguments. */ +export type BooleanFlagOptions = BaseFlagOptions; + +/** Options for a flag with an argument. */ +export interface ValueFlagOptions extends BaseFlagOptions, ArgumentOptions { + optionalValue?: boolean; +} + +/** + * Options for a flag with multiple arguments. Arguments are defined with the + * `args` array. Each argument can have it's own type specified with the `type` + * option. + */ +export interface ValuesFlagOptions extends BaseFlagOptions { + args: Array; +} + +/** Flag options. */ +export type FlagOptions = + | BooleanFlagOptions + | ValueFlagOptions + | ValuesFlagOptions; + /** Options for a flag argument. */ export interface ArgumentOptions { - type?: ArgumentType | string; + type: ArgumentType | string; optional?: boolean; variadic?: boolean; list?: boolean;