From 3ec82ad51fe24e0523f0c52f15aeb50efcee4ac0 Mon Sep 17 00:00:00 2001 From: Bixilon Date: Wed, 18 May 2022 14:45:12 +0200 Subject: [PATCH] command execution --- src/main/java/de/bixilon/minosoft/Minosoft.kt | 3 + .../commands/errors/ExpectedArgumentError.kt | 20 ++++++ .../errors/literal/ExpectedLiteralArgument.kt | 21 ++++++ .../literal/InvalidLiteralArgumentError.kt | 22 +++++++ .../minosoft/commands/nodes/ArgumentNode.kt | 7 +- .../minosoft/commands/nodes/CommandNode.kt | 32 +++++++++- .../minosoft/commands/nodes/ExecutableNode.kt | 35 +++++++++- .../minosoft/commands/nodes/LiteralNode.kt | 24 ++++++- .../minosoft/commands/nodes/RootNode.kt | 10 ++- .../minosoft/commands/stack/CommandStack.kt | 30 ++++++++- .../de/bixilon/minosoft/main/BootTasks.kt | 1 + .../de/bixilon/minosoft/terminal/cli/CLI.kt | 64 +++++++++++++++++++ .../minosoft/terminal/commands/Commands.kt | 26 ++++++++ .../minosoft/terminal/commands/HelpCommand.kt | 12 +++- 14 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/commands/errors/ExpectedArgumentError.kt create mode 100644 src/main/java/de/bixilon/minosoft/commands/errors/literal/ExpectedLiteralArgument.kt create mode 100644 src/main/java/de/bixilon/minosoft/commands/errors/literal/InvalidLiteralArgumentError.kt create mode 100644 src/main/java/de/bixilon/minosoft/terminal/cli/CLI.kt create mode 100644 src/main/java/de/bixilon/minosoft/terminal/commands/Commands.kt diff --git a/src/main/java/de/bixilon/minosoft/Minosoft.kt b/src/main/java/de/bixilon/minosoft/Minosoft.kt index 3148aced2..bc8cb8f2f 100644 --- a/src/main/java/de/bixilon/minosoft/Minosoft.kt +++ b/src/main/java/de/bixilon/minosoft/Minosoft.kt @@ -46,6 +46,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.terminal.AutoConnect import de.bixilon.minosoft.terminal.CommandLineArguments import de.bixilon.minosoft.terminal.RunConfiguration +import de.bixilon.minosoft.terminal.cli.CLI import de.bixilon.minosoft.util.GitInfo import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.RenderPolling @@ -75,6 +76,8 @@ object Minosoft { val taskWorker = TaskWorker(criticalErrorHandler = { _, exception -> exception.crash() }) + taskWorker += Task(identifier = BootTasks.CLI, priority = ThreadPool.HIGH, executor = CLI::startThread) + taskWorker += Task(identifier = BootTasks.PACKETS, priority = ThreadPool.HIGH, executor = PacketTypeRegistry::init) taskWorker += Task(identifier = BootTasks.VERSIONS, priority = ThreadPool.HIGH, dependencies = arrayOf(BootTasks.PACKETS), executor = Versions::load) taskWorker += Task(identifier = BootTasks.PROFILES, priority = ThreadPool.HIGH, dependencies = arrayOf(BootTasks.VERSIONS), executor = GlobalProfileManager::initialize) diff --git a/src/main/java/de/bixilon/minosoft/commands/errors/ExpectedArgumentError.kt b/src/main/java/de/bixilon/minosoft/commands/errors/ExpectedArgumentError.kt new file mode 100644 index 000000000..7ad785036 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/errors/ExpectedArgumentError.kt @@ -0,0 +1,20 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.errors + +import de.bixilon.minosoft.commands.util.CommandReader + +class ExpectedArgumentError( + reader: CommandReader, +) : ReaderError(reader, reader.pointer, reader.pointer) diff --git a/src/main/java/de/bixilon/minosoft/commands/errors/literal/ExpectedLiteralArgument.kt b/src/main/java/de/bixilon/minosoft/commands/errors/literal/ExpectedLiteralArgument.kt new file mode 100644 index 000000000..c28078484 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/errors/literal/ExpectedLiteralArgument.kt @@ -0,0 +1,21 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.errors.literal + +import de.bixilon.minosoft.commands.errors.ReaderError +import de.bixilon.minosoft.commands.util.CommandReader + +class ExpectedLiteralArgument( + reader: CommandReader, +) : ReaderError(reader, reader.pointer, reader.pointer) diff --git a/src/main/java/de/bixilon/minosoft/commands/errors/literal/InvalidLiteralArgumentError.kt b/src/main/java/de/bixilon/minosoft/commands/errors/literal/InvalidLiteralArgumentError.kt new file mode 100644 index 000000000..d27302235 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/commands/errors/literal/InvalidLiteralArgumentError.kt @@ -0,0 +1,22 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.commands.errors.literal + +import de.bixilon.minosoft.commands.errors.ReaderError +import de.bixilon.minosoft.commands.util.CommandReader + +class InvalidLiteralArgumentError( + reader: CommandReader, + val argument: String, +) : ReaderError(reader, reader.pointer - argument.length, reader.pointer) diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt index 8e6fd90e2..ad7e58772 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/ArgumentNode.kt @@ -26,7 +26,7 @@ class ArgumentNode : ExecutableNode { suggestion: SuggestionType<*>? = null, executable: Boolean = false, redirect: CommandNode? = null, - ) : super(name, suggestion, false, null, executable, redirect) { + ) : super(name, setOf(), suggestion, false, null, executable, redirect) { this.parser = parser } @@ -35,4 +35,9 @@ class ArgumentNode : ExecutableNode { this.executor = executor this.parser = parser } + + override fun addChild(node: CommandNode): ArgumentNode { + super.addChild(node) + return this + } } diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt index f46103bbd..ac5f0a244 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt @@ -13,7 +13,37 @@ package de.bixilon.minosoft.commands.nodes +import de.bixilon.minosoft.commands.stack.CommandStack +import de.bixilon.minosoft.commands.util.CommandReader + abstract class CommandNode( val executable: Boolean, val redirect: CommandNode?, -) +) { + protected val children: MutableList = mutableListOf() + + open fun addChild(node: CommandNode): CommandNode { + children += node + return this + } + + protected open fun executeChild(child: CommandNode, reader: CommandReader, stack: CommandStack) { + child.execute(reader, stack) + } + + open fun execute(reader: CommandReader, stack: CommandStack) { + val pointer = reader.pointer + val stackSize = stack.size + var lastError: Throwable? = null + for (child in children) { + try { + return executeChild(child, reader, stack) + } catch (error: Throwable) { + lastError = error + } + reader.pointer = pointer + stack.reset(stackSize) + } + throw lastError ?: return + } +} diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt index fa1167ff5..4167e3c0f 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/ExecutableNode.kt @@ -13,14 +13,47 @@ package de.bixilon.minosoft.commands.nodes +import de.bixilon.minosoft.commands.errors.ExpectedArgumentError import de.bixilon.minosoft.commands.stack.CommandExecutor +import de.bixilon.minosoft.commands.stack.CommandStack import de.bixilon.minosoft.commands.suggestion.types.SuggestionType +import de.bixilon.minosoft.commands.util.CommandReader abstract class ExecutableNode( val name: String, + val aliases: Set = setOf(), val suggestion: SuggestionType<*>? = null, var onlyDirectExecution: Boolean = true, var executor: CommandExecutor? = null, executable: Boolean = executor != null, redirect: CommandNode? = null, -) : CommandNode(executable, redirect) +) : CommandNode(executable, redirect) { + + private fun execute(stack: CommandStack) { + try { + executor?.invoke(stack) + } catch (exception: Throwable) { + exception.printStackTrace() + } + } + + override fun execute(reader: CommandReader, stack: CommandStack) { + reader.skipWhitespaces() + if (!reader.canPeekNext()) { + // empty string + if (executable) { + return execute(stack) + } else { + throw ExpectedArgumentError(reader) + } + } + super.execute(reader, stack) + } + + override fun executeChild(child: CommandNode, reader: CommandReader, stack: CommandStack) { + super.executeChild(child, reader, stack) + if (!onlyDirectExecution) { + execute(stack) + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt index 77ac7bc82..debdc01cf 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/LiteralNode.kt @@ -13,18 +13,38 @@ package de.bixilon.minosoft.commands.nodes +import de.bixilon.minosoft.commands.errors.literal.ExpectedLiteralArgument +import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError import de.bixilon.minosoft.commands.stack.CommandExecutor +import de.bixilon.minosoft.commands.stack.CommandStack import de.bixilon.minosoft.commands.suggestion.types.SuggestionType +import de.bixilon.minosoft.commands.util.CommandReader class LiteralNode : ExecutableNode { constructor( name: String, + aliases: Set = setOf(), suggestion: SuggestionType<*>? = null, executable: Boolean = false, redirect: CommandNode? = null, - ) : super(name, suggestion, false, null, executable, redirect) + ) : super(name, aliases, suggestion, false, null, executable, redirect) - constructor(name: String, onlyDirectExecution: Boolean = true, executor: CommandExecutor) : super(name, onlyDirectExecution = onlyDirectExecution, executor = executor, executable = true) + constructor(name: String, aliases: Set = setOf(), onlyDirectExecution: Boolean = true, executor: CommandExecutor) : super(name, aliases, onlyDirectExecution = onlyDirectExecution, executor = executor, executable = true) + + override fun addChild(node: CommandNode): LiteralNode { + super.addChild(node) + return this + } + + + override fun execute(reader: CommandReader, stack: CommandStack) { + val literalName = reader.readUnquotedString() ?: throw ExpectedLiteralArgument(reader) + if (literalName != name && literalName !in aliases) { + throw InvalidLiteralArgumentError(reader, literalName) + } + stack.push(name, name) + return super.execute(reader, stack) + } } diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/RootNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/RootNode.kt index ec9a9318f..c8d22c993 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/RootNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/RootNode.kt @@ -13,4 +13,12 @@ package de.bixilon.minosoft.commands.nodes -class RootNode : CommandNode(false, null) +import de.bixilon.minosoft.commands.stack.CommandStack +import de.bixilon.minosoft.commands.util.CommandReader + +class RootNode : CommandNode(false, null) { + + fun execute(command: String) { + execute(CommandReader(command), CommandStack()) + } +} diff --git a/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt b/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt index e3ccab7c2..3d6b11ec0 100644 --- a/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt +++ b/src/main/java/de/bixilon/minosoft/commands/stack/CommandStack.kt @@ -13,4 +13,32 @@ package de.bixilon.minosoft.commands.stack -class CommandStack +import de.bixilon.kutil.cast.CastUtil.nullCast + +class CommandStack { + private val stack: MutableList = mutableListOf() + val size: Int get() = stack.size + + @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") + inline operator fun get(name: String): T? { + return getAny(name).nullCast() + } + + fun getAny(name: String): Any? { + return stack.find { it.name == name }?.data + } + + fun reset(size: Int) { + var index = 0 + stack.removeAll { index++ >= size } + } + + fun push(name: String, data: Any) { + stack.add(StackEntry(name, data)) + } + + private data class StackEntry( + val name: String, + val data: Any?, + ) +} diff --git a/src/main/java/de/bixilon/minosoft/main/BootTasks.kt b/src/main/java/de/bixilon/minosoft/main/BootTasks.kt index d5ba45d30..6dade0381 100644 --- a/src/main/java/de/bixilon/minosoft/main/BootTasks.kt +++ b/src/main/java/de/bixilon/minosoft/main/BootTasks.kt @@ -27,5 +27,6 @@ enum class BootTasks { YGGDRASIL, STARTUP_PROGRESS, ASSETS_OVERRIDE, + CLI, ; } diff --git a/src/main/java/de/bixilon/minosoft/terminal/cli/CLI.kt b/src/main/java/de/bixilon/minosoft/terminal/cli/CLI.kt new file mode 100644 index 000000000..5e182c224 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/terminal/cli/CLI.kt @@ -0,0 +1,64 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.terminal.cli + +import de.bixilon.kutil.latch.CountUpAndDownLatch +import de.bixilon.minosoft.ShutdownReasons +import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection +import de.bixilon.minosoft.terminal.commands.Commands +import de.bixilon.minosoft.util.ShutdownManager +import org.jline.reader.LineReader +import org.jline.reader.LineReaderBuilder +import org.jline.reader.UserInterruptException +import org.jline.terminal.Terminal +import org.jline.terminal.TerminalBuilder + +object CLI { + var connection: PlayConnection? = null + + + fun startThread(latch: CountUpAndDownLatch) { + latch.inc() + Thread({ latch.dec(); startLoop() }, "CLI").start() + } + + private fun startLoop() { + val builder = TerminalBuilder.builder() + + val terminal: Terminal = builder.build() + val reader: LineReader = LineReaderBuilder.builder() + .terminal(terminal) + // .completer() // ToDo + .build() + + while (true) { + try { + val line: String = reader.readLine().removeDuplicatedWhitespaces() + if (line.isBlank()) { + continue + } + terminal.flush() + Commands.ROOT_NODE.execute(line) + } catch (exception: UserInterruptException) { + ShutdownManager.shutdown(reason = ShutdownReasons.ALL_FINE) + } catch (exception: Throwable) { + exception.printStackTrace() + } + } + } + + fun String.removeDuplicatedWhitespaces(): String { + return this.replace("\\s{2,}".toRegex(), "") + } +} diff --git a/src/main/java/de/bixilon/minosoft/terminal/commands/Commands.kt b/src/main/java/de/bixilon/minosoft/terminal/commands/Commands.kt new file mode 100644 index 000000000..1004a2069 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/terminal/commands/Commands.kt @@ -0,0 +1,26 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.terminal.commands + +import de.bixilon.minosoft.commands.nodes.RootNode + +object Commands { + val ROOT_NODE = RootNode() + .register(HelpCommand) + + fun RootNode.register(command: Command): RootNode { + this.addChild(command.build()) + return this + } +} diff --git a/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt b/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt index c44f7525b..02524c95e 100644 --- a/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt +++ b/src/main/java/de/bixilon/minosoft/terminal/commands/HelpCommand.kt @@ -15,9 +15,17 @@ package de.bixilon.minosoft.terminal.commands import de.bixilon.minosoft.commands.nodes.LiteralNode -class HelpCommand : Command { +object HelpCommand : Command { override fun build(): LiteralNode { - TODO("Not yet implemented") + return LiteralNode("help", setOf("?"), onlyDirectExecution = false, executor = { + printHelp(it["general"]) + }) + .addChild(LiteralNode("general", executable = true)) + } + + fun printHelp(subcommand: String?) { + println("-------------- Minosoft help --------------") + println("Subcommand: $subcommand") } }