Compare commits

..

2 Commits

Author SHA1 Message Date
57313488b4
v1.5.0
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-12 17:25:36 +03:00
97561a4b7f
Refactor parser to allow parsing of just arguments 2023-05-12 17:24:59 +03:00
3 changed files with 63 additions and 21 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@navy.gif/commandparser", "name": "@navy.gif/commandparser",
"version": "1.4.5", "version": "1.5.0",
"description": "Parser meant to parse commands and their options for discord bots", "description": "Parser meant to parse commands and their options for discord bots",
"author": "Navy.gif", "author": "Navy.gif",
"license": "MIT", "license": "MIT",

View File

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