Merge pull request #136 from MierenManz/main
Argument parsing for commands
This commit is contained in:
		
						commit
						60e7c75113
					
				
					 5 changed files with 264 additions and 5 deletions
				
			
		
							
								
								
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -191,3 +191,4 @@ export { | |||
|   isVoiceChannel, | ||||
|   default as getChannelByType | ||||
| } from './src/utils/channel.ts' | ||||
| export * from "./src/utils/command.ts" | ||||
|  | @ -9,6 +9,7 @@ import { | |||
|   CommandsManager, | ||||
|   parseCommand | ||||
| } from './command.ts' | ||||
| import { parseArgs } from '../utils/command.ts' | ||||
| import { Extension, ExtensionsManager } from './extension.ts' | ||||
| 
 | ||||
| type PrefixReturnType = string | string[] | Promise<string | string[]> | ||||
|  | @ -239,7 +240,7 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|       client: this, | ||||
|       name: parsed.name, | ||||
|       prefix, | ||||
|       args: parsed.args, | ||||
|       args: parseArgs(command.args, parsed.args), | ||||
|       argString: parsed.argString, | ||||
|       message: msg, | ||||
|       author: msg.author, | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import { Collection } from '../utils/collection.ts' | |||
| import type { CommandClient } from './client.ts' | ||||
| import type { Extension } from './extension.ts' | ||||
| import { join, walk } from '../../deps.ts' | ||||
| 
 | ||||
| import type { Args } from '../utils/command.ts' | ||||
| export interface CommandContext { | ||||
|   /** The Client object */ | ||||
|   client: CommandClient | ||||
|  | @ -23,7 +23,7 @@ export interface CommandContext { | |||
|   /** Name of Command which was used */ | ||||
|   name: string | ||||
|   /** Array of Arguments used with Command */ | ||||
|   args: string[] | ||||
|   args: Record<string, unknown> | null | ||||
|   /** Complete Raw String of Arguments */ | ||||
|   argString: string | ||||
|   /** Guild which the command has called */ | ||||
|  | @ -46,7 +46,7 @@ export interface CommandOptions { | |||
|   /** Usage Example of Command, only Arguments (without Prefix and Name) */ | ||||
|   examples?: string | string[] | ||||
|   /** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */ | ||||
|   args?: number | boolean | string[] | ||||
|   args?: Args[] | ||||
|   /** Permissions(s) required by both User and Bot in order to use Command */ | ||||
|   permissions?: string | string[] | ||||
|   /** Permission(s) required for using Command */ | ||||
|  | @ -81,7 +81,7 @@ export class Command implements CommandOptions { | |||
|   extension?: Extension | ||||
|   usage?: string | string[] | ||||
|   examples?: string | string[] | ||||
|   args?: number | boolean | string[] | ||||
|   args?: Args[] | ||||
|   permissions?: string | string[] | ||||
|   userPermissions?: string | string[] | ||||
|   botPermissions?: string | string[] | ||||
|  |  | |||
							
								
								
									
										107
									
								
								src/utils/command.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/utils/command.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| interface MentionToRegex { | ||||
|   [key: string]: RegExp | ||||
|   mentionUser: RegExp | ||||
|   mentionRole: RegExp | ||||
|   mentionChannel: RegExp | ||||
| } | ||||
| 
 | ||||
| const mentionToRegex: MentionToRegex = { | ||||
|   mentionUser: /<@!?(\d{17,19})>/, | ||||
|   mentionRole: /<@&(\d{17,19})>/, | ||||
|   mentionChannel: /<#(\d{17,19})>/ | ||||
| } | ||||
| 
 | ||||
| export type CommandArgumentMatchTypes = | ||||
|   | 'flag' | ||||
|   | 'mentionUser' | ||||
|   | 'mentionRole' | ||||
|   | 'mentionChannel' | ||||
|   | 'content' | ||||
|   | 'rest' | ||||
| 
 | ||||
| export interface Args<T = unknown> { | ||||
|   name: string | ||||
|   match: CommandArgumentMatchTypes | ||||
|   defaultValue?: T | ||||
|   flag?: string | ||||
| } | ||||
| 
 | ||||
| export function parseArgs( | ||||
|   commandArgs: Args[] | undefined, | ||||
|   messageArgs: string[] | ||||
| ): Record<string, unknown> | null { | ||||
|   if (commandArgs === undefined) return null | ||||
| 
 | ||||
|   const messageArgsNullableCopy: Array<string | null> = [...messageArgs] | ||||
|   const args: Record<string, unknown> = {} | ||||
| 
 | ||||
|   for (const entry of commandArgs) { | ||||
|     switch (entry.match) { | ||||
|       case 'flag': | ||||
|         parseFlags(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|       case 'mentionUser': | ||||
|       case 'mentionRole': | ||||
|       case 'mentionChannel': | ||||
|         parseMention(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|       case 'content': | ||||
|         parseContent(args, entry, messageArgs) | ||||
|         break | ||||
|       case 'rest': | ||||
|         parseRest(args, entry, messageArgsNullableCopy) | ||||
|         break | ||||
|     } | ||||
|   } | ||||
|   return args | ||||
| } | ||||
| 
 | ||||
| function parseFlags( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   for (let i = 0; i < argsNullable.length; i++) { | ||||
|     if (entry.flag === argsNullable[i]) { | ||||
|       argsNullable[i] = null | ||||
|       args[entry.name] = true | ||||
|       break | ||||
|     } else args[entry.name] = entry.defaultValue ?? false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function parseMention( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   const regex = mentionToRegex[entry.match] | ||||
|   const index = argsNullable.findIndex( | ||||
|     (x) => typeof x === 'string' && regex.test(x) | ||||
|   ) | ||||
|   const regexMatches = regex.exec(argsNullable[index]!) | ||||
|   args[entry.name] = | ||||
|     regexMatches !== null | ||||
|       ? regexMatches[0].replace(regex, '$1') | ||||
|       : entry.defaultValue | ||||
|   argsNullable[index] = null | ||||
| } | ||||
| 
 | ||||
| function parseContent( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNonNullable: Array<string | null> | ||||
| ): void { | ||||
|   args[entry.name] = | ||||
|     argsNonNullable.length > 0 ? argsNonNullable : entry.defaultValue | ||||
| } | ||||
| 
 | ||||
| function parseRest( | ||||
|   args: Record<string, unknown>, | ||||
|   entry: Args, | ||||
|   argsNullable: Array<string | null> | ||||
| ): void { | ||||
|   const restValues = argsNullable.filter((x) => typeof x === 'string') | ||||
|   args[entry.name] = | ||||
|     restValues !== null ? restValues?.join(' ') : entry.defaultValue | ||||
| } | ||||
							
								
								
									
										150
									
								
								test/argsparser_test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								test/argsparser_test.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | |||
| import { Args, parseArgs } from '../src/utils/command.ts' | ||||
| import { | ||||
|   assertEquals, | ||||
|   assertNotEquals | ||||
| } from 'https://deno.land/std@0.95.0/testing/asserts.ts' | ||||
| 
 | ||||
| const commandArgs: Args[] = [ | ||||
|   { | ||||
|     name: 'originalMessage', | ||||
|     match: 'content' | ||||
|   }, | ||||
|   { | ||||
|     name: 'permaban', | ||||
|     match: 'flag', | ||||
|     flag: '--permanent', | ||||
|     defaultValue: true | ||||
|   }, | ||||
|   { | ||||
|     name: 'user', | ||||
|     match: 'mentionUser' | ||||
|   }, | ||||
|   { | ||||
|     name: 'reason', | ||||
|     match: 'rest', | ||||
|     defaultValue: 'ree' | ||||
|   } | ||||
| ] | ||||
| 
 | ||||
| const messageArgs1: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   '--permanent', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult1 = { | ||||
|   originalMessage: [ | ||||
|     '<@!708544768342229012>', | ||||
|     '--permanent', | ||||
|     'bye', | ||||
|     'bye', | ||||
|     'Skyler' | ||||
|   ], | ||||
|   permaban: true, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   only: false, | ||||
|   name: 'parse command arguments 1 (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs1) | ||||
|     assertEquals(result, expectedResult1) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const messageArgs2: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult2 = { | ||||
|   originalMessage: ['<@!708544768342229012>', 'bye', 'bye', 'Skyler'], | ||||
|   permaban: true, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments 2 (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs2) | ||||
|     assertEquals(result, expectedResult2) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const messageArgs3: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   'bye', | ||||
|   'Skyler' | ||||
| ] | ||||
| const expectedResult3 = { | ||||
|   permaban: false, | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye bye Skyler' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments default value (assertNotEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs, messageArgs3) | ||||
|     assertNotEquals(result, expectedResult3) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
| 
 | ||||
| const commandArgs2: Args[] = [ | ||||
|   { | ||||
|     name: 'user', | ||||
|     match: 'mentionUser' | ||||
|   }, | ||||
|   { | ||||
|     name: 'channel', | ||||
|     match: 'mentionChannel' | ||||
|   }, | ||||
|   { | ||||
|     name: 'role', | ||||
|     match: 'mentionRole' | ||||
|   }, | ||||
|   { | ||||
|     name: 'reason', | ||||
|     match: 'rest', | ||||
|     defaultValue: 'ree' | ||||
|   } | ||||
| ] | ||||
| 
 | ||||
| const messageArgs4: string[] = [ | ||||
|   '<@!708544768342229012>', | ||||
|   'bye', | ||||
|   '<#783319033730564098>', | ||||
|   '<@&836715188690092032>' | ||||
| ] | ||||
| const expectedResult4 = { | ||||
|   channel: '783319033730564098', | ||||
|   role: '836715188690092032', | ||||
|   user: '708544768342229012', | ||||
|   reason: 'bye' | ||||
| } | ||||
| 
 | ||||
| Deno.test({ | ||||
|   name: 'parse command arguments mentions (assertEquals)', | ||||
|   fn: () => { | ||||
|     const result = parseArgs(commandArgs2, messageArgs4) | ||||
|     assertEquals(result, expectedResult4) | ||||
|   }, | ||||
|   sanitizeOps: true, | ||||
|   sanitizeResources: true, | ||||
|   sanitizeExit: true | ||||
| }) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue