Main goals

Args interface/abstract class

Expected result

Untitled

Code

import type { ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
import { Result, type Option } from '@sapphire/result';
import type {
	CategoryChannel,
	ChannelType,
	DMChannel,
	GuildMember,
	Message,
	NewsChannel,
	Role,
	StageChannel,
	TextChannel,
	ThreadChannel,
	User,
	VoiceChannel
} from 'discord.js';
import { ArgumentError } from '../errors/ArgumentError';
import type { UserError } from '../errors/UserError';
import type { EmojiObject } from '../resolvers/emoji';
import type { IArgument } from '../structures/Argument';

export abstract class Args2 {
	public abstract start(): this;
	public abstract pickResult<T extends ArgsOptions>(options: T): Promise<ResultType<InferArgReturnType<T>>>;
	public abstract pick<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
	public abstract restResult<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
	public abstract rest<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
	public abstract repeatResult<T extends ArgsOptions>(options: T): Promise<ArrayResultType<InferArgReturnType<T>>>;
	public abstract repeat<T extends ArgsOptions>(options: T): Promise<ArrayResultType<InferArgReturnType<T>>>;
	public abstract peekResult<T extends ArgsOptions>(options: T): Promise<ResultType<InferArgReturnType<T>>>;
	public abstract peek<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
	// nextMaybe, next, getFlags, getOptionResult, getOption, getOptionsResult, getOptions should only go on message args

	/**
	 * Converts a callback into a usable argument.
	 * @param cb The callback to convert into an {@link IArgument}.
	 * @param name The name of the argument.
	 */
	public static make<T>(cb: IArgument<T>['run'], name = ''): IArgument<T> {
		return { run: cb, name };
	}

	/**
	 * Constructs an {@link Ok} result.
	 * @param value The value to pass.
	 */
	public static ok<T>(value: T): Result.Ok<T> {
		return Result.ok(value);
	}

	/**
	 * Constructs an {@link Err} result containing an {@link ArgumentError}.
	 * @param options The options for the argument error.
	 */
	public static error<T>(options: ArgumentError.Options<T>): Result.Err<ArgumentError<T>> {
		return Result.err(new ArgumentError<T>(options));
	}
}

declare const args: Args2;

const result = await args.pickResult({ name: 'url', type: 'url' });
//    ^?
const result2 = await args.pickResult({ name: 'url', type: 'guildChannel' });
//    ^?
const testArg = Args2.make(() => {
	return Args2.ok(null as any as symbol);
});
const result3 = await args.pickResult({ name: 'test', type: testArg });
//    ^?

export interface ArgsOptions<T = unknown, K extends keyof ArgType = keyof ArgType> {
	// Up to the person implementing if this should always be required or only required for chat commands, but this should
	// always be required for chat commands, and be used to find the starting point for our parsing
	name: string;
	type: IArgument<T> | K;
	// TODO: the rest of the args options go here (repeat, maximum, minimum, etc)
}

export type InferArgReturnType<T extends ArgsOptions> =
	T['type'] extends IArgument<infer R> ? R : T['type'] extends keyof ArgType ? ArgType[T['type']] : never;

export interface ArgType {
	boolean: boolean;
	channel: ChannelTypes;
	date: Date;
	dmChannel: DMChannel;
	emoji: EmojiObject;
	float: number;
	guildCategoryChannel: CategoryChannel;
	guildChannel: GuildBasedChannelTypes;
	guildNewsChannel: NewsChannel;
	guildNewsThreadChannel: ThreadChannel & { type: ChannelType.AnnouncementThread; parent: NewsChannel | null };
	guildPrivateThreadChannel: ThreadChannel & { type: ChannelType.PrivateThread; parent: TextChannel | null };
	guildPublicThreadChannel: ThreadChannel & { type: ChannelType.PublicThread; parent: TextChannel | null };
	guildStageVoiceChannel: StageChannel;
	guildTextChannel: TextChannel;
	guildThreadChannel: ThreadChannel;
	guildVoiceChannel: VoiceChannel;
	hyperlink: URL;
	integer: number;
	member: GuildMember;
	message: Message;
	number: number;
	role: Role;
	string: string;
	url: URL;
	user: User;
	enum: string;
}

export type ResultType<T> = Result<T, UserError | ArgumentError<T>>;
export type ArrayResultType<T> = Result<T[], UserError | ArgumentError<T>>;

/**
 * The callback used for {@link Args.nextMaybe} and {@link Args.next}.
 */
export interface ArgsNextCallback<T> {
	/**
	 * The value to be mapped.
	 */
	(value: string): Option<T>;
}