Home Manual Reference Source Repository

src/commands/registry.js

'use babel';
'use strict';

import EventEmitter from 'events';
import Module from './module';
import CommandBuilder from './builder';

/** Handles registration and searching of commands and modules */
export default class CommandRegistry extends EventEmitter {
	/** @param {?Logger} [logger] - The logger to use  */
	constructor(logger) {
		super();

		/** @type {?Logger} */
		this.logger = logger || null;
		/** @type {Command[]} */
		this.commands = [];
		/** @type {Module[]} */
		this.modules = [];
	}

	/**
	 * Registers a single command
	 * @param {Command} command - The command to register
	 * @see {@link CommandRegistry#registerCommands}
	 */
	registerCommand(command) {
		this.registerCommands([command]);
	}

	/**
	 * Registers multiple commands
	 * @param {Command[]} commands - The commands to register
	 * @emits commandRegister When a command is registered, with the command and registry passed
	 */
	registerCommands(commands) {
		if(!Array.isArray(commands)) throw new TypeError('Commands must be an array.');
		for(let command of commands) {
			if(command instanceof CommandBuilder) command = command.command;

			// Make sure there aren't any conflicts
			if(this.commands.some(cmd => cmd.name === command.name)) throw new Error(`A command with the name "${command.name}"" is already registered.`);
			const module = this.modules.find(mod => mod.id === command.module);
			if(!module) throw new Error(`Module "${command.module}" is not registered.`);
			if(module.commands.some(cmd => cmd.memberName === command.memberName)) throw new Error(`A command with the member name "${command.memberName}" is already registered in ${module.id}`);

			// Make sure there aren't any conflicts for the aliases, and add dehyphenated aliases
			if(command.name.includes('-')) command.aliases.push(command.name.replace(/-/g, ''));
			for(const alias of command.aliases) {
				if(this.commands.some(cmd => cmd.aliases.some(ali => ali === alias))) throw new Error(`A command with the alias "${alias}" is already registered.`);
				if(alias.includes('-')) command.aliases.push(alias.replace(/-/g, ''));
			}

			// Add the command
			module.commands.push(command);
			this.commands.push(command);
			if(this.logger) this.logger.verbose(`Registered command ${command.module}:${command.memberName}.`);
			this.emit('commandRegister', command, this);
		}
	}

	/**
	 * Registers a single module
	 * @param {Module} module - The module to register
	 * @see {@link CommandRegistry#registerModules}
	 */
	registerModule(module) {
		this.registerModules([module]);
	}

	/**
	 * Registers multiple modules
	 * @param {Module[]} modules - The modules to register
	 * @emits moduleRegister When a module is registered, with the module and registry passed
	 */
	registerModules(modules) {
		if(!Array.isArray(modules)) throw new TypeError('Modules must be an array.');
		for(let module of modules) {
			if(!(module instanceof Module)) throw new TypeError('Module must be an instance of Module.');
			if(this.modules.some(mod => mod.name === module.name)) throw new Error(`Module "${module.name}"" is already registered.`);
			this.modules.push(module);
			if(this.logger) this.logger.verbose(`Registered module ${module.id}.`);
			this.emit('moduleRegister', module, this);
		}
	}

	/**
	 * Finds all commands that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @param {Message} [message] - The message to check usability against
	 * @return {Command[]} All commands that are found
	 */
	findCommands(searchString = null, message = null) {
		if(!searchString) return message ? this.commands.filter(cmd => cmd.isUsable(message)) : this.commands;

		// Find all matches
		const lowercaseSearch = searchString.toLowerCase();
		const matchedCommands = this.commands.filter(cmd =>
			cmd.name.includes(lowercaseSearch)
			|| (cmd.aliases && cmd.aliases.some(ali => ali.includes(lowercaseSearch)))
			|| `${cmd.module}:${cmd.memberName}` === lowercaseSearch
		);

		// See if there's an exact match
		for(const command of matchedCommands) {
			if(command.name === lowercaseSearch || (command.aliases && command.aliases.some(ali => ali === lowercaseSearch))) return [command];
		}

		return matchedCommands;
	}

	/**
	 * Finds all modules that match the search string
	 * @param {string} [searchString] - The string to search for
	 * @return {Module[]} All modules that are found
	 */
	findModules(searchString = null) {
		if(!searchString) return this.modules;

		// Find all matches
		const lowercaseSearch = searchString.toLowerCase();
		const matchedModules = this.modules.filter(mod => mod.name.includes(lowercaseSearch) || mod.id.includes(lowercaseSearch));

		// See if there's an exact match
		for(const module of matchedModules) {
			if(module.name.toLowerCase() === lowercaseSearch || module.id === lowercaseSearch) return [module];
		}

		return matchedModules;
	}
}