Compare commits
2 Commits
01e4eaca5e
...
c669d60428
Author | SHA1 | Date | |
---|---|---|---|
c669d60428 | |||
eb4faa489b |
12
babel.config.cjs
Normal file
12
babel.config.cjs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
targets: {
|
||||||
|
node: 'current'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [[ '@babel/preset-env', { targets: { node: 'current' } }]]
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@navy.gif/commandparser",
|
"name": "@navy.gif/commandparser",
|
||||||
"version": "1.4.3",
|
"version": "1.4.4",
|
||||||
"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",
|
||||||
@ -34,8 +34,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/declareTypes.js",
|
"build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/declareTypes.js",
|
||||||
"test": "tsc && jest",
|
"test": "yarn build && jest",
|
||||||
"release": "tsc && yarn publish",
|
"release": "yarn build && yarn publish",
|
||||||
"lint": "eslint --fix"
|
"lint": "eslint --fix"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,6 @@ class Parser extends EventEmitter {
|
|||||||
return command || null;
|
return command || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
@ -161,9 +160,12 @@ class Parser extends EventEmitter {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const short = match.groups.flag.startsWith('-') && !match.groups.flag.startsWith('--');
|
||||||
const _flag = match.groups.flag.replace(/--?/u, '').toLowerCase();
|
const _flag = match.groups.flag.replace(/--?/u, '').toLowerCase();
|
||||||
let aliased = false;
|
let aliased = false;
|
||||||
const flag = flags.find((f) => {
|
const flag = flags.find((f) => {
|
||||||
|
if (short)
|
||||||
|
return f.name[0] === _flag;
|
||||||
aliased = f.valueAsAlias && f.choices.some((c) => c === _flag);
|
aliased = f.valueAsAlias && f.choices.some((c) => c === _flag);
|
||||||
return f.name === _flag || aliased;
|
return f.name === _flag || aliased;
|
||||||
});
|
});
|
||||||
@ -192,7 +194,7 @@ class Parser extends EventEmitter {
|
|||||||
if (flag.choices.length) {
|
if (flag.choices.length) {
|
||||||
throw new ParseError(`Invalid choice for ${flag.name}, Valid choices are ${flag.choices.join(', ')}.`);
|
throw new ParseError(`Invalid choice for ${flag.name}, Valid choices are ${flag.choices.join(', ')}.`);
|
||||||
}
|
}
|
||||||
throw new ParseError(`Failed to parse value for ${flag.name}, expected value type: ${flag.type}`); // return { option: flag, ...result.removed };
|
throw new ParseError(`Failed to parse value for ${flag.name}, expected value type: ${OptionType[flag.type]}`); // return { option: flag, ...result.removed };
|
||||||
}
|
}
|
||||||
this.debug(`Cleaning up params after ${flag.name}`);
|
this.debug(`Cleaning up params after ${flag.name}`);
|
||||||
for (const r of result.removed)
|
for (const r of result.removed)
|
||||||
|
@ -22,15 +22,26 @@ abstract class Command implements ICommand {
|
|||||||
|
|
||||||
this.aliases = def.aliases || [];
|
this.aliases = def.aliases || [];
|
||||||
|
|
||||||
this.options = [];
|
// Add a built-in help flag to any non-subcommand option
|
||||||
|
this.options = [
|
||||||
|
new CommandOption({
|
||||||
|
name: 'help',
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
flag: true,
|
||||||
|
valueOptional: true,
|
||||||
|
defaultValue: true
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
for (const opt of def.options || []) {
|
for (const opt of def.options || []) {
|
||||||
if (opt instanceof CommandOption)
|
if (opt instanceof CommandOption)
|
||||||
this.options.push(opt);
|
this.options.push(opt);
|
||||||
else
|
else
|
||||||
this.options.push(new CommandOption(opt));
|
this.options.push(new CommandOption(opt));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.some(opt => [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type))
|
if (this.options.some(opt => [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type))
|
||||||
&& this.options.some(opt => ![ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type)))
|
&& this.options.some(opt => ![ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type) && opt.name !== 'help'))
|
||||||
throw new Error('Cannot have subcommand(group)s on the same level as an option');
|
throw new Error('Cannot have subcommand(group)s on the same level as an option');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import ICommandOption, { Choice, CommandOptionDefinition, DependsOnMode, OptionType, ParseResult } from "../interfaces/CommandOption.js";
|
import ICommandOption, { Choice, CommandOptionDefinition, DependsOnMode, OptionType, ParseResult } from "../interfaces/CommandOption.js";
|
||||||
import IResolver from "../interfaces/Resolver.js";
|
import IResolver from "../interfaces/Resolver.js";
|
||||||
|
|
||||||
|
const SUB = [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ];
|
||||||
class CommandOption implements ICommandOption {
|
class CommandOption implements ICommandOption {
|
||||||
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
@ -36,7 +37,18 @@ class CommandOption implements ICommandOption {
|
|||||||
this.flag = def.flag || false;
|
this.flag = def.flag || false;
|
||||||
|
|
||||||
this.options = [];
|
this.options = [];
|
||||||
|
if (SUB.includes(this.type))
|
||||||
|
this.options.push(new CommandOption({
|
||||||
|
name: 'help',
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
flag: true,
|
||||||
|
valueOptional: true,
|
||||||
|
defaultValue: true
|
||||||
|
}));
|
||||||
|
|
||||||
for (const opt of def.options || []) {
|
for (const opt of def.options || []) {
|
||||||
|
if (this.options.some(o => o.name === opt.name))
|
||||||
|
throw new Error(`An option by the name ${opt.name} already exists`);
|
||||||
if (opt instanceof CommandOption)
|
if (opt instanceof CommandOption)
|
||||||
this.options.push(opt);
|
this.options.push(opt);
|
||||||
else
|
else
|
||||||
@ -49,12 +61,17 @@ class CommandOption implements ICommandOption {
|
|||||||
throw new Error('Cannot have subcommand groups under a subcommand');
|
throw new Error('Cannot have subcommand groups under a subcommand');
|
||||||
if (this.type === OptionType.SUB_COMMAND && this.options.some(opt => opt.type === OptionType.SUB_COMMAND))
|
if (this.type === OptionType.SUB_COMMAND && this.options.some(opt => opt.type === OptionType.SUB_COMMAND))
|
||||||
throw new Error('Cannot nest subcommands');
|
throw new Error('Cannot nest subcommands');
|
||||||
if (![ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(this.type)
|
if (!SUB.includes(this.type)
|
||||||
&& this.options.some(opt => [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type)))
|
&& this.options.some(opt => SUB.includes(opt.type))
|
||||||
|
&& this.name !== 'help') // Allow the help flag at any level
|
||||||
throw new Error('Cannot have subcommand(group)s under an option');
|
throw new Error('Cannot have subcommand(group)s under an option');
|
||||||
|
|
||||||
if (this.options.some(opt => [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type))
|
// if sub commands and normal options exists together, throw an error
|
||||||
&& this.options.some(opt => ![ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type)))
|
// Checks for subcommands: if opt.type is a subcommand type
|
||||||
|
// console.log(this.options, this.options.some(opt => SUB.includes(opt.type)), this.options.some(opt => !SUB.includes(opt.type)), this.options.some(opt => !SUB.includes(opt.type) && opt.name !== 'help'));
|
||||||
|
if (this.options.some(opt => SUB.includes(opt.type))
|
||||||
|
// Check for non-subcommands: if opt.type is not a sub type
|
||||||
|
&& this.options.some(opt => !SUB.includes(opt.type) && opt.name !== 'help'))
|
||||||
throw new Error('Cannot have subcommand(group)s on the same level as an option');
|
throw new Error('Cannot have subcommand(group)s on the same level as an option');
|
||||||
|
|
||||||
this.choices = def.choices || [];
|
this.choices = def.choices || [];
|
||||||
@ -81,13 +98,12 @@ class CommandOption implements ICommandOption {
|
|||||||
if (!this[OptionType[this.type]])
|
if (!this[OptionType[this.type]])
|
||||||
throw new Error(`Missing parsing function for ${this.type}`);
|
throw new Error(`Missing parsing function for ${this.type}`);
|
||||||
if (typeof this[OptionType[this.type]] !== 'function')
|
if (typeof this[OptionType[this.type]] !== 'function')
|
||||||
throw new Error(`Expected function type for memberr ${OptionType[this.type]}`);
|
throw new Error(`Expected function type for member ${OptionType[this.type]}`);
|
||||||
this.guild = guild;
|
this.guild = guild;
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
const func = this[OptionType[this.type]] as () => Promise<ParseResult>;
|
||||||
const func = this[OptionType[this.type]] as Function;
|
|
||||||
const result = await func.bind(this)();
|
const result = await func.bind(this)();
|
||||||
this.value = result.value;
|
this.value = result.value;
|
||||||
return result as ParseResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
get plural (): boolean {
|
get plural (): boolean {
|
||||||
@ -149,14 +165,20 @@ class CommandOption implements ICommandOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected BOOLEAN () {
|
protected BOOLEAN () {
|
||||||
if (!this.rawValue)
|
if (!this.rawValue && !this.valueOptional)
|
||||||
return { error: true };
|
return { error: true };
|
||||||
const boolean = this.resolver?.resolveBoolean(this.rawValue[0]) || null;
|
|
||||||
|
const boolean = this.rawValue ? this.resolver?.resolveBoolean(this.rawValue[0]) : this.defaultValue;
|
||||||
|
|
||||||
if (boolean === null && this.valueOptional)
|
if (boolean === null && this.valueOptional)
|
||||||
return { value: this.defaultValue, removed: [] };
|
return { value: this.defaultValue, removed: [] };
|
||||||
else if (boolean === null)
|
else if (boolean === null)
|
||||||
return { error: true };
|
return { error: true };
|
||||||
return { value: boolean, removed: [ this.rawValue[0] ] };
|
|
||||||
|
const removed = [];
|
||||||
|
if (this.rawValue)
|
||||||
|
removed.push(this.rawValue[0]);
|
||||||
|
return { value: boolean, removed };
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,8 @@ type DependsOnMode = 'AND' | 'OR';
|
|||||||
|
|
||||||
type ParseResult = {
|
type ParseResult = {
|
||||||
error: boolean,
|
error: boolean,
|
||||||
removed: string[]
|
removed: string[],
|
||||||
|
value: never
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandOptionDefinition = {
|
type CommandOptionDefinition = {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Parser, Command, CommandOption, OptionType } from '../build/esm'
|
// import { Parser, Command, CommandOption, OptionType } from '../build/esm'
|
||||||
|
const {Parser, Command, CommandOption, OptionType} = require('../build/cjs')
|
||||||
|
|
||||||
const opt = new CommandOption({
|
const opt = new CommandOption({
|
||||||
name: 'text',
|
name: 'text',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Parser, Command, OptionType } from '../build/esm/index';
|
// import { Parser, Command, OptionType } from '../build/esm/index';
|
||||||
|
const { Parser, Command, OptionType } = require('../build/cjs');
|
||||||
|
|
||||||
test('Command definition', () => {
|
test('Command definition', () => {
|
||||||
expect(() => new Command()).toThrow();
|
expect(() => new Command()).toThrow();
|
||||||
@ -42,6 +43,34 @@ test('Command definition', () => {
|
|||||||
}] })).not.toThrow();
|
}] })).not.toThrow();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Command Parser', async () => {
|
||||||
|
const command = new Command({
|
||||||
|
name: 'create',
|
||||||
|
options: [{
|
||||||
|
name: 'registration-code',
|
||||||
|
aliases: ['code'],
|
||||||
|
type: OptionType.SUB_COMMAND,
|
||||||
|
options: [{
|
||||||
|
name: 'amount',
|
||||||
|
flag: true,
|
||||||
|
type: OptionType.INTEGER,
|
||||||
|
defaultValue: 1,
|
||||||
|
valueOptional: true
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
const parser = new Parser({ commands: [command], prefix: '', debug: true });
|
||||||
|
|
||||||
|
await expect(parser.parseMessage('create')).rejects.toThrow();
|
||||||
|
// Cannot have an option at the sub-command level
|
||||||
|
await expect(parser.parseMessage('create test')).rejects.toThrow();
|
||||||
|
|
||||||
|
await expect(parser.parseMessage('create code')).resolves.toHaveProperty('command.name', 'create');
|
||||||
|
|
||||||
|
await expect(parser.parseMessage('create code -a 1')).resolves.toHaveProperty('args.amount.value', 1)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
test('Command Parser', async () => {
|
test('Command Parser', async () => {
|
||||||
const command = new Command({
|
const command = new Command({
|
||||||
name: 'create',
|
name: 'create',
|
||||||
|
21
tests/playground.js
Normal file
21
tests/playground.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Parser, Command, OptionType } from '../build/esm/index.js';
|
||||||
|
|
||||||
|
const command = new Command({
|
||||||
|
name: 'create',
|
||||||
|
options: [{
|
||||||
|
name: 'registration-code',
|
||||||
|
aliases: ['code'],
|
||||||
|
type: OptionType.SUB_COMMAND,
|
||||||
|
options: [{
|
||||||
|
name: 'amount',
|
||||||
|
flag: true,
|
||||||
|
type: OptionType.INTEGER,
|
||||||
|
defaultValue: 1,
|
||||||
|
valueOptional: true
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
const parser = new Parser({ commands: [command], prefix: '', debug: true });
|
||||||
|
parser.on('debug', console.log)
|
||||||
|
console.log(await parser.parseMessage('create code -a 1'));
|
||||||
|
console.log(await parser.parseMessage('create code --help'))
|
Loading…
Reference in New Issue
Block a user