diff options
Diffstat (limited to 'src/main/kotlin/io/github/moulberry/notenoughupdates/util')
5 files changed, 405 insertions, 0 deletions
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/BrigadierRoot.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/BrigadierRoot.kt new file mode 100644 index 00000000..66008044 --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/BrigadierRoot.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 Linnea Gräf + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.CommandDispatcher +import com.mojang.brigadier.ParseResults +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.CommandNode +import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe +import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent +import io.github.moulberry.notenoughupdates.util.LRUCache +import net.minecraft.command.ICommandSender +import net.minecraftforge.client.ClientCommandHandler +import java.lang.RuntimeException +import java.util.* + +@NEUAutoSubscribe +object BrigadierRoot { + private val help: MutableMap<CommandNode<DefaultSource>, String> = IdentityHashMap() + var dispatcher = CommandDispatcher<DefaultSource>() + private set + val parseText = + LRUCache.memoize<Pair<ICommandSender, String>, ParseResults<DefaultSource>>({ (sender, text) -> + dispatcher.parse(text, sender) + }, 1) + + fun getHelpForNode(node: CommandNode<DefaultSource>): String? { + return help[node] + } + + fun setHelpForNode(node: CommandNode<DefaultSource>, helpText: String) { + if (node.command == null) { + RuntimeException("Warning: Setting help on node that cannot be executed. Will be ignored").printStackTrace() + } + help[node] = helpText + } + + + fun getAllUsages( + path: String, + node: CommandNode<ICommandSender>, + visited: MutableSet<CommandNode<ICommandSender>> = mutableSetOf() + ): Sequence<NEUBrigadierHook.Usage> = sequence { + if (node in visited) return@sequence + visited.add(node) + val redirect = node.redirect + if (redirect != null) { + yieldAll(getAllUsages(path, node.redirect, visited)) + visited.remove(node) + return@sequence + } + if (node.command != null) + yield(NEUBrigadierHook.Usage(path, getHelpForNode(node))) + node.children.forEach { + val nodeName = when (it) { + is ArgumentCommandNode<*, *> -> "<${it.name}>" + else -> it.name + } + yieldAll(getAllUsages("$path $nodeName", it, visited)) + } + visited.remove(node) + } + + + fun updateHooks() = registerHooks(ClientCommandHandler.instance) + + fun registerHooks(handler: ClientCommandHandler) { + val iterator = handler.commands.entries.iterator() + while (iterator.hasNext()) { + if (iterator.next().value is NEUBrigadierHook) + iterator.remove() + } + dispatcher = CommandDispatcher() + help.clear() + parseText.clearCache() + val event = RegisterBrigadierCommandEvent(this) + event.post() + event.hooks.forEach { + if (handler.commands.containsKey(it.commandName)) { + println("Could not register command ${it.commandName}") + } else { + handler.registerCommand(it) + } + } + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt new file mode 100644 index 00000000..14b6ed6e --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.LiteralMessage +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import java.util.concurrent.CompletableFuture + +class EnumArgumentType<T : Enum<T>>( + val values: List<T> +) : ArgumentType<T> { + companion object { + @JvmStatic + fun <T : Enum<T>> enum(values: Array<T>) = EnumArgumentType(values.toList()) + + inline fun <reified T : Enum<T>> enum() = enum(enumValues<T>()) + } + + override fun getExamples(): Collection<String> { + return values.map { it.name } + } + + override fun <S : Any?> listSuggestions( + context: CommandContext<S>, + builder: SuggestionsBuilder + ): CompletableFuture<Suggestions> { + + examples + .filter {builder.remaining.isBlank() || it.startsWith(builder.remaining, ignoreCase = true) } + .forEach { builder.suggest(it) } + return builder.buildFuture() + } + + private val invalidEnum = + SimpleCommandExceptionType(LiteralMessage("Expected one of: ${values.joinToString(", ")}")) + + override fun parse(reader: StringReader): T { + val enumName = reader.readString() + return values.find { enumName == it.name } + ?: throw invalidEnum.createWithContext(reader) + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt new file mode 100644 index 00000000..adfdae6a --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType + +object RestArgumentType : ArgumentType<String> { + override fun parse(reader: StringReader): String { + val remaining = reader.remaining + reader.cursor += remaining.length + return remaining + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt new file mode 100644 index 00000000..17203a4b --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 Linnea Gräf + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util.brigadier + +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.builder.ArgumentBuilder +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import com.mojang.brigadier.builder.RequiredArgumentBuilder +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.tree.ArgumentCommandNode +import com.mojang.brigadier.tree.CommandNode +import com.mojang.brigadier.tree.LiteralCommandNode +import io.github.moulberry.notenoughupdates.commands.dev.DevTestCommand +import io.github.moulberry.notenoughupdates.util.iterate +import net.minecraft.command.ICommandSender +import net.minecraft.util.ChatComponentText +import net.minecraft.util.IChatComponent +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.lang.reflect.TypeVariable + + +typealias DefaultSource = ICommandSender + + + +private fun normalizeGeneric(argument: Type): Class<*> { + return if (argument is Class<*>) { + argument + } else if (argument is TypeVariable<*>) { + normalizeGeneric(argument.bounds[0]) + } else if (argument is ParameterizedType) { + normalizeGeneric(argument.rawType) + } else { + Any::class.java + } +} + +data class TypeSafeArg<T : Any>(val name: String, val argument: ArgumentType<T>) { + val argClass by lazy { + argument.javaClass + .iterate<Class<in ArgumentType<T>>> { + it.superclass + } + .flatMap { + it.genericInterfaces.toList() + } + .filterIsInstance<ParameterizedType>() + .find { it.rawType == ArgumentType::class.java }!! + .let { + normalizeGeneric(it.actualTypeArguments[0]) + } + } + + @JvmName("getWithThis") + fun <S> CommandContext<S>.get(): T = + get(this) + + + fun <S> get(ctx: CommandContext<S>): T { + return ctx.getArgument(name, argClass) as T + } +} + +fun <T : ICommandSender, C : CommandContext<T>> C.reply(component: IChatComponent) { + source.addChatMessage(ChatComponentText("§e[NEU] ").appendSibling(component)) +} + +fun <T : ICommandSender, C : CommandContext<T>> C.reply(text: String, block: ChatComponentText.() -> Unit = {}) { + source.addChatMessage(ChatComponentText(text.split("\n").joinToString("\n") { "§e[NEU] $it" }).also(block)) +} + +operator fun <T : Any, C : CommandContext<*>> C.get(arg: TypeSafeArg<T>): T { + return arg.get(this) +} + + +fun <T : Any> argument( + name: String, + argument: ArgumentType<T>, + block: RequiredArgumentBuilder<DefaultSource, T>.(TypeSafeArg<T>) -> Unit +): RequiredArgumentBuilder<DefaultSource, T> = + RequiredArgumentBuilder.argument<DefaultSource, T>(name, argument).also { block(it, TypeSafeArg(name, argument)) } + +fun <T : ArgumentBuilder<DefaultSource, T>, AT : Any> T.thenArgument( + name: String, + argument: ArgumentType<AT>, + block: RequiredArgumentBuilder<DefaultSource, AT>.(TypeSafeArg<AT>) -> Unit +): ArgumentCommandNode<DefaultSource, AT> = argument(name, argument, block).build().also(::then) + +fun <T : ArgumentBuilder<DefaultSource, T>, AT : Any> T.thenArgumentExecute( + name: String, + argument: ArgumentType<AT>, + block: CommandContext<DefaultSource>.(TypeSafeArg<AT>) -> Unit +): ArgumentCommandNode<DefaultSource, AT> = thenArgument(name, argument) { + thenExecute { + block(it) + } +} + +fun literal( + name: String, + block: LiteralArgumentBuilder<DefaultSource>.() -> Unit = {} +): LiteralArgumentBuilder<DefaultSource> = + LiteralArgumentBuilder.literal<DefaultSource>(name).also(block) + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenLiteral( + name: String, + block: LiteralArgumentBuilder<DefaultSource>.() -> Unit +): LiteralCommandNode<DefaultSource> = + then(literal(name), block) as LiteralCommandNode<DefaultSource> + + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenLiteralExecute( + name: String, + block: CommandContext<DefaultSource>.() -> Unit +): LiteralCommandNode<DefaultSource> = + thenLiteral(name) { + thenExecute(block) + } + +fun <T : ArgumentBuilder<DefaultSource, T>, U : ArgumentBuilder<DefaultSource, U>> T.then( + node: U, + block: U.() -> Unit +): CommandNode<DefaultSource> = + node.also(block).build().also(::then) + +fun <T : ArgumentBuilder<DefaultSource, T>> T.thenExecute(block: CommandContext<DefaultSource>.() -> Unit): T = + executes { + block(it) + 1 + } + +fun <T : ArgumentBuilder<DefaultSource, T>> T.requiresDev(): T { + requires { DevTestCommand.isDeveloper(it) } + return this +} + +fun NEUBrigadierHook.withHelp(helpText: String): NEUBrigadierHook { + commandNode.withHelp(helpText) + return this +} + +fun <T : CommandNode<DefaultSource>> T.withHelp(helpText: String): T { + BrigadierRoot.setHelpForNode(this, helpText) + return this +} + +fun <A : Any, T : RequiredArgumentBuilder<DefaultSource, A>> T.suggestsList(list: List<String>) { + suggestsList { list } +} + +fun <A : Any, T : RequiredArgumentBuilder<DefaultSource, A>> T.suggestsList(list: () -> List<String>) { + suggests { context, builder -> + list().filter { it.startsWith(builder.remaining, ignoreCase = true) } + .forEach { builder.suggest(it) } + builder.buildFuture() + } +} + + + diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt new file mode 100644 index 00000000..bcfe11aa --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util + +fun <T : Any> T.iterate(evolve: (T) -> T?): Sequence<T> = sequence { + var pointer: T? = this@iterate + while (pointer != null) { + yield(pointer) + pointer = evolve(pointer) + } +} |
