Skip to content
This repository has been archived by the owner on Apr 16, 2023. It is now read-only.

Commit

Permalink
Add support for aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
BartArys committed Jun 2, 2020
1 parent 7016511 commit 93c2ac0
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 40 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.2.0

## Additions

* Added support for aliases

#0.1.1

## Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package commands.example
import com.gitlab.kordlib.kordx.commands.annotation.AutoWired
import com.gitlab.kordlib.kordx.commands.annotation.ModuleName
import com.gitlab.kordlib.kordx.commands.argument.primitive.DoubleArgument
import com.gitlab.kordlib.kordx.commands.model.module.command
import com.gitlab.kordlib.kordx.commands.kord.module.commands
import com.gitlab.kordlib.kordx.commands.model.command.invoke

Expand All @@ -17,10 +16,12 @@ fun simpleMath() = commands {

command("add") {

alias("+", "combine")

invoke(DoubleArgument, DoubleArgument) { a, b ->
respond("${a + b}")
}

}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.gitlab.kordlib.kordx.commands.model.command

/**
* Information on the command's relation to other aliases.
* A command can either be a [Parent] of aliases,
* a [Child] alias of another command,
* or [not][None] be part of any alias structure.
*/
sealed class AliasInfo<T : CommandEvent> {

/**
* The command is not part of any alias structure. It has neither a parent nor children.
*/
class None<T : CommandEvent> : AliasInfo<T>()

/**
* The command is a parent, it contains [children] with the same functionality under a different [Command.name].
*
* @param childrenNames the names of the child commands defined in [CommandBuilder.alias].
* @param commands A map of all registered commands, these are used to resolve the [children].
*/
class Parent<T : CommandEvent>(
private val childrenNames: Set<String>,
private val commands: Map<String, Command<*>>
) : AliasInfo<T>() {

/**
* The child commands defined by invoking [CommandBuilder.alias]. These are identical copies of the parent
* with a different [Command.name].
*/
@Suppress("UNCHECKED_CAST")
val children: List<Command<T>>
get() = childrenNames.map { commands.getValue(it) as Command<T> }
}

/**
* The command is a child, it contains a [parent] with the same functionality under a different [Command.name].
*
* @param parentName The name of the parent command that created this command.
* @param commands A map of all registered commands, these are used to resolve the [parent].
*/
class Child<T : CommandEvent>(
private val parentName: String,
private val commands: Map<String, Command<*>>
) : AliasInfo<T>() {

/**
* The parent command that defined this alias, it is an identical copy of this command with a different name.
*/
@Suppress("UNCHECKED_CAST")
val parent: Command<T>
get() = commands[parentName] as Command<T>

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ import org.koin.core.KoinComponent
* the values generated by these are expected in the [invoke] function.
*
* @param modules All modules currently know by the [CommandProcessor], with their [name][Module.name] as key.
* Used in combination with [moduleName] to find the Commands' [module].
* Used in combination with [moduleName] to find the [Command.module].
*
* @param preconditions The preconditions the command expects to have been passed before being invoked.
*
* @param koin Koin instance used for dependency injection.
*
* @param aliasInfo Information about the alias status of the command.
*
* @param execution The behavior executed on [invoke].
*/
data class CommandData<T: CommandEvent>(
Expand All @@ -41,6 +43,7 @@ data class CommandData<T: CommandEvent>(
val modules: Map<String, Module>,
val preconditions: List<Precondition<T>>,
val koin: Koin,
val aliasInfo: AliasInfo<T>,
val execution: suspend (T, List<*>) -> Unit
)

Expand Down Expand Up @@ -85,6 +88,11 @@ class Command<T : CommandEvent>(val data: CommandData<T>) : KoinComponent {
*/
val module: Module get() = modules[data.moduleName] ?: error("expected module")

/**
* Information about the alias status of the command.
*/
val aliasInfo: AliasInfo<T> get() = data.aliasInfo

/**
* Invokes the command with a [context] and [arguments], suspending until its execution ends.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.gitlab.kordlib.kordx.commands.model.command

import com.gitlab.kordlib.kordx.commands.argument.Argument
import com.gitlab.kordlib.kordx.commands.model.precondition.Precondition
import com.gitlab.kordlib.kordx.commands.internal.CommandsBuilder
import com.gitlab.kordlib.kordx.commands.model.module.Module
import com.gitlab.kordlib.kordx.commands.model.exception.ConflictingAliasException
import com.gitlab.kordlib.kordx.commands.model.metadata.MutableMetadata
import com.gitlab.kordlib.kordx.commands.model.processor.ProcessorContext
import org.koin.core.Koin
import com.gitlab.kordlib.kordx.commands.model.precondition.Precondition
import com.gitlab.kordlib.kordx.commands.model.precondition.precondition
import com.gitlab.kordlib.kordx.commands.model.processor.BuildEnvironment
import com.gitlab.kordlib.kordx.commands.model.processor.ProcessorContext

private val spaceRegex = Regex("\\s")

Expand All @@ -18,6 +18,7 @@ private val spaceRegex = Regex("\\s")
* @param context The context this command operates in, used as a type token.
* @param metaData The metadata for this command.
* @param preconditions Preconditions for this command.
* @param aliases the aliases that will be created for this command.
*
* @throws IllegalArgumentException when [name] contains whitespace characters.
*/
Expand All @@ -27,7 +28,8 @@ class CommandBuilder<S, A, COMMANDCONTEXT : CommandEvent>(
val moduleName: String,
val context: ProcessorContext<S, A, COMMANDCONTEXT>,
val metaData: MutableMetadata = MutableMetadata(),
val preconditions: MutableList<Precondition<COMMANDCONTEXT>> = mutableListOf()
val preconditions: MutableList<Precondition<COMMANDCONTEXT>> = mutableListOf(),
val aliases: MutableSet<String> = mutableSetOf()
) {

init {
Expand All @@ -45,21 +47,38 @@ class CommandBuilder<S, A, COMMANDCONTEXT : CommandEvent>(
var arguments: List<Argument<*, A>> = emptyList()

/**
* Builds the command from the current data.
* Adds the [aliases] to this command's aliases.
*/
fun build(modules: Map<String, Module>, koin: Koin): Command<COMMANDCONTEXT> {
fun alias(vararg aliases: String) = this.aliases.addAll(aliases)

/**
* Builds the command from the current data and adds it to the [environment].
*/
fun build(environment: BuildEnvironment): Set<Command<COMMANDCONTEXT>> {
if (name in aliases) throw ConflictingAliasException(this, name)

val data = CommandData(
name,
moduleName,
context,
metaData.toMetaData(),
arguments,
modules,
environment.modules,
preconditions,
koin,
environment.koin,
if (aliases.isEmpty()) AliasInfo.None()
else AliasInfo.Parent(aliases, environment.commands),
execution
)
return Command(data)

val children = aliases.map {
data.copy(name = it, aliasInfo = AliasInfo.Child(name, environment.commands))
}

val commands = (children + data).map { Command(it) }

commands.forEach { environment.addCommand(it) }
return commands.toSet()
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")

package com.gitlab.kordlib.kordx.commands.model.exception

import com.gitlab.kordlib.kordx.commands.model.command.Command
import com.gitlab.kordlib.kordx.commands.model.command.CommandBuilder
import com.gitlab.kordlib.kordx.commands.model.module.Module

/**
* Base class for all kordx.commands exceptions.
*/
abstract class KordxException(override val message: String?) : Exception()

/**
* Exception thrown when an [CommandBuilder] detected an alias that is the same as the [CommandBuilder.name].
*
* Kordx.commands expects all command names to be unique, this includes aliases.
*
* @param builder The builder that encountered the offending alias.
* @param aliasName The offending alias.
*/
class ConflictingAliasException(
val builder: CommandBuilder<*, *, *>,
val aliasName: String
) : KordxException("""
Command '${builder.name}' has an alias '$aliasName' that that is identical to the command name.
""".trimIndent())

/**
* Exception thrown when an [Module] was created with the same name of another module.
* Kordx.commands expects all module names to be unique.
*
* @param module The module that was already registered.
* @param conflictingModule The module that was going to be added under the same name.
*/
class DuplicateModuleNameException(
val module: Module,
val conflictingModule: Module
) : KordxException("""
Module '${conflictingModule.name}' was registered, but another module with the same name already exists.
""".trimIndent())

/**
* Exception thrown when an [Command] was created with the same name of another command.
* Kordx.commands expects all command names to be unique.
*
* @param command The command that was already registered.
* @param conflictingCommand The command that was going to be added under the same name.
*/
class DuplicateCommandNameException(
val command: Command<*>,
val conflictingCommand: Command<*>
) : KordxException("""
Command '${conflictingCommand.name}' was registered, but another command with the same name already exists.
""".trimIndent())
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.gitlab.kordlib.kordx.commands.model.command.Command
import com.gitlab.kordlib.kordx.commands.model.command.CommandBuilder
import com.gitlab.kordlib.kordx.commands.model.command.CommandEvent
import com.gitlab.kordlib.kordx.commands.model.metadata.MutableMetadata
import com.gitlab.kordlib.kordx.commands.model.processor.BuildEnvironment
import com.gitlab.kordlib.kordx.commands.model.processor.ProcessorContext
import org.koin.core.Koin

Expand Down Expand Up @@ -85,12 +86,17 @@ data class ModuleBuilder<S, A, C : CommandEvent>(


/**
* Builds the modules, adding it to the [modules].
* Builds the modules, adding it to the [environment].
*
* @throws IllegalStateException when a module with the same name already exists.
*/
fun build(modules: MutableMap<String, Module>, koin: Koin) {
check(!modules.containsKey(name)) { "a module with name $name is already present" }
modules[name] = Module(name, commands.mapValues { it.value.build(modules, koin) }, metadata.toMetaData())
fun build(environment: BuildEnvironment) {
val commands = commands.values
.flatMap { it.build(environment) }
.map { it.name to it }
.toMap()

val module = Module(name, commands, metadata.toMetaData())
environment.addModule(module)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.gitlab.kordlib.kordx.commands.model.processor

import com.gitlab.kordlib.kordx.commands.model.command.Command
import com.gitlab.kordlib.kordx.commands.model.exception.DuplicateCommandNameException
import com.gitlab.kordlib.kordx.commands.model.exception.DuplicateModuleNameException
import com.gitlab.kordlib.kordx.commands.model.module.Module
import org.koin.core.Koin

/**
* Container used to store all build [commands] and [modules] in a [CommandProcessor].
*
* @param koin The Koin instance for dependency injection post [CommandProcessor] build.
*/
data class BuildEnvironment(
val koin: Koin,
val modules: MutableMap<String, Module> = mutableMapOf(),
val commands: MutableMap<String, Command<*>> = mutableMapOf()
) {

/**
* Adds the [module] to the [modules].
*
* @throws DuplicateModuleNameException if a module with the same name is already registered.
*/
fun addModule(module: Module) {
if (modules.containsKey(module.name)) throw DuplicateModuleNameException(modules[module.name]!!, module)

modules[module.name] = module
}

/**
* Adds the [command] to the [commands].
*
* @throws DuplicateCommandNameException if a command with the same name is already registered.
*/
fun addCommand(command: Command<*>) {
if (commands.containsKey(command.name)) throw DuplicateCommandNameException(commands[command.name]!!, command)

commands[command.name] = command
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private val logger = KotlinLogging.logger { }
data class CommandProcessorData(
val filters: Map<ProcessorContext<*, *, *>, List<EventFilter<*>>>,
val preconditions: Map<ProcessorContext<*, *, *>, List<Precondition<out CommandEvent>>>,
var commands: Map<String, Command<out CommandEvent>>,
var environment: BuildEnvironment,
val prefix: Prefix,
val handlers: Map<ProcessorContext<*, *, *>, EventHandler<*>>,
var modifiers: List<ModuleModifier>,
Expand Down Expand Up @@ -63,7 +63,7 @@ class CommandProcessor(private val data: CommandProcessorData) : CoroutineScope
/**
* All commands in this processor.
*/
val commands: Map<String, Command<out CommandEvent>> get() = data.commands
val commands: Map<String, Command<out CommandEvent>> get() = data.environment.commands

/**
* The collection of prefix suppliers used to get the prefix for a certain [ProcessorContext].
Expand Down Expand Up @@ -168,29 +168,27 @@ class CommandProcessor(private val data: CommandProcessorData) : CoroutineScope
rebuild()
}

/**
* Adds the [modifiers] to the current modules and commands, rebuilding the processor in the process.
*
* > This is potentially a **very** expensive operation, as every command and module needs to be rebuild.
* If you need to temporarily disable/enable an entity
* consider doing so with an [EventFilter] or [Precondition] instead.
*/
suspend fun addModules(vararg modifiers: ModuleModifier) = edit {
data.modifiers += modifiers
rebuild()
}

private suspend fun rebuild() {
val container = ModuleContainer()
data.modifiers.forEach { it.apply(container) }
container.applyForEach()

val modules = commands.values.firstOrNull()?.modules ?: mutableMapOf()
val modifiableModules = modules as MutableMap<String, Module>
container.modules.values.forEach { it.build(modifiableModules, koin) }

val map: Map<String, Command<out CommandEvent>> = modifiableModules.values
.map { it.commands }
.fold(emptyMap()) { acc, map ->
map.keys.forEach {
require(it !in acc) {
"command $it is already registered in ${acc.getValue(it).module.name}"
}
}
acc + map
}
map.keys.forEach {
require(it !in commands) { "command $it is already registered ${commands[it]!!.module.name}" }
}
data.commands = map
val environment = BuildEnvironment(koin)
container.modules.values.forEach { it.build(environment) }

data.environment = environment
}

/**
Expand Down
Loading

0 comments on commit 93c2ac0

Please sign in to comment.