Refactor parser to allow parsing of just arguments

This commit is contained in:
Erik 2023-05-12 17:24:59 +03:00
parent aa7fe8643c
commit 97561a4b7f
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
2 changed files with 62 additions and 20 deletions

View File

@ -26,6 +26,12 @@ type ParserOptions = {
resolver?: IResolver<unknown, unknown, unknown, unknown, unknown>
}
type ParseOptions = {
prefix?: string,
commandFilter?: (cmd: Command) => boolean,
guild?: unknown
}
class ParseError extends Error {}
const flagReg = /(?:^| )(?<flag>(?:--[a-z0-9]{3,})|(?:-[a-z]{1,2}))(?:$| )/iu;
@ -75,8 +81,12 @@ class Parser extends EventEmitter {
*/
// eslint-disable-next-line @typescript-eslint/ban-types
async parseMessage (
message: string, prefix = this.prefix,
guild?: unknown, commandFilter?: (command: Command) => boolean
message: string,
{
prefix = this.prefix,
guild,
commandFilter
}: ParseOptions = {}
): Promise<ParseResult | null> {
if (!message.startsWith(prefix) || !message.length)
@ -134,11 +144,20 @@ class Parser extends EventEmitter {
}
const activeCommand = subcommand || command;
const flags = activeCommand.options.filter((opt) => opt.flag);
params = Util.parseQuotes(params.join(' ')).map(([ str ]: (string | boolean)[]): string => str.toString());
this.debug(`Given params: "${params.join('", "')}"`);
parseResult.args = await this.parseOptions(params, activeCommand.options, guild);
return parseResult;
}
async parseOptions (params: string[], options: CommandOption[], guild?: unknown) {
let currentFlag = null;
const flags = options.filter((opt) => opt.flag);
const args: ArgsResult = {};
// Parse flags
for (let index = 0; index < params.length;) {
@ -202,12 +221,13 @@ class Parser extends EventEmitter {
}
this.debug(`Params after parsing "${params.join('", "')}"`);
const options = activeCommand.options.filter((opt) => !opt.flag && (opt.type !== OptionType.STRING || opt.choices.length));
const stringOpts = activeCommand.options.filter((opt) => !opt.flag && !opt.choices.length && opt.type === OptionType.STRING);
// Non-string options
const nStrOptions = options.filter((opt) => !opt.flag && (opt.type !== OptionType.STRING || opt.choices.length));
const stringOpts = options.filter((opt) => !opt.flag && !opt.choices.length && opt.type === OptionType.STRING);
this.debug(`Parsing non-flag options`);
// Parse out non-flag options
for (const option of options) { // String options are parsed separately at the end
for (const option of nStrOptions) { // String options are parsed separately at the end
if (!params.some((param) => param !== null)) {
this.debug(`No potential values left in params`);
break;
@ -279,26 +299,26 @@ class Parser extends EventEmitter {
}
// This part is obsolete now, I think, the string option checks the choice value
for (const arg of Object.values(args)) {
if (!arg.choices.length)
continue;
if (!arg.choices.some((choice) => {
if (typeof arg.value === 'string' && typeof choice === 'string')
return arg.value.toLowerCase() === choice.toLowerCase();
return arg.value === choice;
}))
throw new ParseError(`Invalid choice: ${arg.name} value must be one of ${arg.choices.join(', ')}`); // return { error: true, index: 'O_COMMANDHANDLER_INVALID_CHOICE', params: { option: arg.name, value: arg.value, choices: arg.choices.map((c) => c).join('`, `') } };
}
// for (const arg of Object.values(args)) {
// if (!arg.choices.length)
// continue;
// if (!arg.choices.some((choice) => {
// if (typeof arg.value === 'string' && typeof choice === 'string')
// return arg.value.toLowerCase() === choice.toLowerCase();
// return arg.value === choice;
// }))
// throw new ParseError(`Invalid choice: ${arg.name} value must be one of ${arg.choices.join(', ')}`); // return { error: true, index: 'O_COMMANDHANDLER_INVALID_CHOICE', params: { option: arg.name, value: arg.value, choices: arg.choices.map((c) => c).join('`, `') } };
// }
this.debug(`Making sure required options were given.`);
for (const req of activeCommand.options.filter((opt) => opt.required))
for (const req of options.filter((opt) => opt.required))
if (!args[req.name])
throw new ParseError(`${req.name} is a required option`);
if (strings.length)
throw new ParseError(`Unrecognised option(s): "${strings.join('", "')}"`);// return { error: true, index: 'O_COMMANDHANDLER_UNRECOGNISED_OPTIONS', params: { opts: strings.join('`, `') } };
return parseResult;
throw new ParseError(`Unrecognised option(s): "${strings.join('", "')}"`);
return args;
}

View File

@ -95,4 +95,26 @@ test('Command Parser', async () => {
await expect(parser.parseMessage('create code')).resolves.toHaveProperty('command.name', 'create');
await expect(parser.parseMessage('create code 1')).resolves.toHaveProperty('args.amount.value', 1);
});
test('Choice option', async () => {
const command = new Command({
name: 'create',
options: [{
name: 'registration-code',
aliases: ['code'],
type: OptionType.SUB_COMMAND,
options: [{
name: 'amount',
type: OptionType.STRING,
choices: ['one', 'two']
}]
}]
});
const parser = new Parser({ commands: [command], prefix: '' });
await expect(parser.parseMessage('create code 1')).rejects.toThrow();
await expect(parser.parseMessage('create code one')).resolves.toHaveProperty('args.amount.value', 'one');
});