Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental: refactor flags and command module #464

Draft
wants to merge 49 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0b9b3af
refactor(flags): refactor parsing
c4spar Oct 2, 2022
52be57d
refactor(flags): refactor flags module
c4spar Oct 2, 2022
ea4b13e
fix splitFlags
c4spar Oct 3, 2022
69fbb50
use ValueFlagOptions
c4spar Oct 3, 2022
6a896c5
exclude equalsSign
c4spar Oct 3, 2022
61d3c2b
minor
c4spar Oct 3, 2022
ed55674
minor
c4spar Oct 3, 2022
ca3ecc0
export integer type in mod.ts
c4spar Oct 3, 2022
2521210
remove validateFlags from exports
c4spar Oct 3, 2022
66df8e2
refactor types
c4spar Oct 4, 2022
1f68641
adapt command module
c4spar Oct 4, 2022
cc7445e
update examples
c4spar Oct 4, 2022
a9f69a1
print all example errors
c4spar Oct 4, 2022
c370231
fix standalone option type
c4spar Oct 4, 2022
ca054ea
some re-namings and update docs
c4spar Oct 5, 2022
1eef561
minor
c4spar Oct 5, 2022
c98ee28
minor
c4spar Oct 5, 2022
8463fcb
docs
c4spar Oct 5, 2022
bf0b05a
rename command types
c4spar Oct 5, 2022
a93a65b
add missing exports
c4spar Oct 5, 2022
0fe0bfd
rename errors
c4spar Oct 5, 2022
dc6ee1a
rename CommandDescription
c4spar Oct 5, 2022
0bc8c38
minor
c4spar Oct 7, 2022
b8d584f
some re-namings
c4spar Oct 16, 2022
fc8dfef
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 21, 2022
b208891
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 21, 2022
9636fea
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 21, 2022
d158015
minor
c4spar Oct 21, 2022
338292a
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 22, 2022
b01e9b2
lint
c4spar Oct 22, 2022
4dff4bd
minor
c4spar Oct 23, 2022
05310f8
minor
c4spar Oct 23, 2022
690dd6e
minor
c4spar Oct 23, 2022
18d2754
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 23, 2022
87cb99c
fix
c4spar Oct 23, 2022
9a0309e
minor
c4spar Oct 23, 2022
ae05599
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 23, 2022
6aae022
fmt
c4spar Oct 23, 2022
661ab56
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 24, 2022
34787ca
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 24, 2022
ad556d9
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 24, 2022
c0197ce
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Oct 24, 2022
58dca7c
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Nov 15, 2022
644f616
Merge branch 'main' into flags/refactor-and-cleanup
c4spar Dec 4, 2022
6d7c7bb
fix
c4spar Dec 4, 2022
4e74952
Merge remote-tracking branch 'origin/main' into flags/refactor-and-cl…
c4spar Jan 5, 2023
b75bdb3
Merge branch 'main' into flags/refactor-and-cleanup
c4spar Feb 18, 2023
59891fb
revert generics
c4spar Feb 21, 2023
1f8d05e
fix example
c4spar Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 2 additions & 20 deletions command/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TAllowEmpty extends boolean | undefined = undefined>(
// allowEmpty?: TAllowEmpty,
// ): false extends TAllowEmpty ? this
// : Command<
// Partial<TParentCommandGlobals>,
// TParentCommandTypes,
// Partial<TCommandOptions>,
// TCommandArguments,
// TCommandGlobals,
// TCommandTypes,
// TCommandGlobalTypes,
// TParentCommand
// > {
// this.cmd._allowEmpty = allowEmpty !== false;
// return this;
// }

public allowEmpty<TAllowEmpty extends boolean | undefined = undefined>(
allowEmpty?: TAllowEmpty,
): false extends TAllowEmpty ? this
Expand Down Expand Up @@ -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. */
Expand Down
2 changes: 1 addition & 1 deletion command/test/command/arguments_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
30 changes: 10 additions & 20 deletions command/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import type {
ArgumentOptions,
ArgumentValue,
BaseFlagOptions,
DefaultValue,
FlagOptions,
TypeHandler,
ValueHandler,
} from "../flags/types.ts";
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -183,18 +181,6 @@ export type OptionValueHandler<TValue = any, TReturn = TValue> = ValueHandler<
TReturn
>;

type ExcludedCommandOptions =
| "name"
| "args"
| "type"
| "optionalValue"
| "requiredValue"
| "aliases"
| "variadic"
| "list"
| "value"
| "default";

/** Command option options. */
export interface GlobalOptionOptions<
TOptions extends Record<string, any> | void = any,
Expand All @@ -215,7 +201,7 @@ export interface GlobalOptionOptions<
TParentCommand extends Command<any> | undefined = TOptions extends number
? any
: undefined,
> extends Omit<FlagOptions, ExcludedCommandOptions> {
> extends Omit<BaseFlagOptions, "name" | "aliases" | "equalsSign"> {
override?: boolean;
hidden?: boolean;
action?: ActionHandler<
Expand All @@ -230,7 +216,7 @@ export interface GlobalOptionOptions<
>;
prepend?: boolean;
value?: OptionValueHandler;
default?: DefaultValue;
separator?: string;
}

export interface OptionOptions<
Expand Down Expand Up @@ -297,12 +283,13 @@ export interface Option<
TParentTypes,
TParentCommand
>,
Omit<FlagOptions, "value"> {
BaseFlagOptions {
description: string;
flags: Array<string>;
typeDefinition?: string;
args: Argument[];
args: Array<Argument>;
groupName?: string;
separator?: string;
}

/* ENV VARS TYPES */
Expand All @@ -329,6 +316,7 @@ export interface EnvVar extends EnvVarOptions {
name: string;
names: string[];
description: string;
// @TODO: extend EnvVar from Argument
type: string;
details: Argument;
}
Expand All @@ -344,7 +332,7 @@ export interface TypeOptions {
/** Type settings. */
export interface TypeDef extends TypeOptions {
name: string;
handler: Type<unknown> | TypeHandler<unknown>;
handler: TypeOrTypeHandler<unknown>;
}

/* EXAMPLE TYPES */
Expand Down Expand Up @@ -437,6 +425,8 @@ export type CompleteHandler<
parent?: Command<any>,
) => CompleteHandlerResult;

/* HELP */

/**
* Help callback method to print the help.
* Invoked by the `--help` option and `help` command and the `.getHelp()` and `.showHelp()` methods.
Expand Down
25 changes: 12 additions & 13 deletions examples/flags/custom_option_processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
9 changes: 8 additions & 1 deletion flags/_utils.ts
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down Expand Up @@ -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;
}
17 changes: 13 additions & 4 deletions flags/_validate_flags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getDefaultValue, getOption, paramCaseToCamelCase } from "./_utils.ts";
import {
getDefaultValue,
getOption,
isValueFlag,
paramCaseToCamelCase,
} from "./_utils.ts";
import {
ConflictingOptionError,
DependingOptionError,
Expand All @@ -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.
Expand Down Expand Up @@ -169,7 +178,7 @@ function validateRequiredValues(
option: FlagOptions,
name: string,
): void {
if (!option.args) {
if (!isValueFlag(option)) {
return;
}
const isArray = option.args.length > 1;
Expand Down
49 changes: 22 additions & 27 deletions flags/flags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
getDefaultValue,
getOption,
isValueFlag,
matchWildCardOptions,
paramCaseToCamelCase,
} from "./_utils.ts";
Expand All @@ -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";
Expand Down Expand Up @@ -75,8 +77,9 @@ export function parseFlags<
TFlagOptions extends FlagOptions,
TFlagsResult extends ParseFlagsContext,
>(
argsOrCtx: string[] | TFlagsResult,
argsOrCtx: Array<string> | TFlagsResult,
opts: ParseFlagsOptions<TFlagOptions> = {},
parse?: TypeHandler,
): TFlagsResult & ParseFlagsContext<TFlags, TFlagOptions> {
let args: Array<string>;
let ctx: ParseFlagsContext<Record<string, unknown>>;
Expand All @@ -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) {
Expand Down Expand Up @@ -131,6 +134,7 @@ function parseArgs<TFlagOptions extends FlagOptions>(
ctx: ParseFlagsContext<Record<string, unknown>>,
args: Array<string>,
opts: ParseFlagsOptions<TFlagOptions>,
parseCustomType?: TypeHandler,
): Map<string, FlagOptions> {
/** Option name mapping: propertyName -> option.name */
const optionsMap: Map<string, FlagOptions> = new Map();
Expand Down Expand Up @@ -230,8 +234,8 @@ function parseArgs<TFlagOptions extends FlagOptions>(
}
}

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,
Expand All @@ -241,7 +245,7 @@ function parseArgs<TFlagOptions extends FlagOptions>(
}

if (
opts.flags?.length && !option.args?.length &&
opts.flags?.length && !isValueFlag(option) &&
typeof currentValue !== "undefined"
) {
throw new UnexpectedOptionValueError(option.name, currentValue);
Expand All @@ -255,7 +259,7 @@ function parseArgs<TFlagOptions extends FlagOptions>(
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);
Expand Down Expand Up @@ -285,7 +289,7 @@ function parseArgs<TFlagOptions extends FlagOptions>(
if (negate) {
ctx.flags[propName] = false;
return;
} else if (!option.args?.length) {
} else if (!isValueFlag(option)) {
ctx.flags[propName] = undefined;
return;
}
Expand All @@ -296,19 +300,6 @@ function parseArgs<TFlagOptions extends FlagOptions>(
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) {
Expand Down Expand Up @@ -372,7 +363,7 @@ function parseArgs<TFlagOptions extends FlagOptions>(

/** 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];
Expand Down Expand Up @@ -403,18 +394,22 @@ function parseArgs<TFlagOptions extends FlagOptions>(

/** 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;
Expand Down Expand Up @@ -482,7 +477,7 @@ function splitFlags(flag: string): Array<string> {
}

function parseDefaultType(
option: FlagOptions,
option: ValuesFlagOptions,
arg: ArgumentOptions,
value: string,
): unknown {
Expand Down
1 change: 1 addition & 0 deletions flags/test/option/required_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const options: ParseFlagsOptions = {
name: "required",
type: OptionType.STRING,
required: true,
optionalValue: true,
}, {
name: "required-value",
type: OptionType.STRING,
Expand Down
Loading