command execution

This commit is contained in:
Bixilon 2022-05-18 14:45:12 +02:00
parent 5135fa4d27
commit 3ec82ad51f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
14 changed files with 298 additions and 9 deletions

View File

@ -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)

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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)

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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)

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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)

View File

@ -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
}
}

View File

@ -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<CommandNode> = 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
}
}

View File

@ -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<String> = 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)
}
}
}

View File

@ -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<String> = 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<String> = 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)
}
}

View File

@ -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())
}
}

View File

@ -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<StackEntry> = mutableListOf()
val size: Int get() = stack.size
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline operator fun <reified T> 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?,
)
}

View File

@ -27,5 +27,6 @@ enum class BootTasks {
YGGDRASIL,
STARTUP_PROGRESS,
ASSETS_OVERRIDE,
CLI,
;
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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(), "")
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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
}
}

View File

@ -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")
}
}