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

feat(args): allow peeking arguments #121

Merged
merged 5 commits into from
Jan 2, 2021
Merged
Changes from all commits
Commits
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
144 changes: 142 additions & 2 deletions src/lib/utils/Args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type * as Lexure from 'lexure';
import type { URL } from 'url';
import { ArgumentError } from '../errors/ArgumentError';
import { UserError } from '../errors/UserError';
import type { ArgumentContext, IArgument } from '../structures/Argument';
import type { ArgumentContext, ArgumentResult, IArgument } from '../structures/Argument';
import type { Command } from '../structures/Command';
import { err, isErr, isOk, ok, Ok, Result } from './Result';

Expand Down Expand Up @@ -276,7 +276,7 @@ export class Args {
* ```typescript
* // !reverse-each 2 Hello World!
* const resolver = Args.make((arg) => ok(arg.split('').reverse()));
* const result = await args.repeatResult(resolver, { times: 5 });
* const result = await args.repeat(resolver, { times: 5 });
* await message.channel.send(`You have written ${result.length} word(s): ${result.join(' ')}`);
* // Sends "You have written 2 word(s): Hello World!"
* ```
Expand All @@ -300,6 +300,146 @@ export class Args {
throw result.error;
}

/**
* Peeks the following parameter(s) without advancing the parser's state.
* Passing a function as a parameter allows for returning [[Args.pickResult]], [[Args.repeatResult]],
* or [[Args.restResult]]; otherwise, passing the custom argument or the argument type with options
* will use [[Args.pickResult]] and only peek a single argument.
* @param type The function, custom argument, or argument name.
* @example
* ```typescript
* // !reversedandscreamfirst hello world
* const resolver = Args.make((arg) => ok(arg.split('').reverse().join('')));
*
* const result = await args.peekResult(() => args.repeatResult(resolver));
* if (isOk(result)) await message.channel.send(
* `Reversed ${result.value.length} word(s): ${result.value.join(' ')}`
* ); // Reversed 2 word(s): olleh dlrow
*
* const firstWord = await args.pickResult('string');
* if (isOk(firstWord)) await message.channel.send(firstWord.value.toUpperCase()); // HELLO
* ```
*/
public async peekResult<T>(type: () => ArgumentResult<T>): Promise<Result<T, UserError>>;
/**
* Peeks the following parameter(s) without advancing the parser's state.
* Passing a function as a parameter allows for returning [[Args.pickResult]], [[Args.repeatResult]],
* or [[Args.restResult]]; otherwise, passing the custom argument or the argument type with options
* will use [[Args.pickResult]] and only peek a single argument.
* @param type The function, custom argument, or argument name.
* @wxample
* ```typescript
* // !reverseandscreamfirst sapphire project
* const resolver = Args.make((arg) => ok(arg.split('').reverse().join('')));
*
* const peekedWord = await args.peekResult(resolver);
* if (isOk(peekedWord)) await message.channel.send(peekedWord.value); // erihppas
*
* const firstWord = await args.pickResult('string');
* if (isOk(firstWord)) await message.channel.send(firstWord.value.toUpperCase()); // SAPPHIRE
* ```
*/
public async peekResult<T>(type: IArgument<T>, options?: ArgOptions): Promise<Result<T, UserError>>;
/**
* Peeks the following parameter(s) without advancing the parser's state.
* Passing a function as a parameter allows for returning [[Args.pickResult]], [[Args.repeatResult]],
* or [[Args.restResult]]; otherwise, passing the custom argument or the argument type with options
* will use [[Args.pickResult]] and only peek a single argument.
* @param type The function, custom argument, or argument name.
* @example
* ```typescript
* // !datethenaddtwo 1608867472611
* const date = await args.peekResult('date');
* if (isOk(date)) await message.channel.send(
* `Your date (in UTC): ${date.value.toUTCString()}`
* ); // Your date (in UTC): Fri, 25 Dec 2020 03:37:52 GMT
*
* const result = await args.pickResult('number', { maximum: Number.MAX_SAFE_INTEGER - 2 });
* if (isOk(result)) await message.channel.send(`Your number plus two: ${result.value + 2}`); // Your number plus two: 1608867472613
* ```
*/
public async peekResult<K extends keyof ArgType>(
type: (() => ArgumentResult<ArgType[K]>) | K,
options?: ArgOptions
): Promise<Result<ArgType[K], UserError>>;

public async peekResult<K extends keyof ArgType>(
type: (() => ArgumentResult<ArgType[K]>) | K,
options: ArgOptions = {}
): Promise<Result<ArgType[K], UserError>> {
this.save();
const result = typeof type === 'function' ? await type() : await this.pickResult(type, options);
this.restore();
return result;
}

/**
* Similar to [[Args.peekResult]] but returns the value on success, throwing otherwise.
* @param type The function, custom argument, or argument name.
* @example
* ```typescript
* // !bigintsumthensquarefirst 25 50 75
* const resolver = Args.make((arg) => {
* try {
* return ok(BigInt(arg));
* } catch {
* return err(new UserError('InvalidBigInt', 'You must specify a valid number for a bigint.'));
* }
* });
*
* const peeked = await args.peek(() => args.repeatResult(resolver));
* await message.channel.send(`Sum: **${peeked.reduce((x, y) => x + y, 0)}**`); // Sum: 150
*
* const first = await args.pick(resolver);
* await message.channel.send(`First bigint squared: ${first**2n}`); // First bigint squared: 625
* ```
*/
public async peek<T>(type: () => ArgumentResult<T>): Promise<T>;
/**
* Similar to [[Args.peekResult]] but returns the value on success, throwing otherwise.
* @param type The function, custom argument, or argument name.
* @example
* ```typescript
* // !createdat 730159185517477900
* const snowflakeResolver = Args.make((arg) =>
* SnowflakeRegex.test(arg) ? ok(BigInt(arg)) : err(new UserError('InvalidSnowflake', 'You must specify a valid snowflake.'));
* );
*
* const snowflake = await args.peek(snowflakeResolver);
* const timestamp = Number((snowflake >> 22n) + DiscordSnowflake.Epoch);
* const createdAt = new Date(timestamp);
*
* await message.channel.send(
* `The snowflake ${snowflake} was registered on ${createdAt.toUTCString()}.`
* ); // The snowflake 730159185517477900 was registered on Tue, 07 Jul 2020 20:31:55 GMT.
*
* const id = await args.pick('string');
* await message.channel.send(`Your ID, reversed: ${id.split('').reverse().join('')}`); // Your ID, reversed: 009774715581951037
* ```
*/
public async peek<T>(type: IArgument<T>, options?: ArgOptions): Promise<T>;
/**
* Similar to [[Args.peekResult]] but returns the value on success, throwing otherwise.
* @param type The function, custom argument, or argument name.
* @example
* ```typescript
* // !messagelink https://discord.com/channels/737141877803057244/737142209639350343/791843123898089483
* const remoteMessage = await args.peek('message');
* await message.channel.send(
* `${remoteMessage.author.tag}: ${remoteMessage.content}`
* ); // RealShadowNova#7462: Yeah, Sapphire has been a great experience so far, especially being able to help and contribute.
*
* const url = await args.pick('hyperlink');
* await message.channel.send(`Hostname: ${url.hostname}`); // Hostname: discord.com
* ```
*/
public async peek<K extends keyof ArgType>(type: (() => ArgumentResult<ArgType[K]>) | K, options?: ArgOptions): Promise<ArgType[K]>;
public async peek<K extends keyof ArgType>(type: (() => ArgumentResult<ArgType[K]>) | K, options?: ArgOptions): Promise<ArgType[K]> {
const result = await this.peekResult(type, options);
if (isOk(result)) return result.value;
throw result.error;
}

/**
* Checks if one or more flag were given.
* @param keys The name(s) of the flag.
Expand Down