This commit is contained in:
2026-05-09 22:26:03 +03:00
parent 73970c192a
commit 9caefa4c84
48 changed files with 850 additions and 279 deletions
+28 -61
View File
@@ -1,8 +1,13 @@
import ch.qos.logback.core.net.server.Client
import commands.ExecuteScriptCommand
import commands.ExitCommand
import commands.HelpCommand
import commands.SyncCommand
import commands.UndefinedCommand
import network.NetworkManager
import input.HumanBeingBuilder
import input.IOManager
import network.Request
import runner.CommandValidator
import runner.CommandInvoker
fun main(args: Array<String>) {
val host = if (args.isNotEmpty()) args[0] else "localhost"
@@ -10,7 +15,7 @@ fun main(args: Array<String>) {
val io = IOManager() // Name by Boris Bosenko
val client = NetworkManager(host, port)
val validator = CommandValidator()
val invoker = CommandInvoker(io)
// Shutdown hook
Runtime.getRuntime().addShutdownHook(Thread {
@@ -23,70 +28,32 @@ fun main(args: Array<String>) {
|════════════════════════════════════════════
""".trimMargin())
runClientRepl(io, client, validator)
registerBasicCommands(invoker, client, io)
runClientRepl(io, invoker)
}
fun runClientRepl(io: IOManager, client: NetworkManager, validator: CommandValidator) {
fun registerBasicCommands(invoker: CommandInvoker,client: NetworkManager, io: IOManager) {
listOf(
HelpCommand(invoker),
ExecuteScriptCommand(io),
SyncCommand(invoker, client)
).forEach { invoker.register(it)}
}
val initialRequest = Request(
commandName = "getCommandsCommand",
humanBeing = null
)
fun runClientRepl(io: IOManager, invoker : CommandInvoker) {
// sync commands w server
invoker.execute("sync")
if (client.sendRequest(initialRequest) == null) {
io.print("[!] Сервер недоступен. Попробуйте позже.")
} else {
io.print("|Введите 'help' для справки.")
}
var isRunning = true
invoker.register(ExitCommand {isRunning = false})
while (true) {
while (isRunning) {
val line = io.readLine(" ☭ ") ?: break
if (line.isBlank()) continue
val parts = line.trim().split(" ")
val commandName = parts[0].lowercase()
val args = parts.drop(1)
val (isValidCommand, message) = validator.validate(commandName, args)
if (!isValidCommand) {
if (message != null) io.print(message)
continue
}
if (commandName == "exit") {
io.print("До свидания!")
break
}
if (commandName == "execute_script") {
io.addScriptScanner(args[0])
continue
}
val humanBeing = if (validator.isBuildsHumanBeing(commandName)) {
try {
HumanBeingBuilder(io).build()
} catch (e: IllegalStateException) {
io.print("[Ошибка] Ввод прерван: ${e.message}")
continue
}
} else null
// Формируем запрос и отправляем
val request = Request(
commandName = commandName,
args = args,
humanBeing = humanBeing
)
val response = client.sendRequest(request)
// Обрабатываем ответ
if (response == null) {
io.print("[!] Сервер недоступен. Попробуйте позже.")
} else {
io.print(response.message)
}
invoker.execute(line)
}
// and here we exit that crazy circus of code and tears
io.print("До свидания!")
}
@@ -0,0 +1,13 @@
package commands
import models.HumanBeing
interface ClientCommand {
val name: String
val description: String
val args: List<CommandArgType>
val type: CommandType
fun execute(args: List<String>, humanBeing: HumanBeing? = null): String
}
@@ -0,0 +1,16 @@
package commands
import input.IOManager
import models.HumanBeing
class ExecuteScriptCommand(private val io: IOManager) : ClientCommand {
override val name = "execute_script"
override val description = "Выполняет скрипт из файла"
override val args = listOf(CommandArgType.STRING)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
io.addScriptScanner(args[0])
return ""
}
}
@@ -0,0 +1,15 @@
package commands
import models.HumanBeing
class ExitCommand(private val onExit: () -> Unit) : ClientCommand {
override val name = "exit"
override val description = "завершить клиент"
override val args: List<CommandArgType> = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
onExit()
return ""
}
}
@@ -0,0 +1,16 @@
package commands
import models.HumanBeing
import runner.CommandInvoker
class HelpCommand(private val invoker: CommandInvoker) : ClientCommand {
override val name = "help"
override val description = "вывести справку по доступным командам"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
invoker.getCommands().values
.sortedBy { it.name }
.joinToString("\n") { " ${it.name.padEnd(40)} — ${it.description}" }
}
@@ -0,0 +1,42 @@
package commands
import input.IOManager
import models.HumanBeing
import network.NetworkManager
import network.Request
import runner.CommandInvoker
class SyncCommand (
private val invoker: CommandInvoker,
private val client: NetworkManager,
) : ClientCommand {
override val name = "sync"
override val description = "получить команды с сервера"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
val initialRequest = Request(
commandName = "sync",
humanBeing = null
)
val res = client.sendRequest(initialRequest)
if (res == null) {
return ("[!] Сервер недоступен. Попробуйте позже.")
} else {
res.commands?.forEach { fuckingCommand ->
invoker.register(
UndefinedCommand( // ser
name = fuckingCommand.name,
description = fuckingCommand.description,
type = fuckingCommand.type,
args = fuckingCommand.args,
networkManager = client
)
)
}
return ("| Подключен. | Введите 'help' для справки.")
}
}
}
@@ -0,0 +1,25 @@
package commands
import input.HumanBeingBuilder
import models.HumanBeing
import network.Request
import network.NetworkManager
class UndefinedCommand(
override var name: String,
override var description: String,
override var args: List<CommandArgType>,
override var type: CommandType,
private val networkManager: NetworkManager,
): ClientCommand {
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
val request = Request(
commandName = this.name,
args = args,
humanBeing = humanBeing
)
val response = networkManager.sendRequest(request)
return response?.message ?: "[!] Сервер не ответил"
}
}
@@ -16,6 +16,7 @@ import models.WeaponType
*
* @property inputManager источник ввода
*/
class HumanBeingBuilder(private val inputManager: IOManager) {
/**
+1 -4
View File
@@ -6,16 +6,13 @@ import org.jline.reader.LineReaderBuilder
import org.jline.reader.UserInterruptException
import org.jline.reader.impl.history.DefaultHistory
import org.jline.terminal.TerminalBuilder
import org.jline.reader.impl.completer.StringsCompleter
import java.io.File
import java.io.FileNotFoundException
import java.util.ArrayDeque
import java.util.Scanner
import runner.CommandValidator
class IOManager {
val isInteractive: Boolean = true
val commandValidator: CommandValidator = CommandValidator()
private val terminal = TerminalBuilder.builder()
.system(true)
@@ -26,7 +23,7 @@ class IOManager {
private val lineReader: LineReader = LineReaderBuilder.builder()
.terminal(terminal)
.history(history)
.completer(StringsCompleter(commandValidator.getKnownCommands()))
// .completer(StringsCompleter(invoker.getCommandNames()))
.option(LineReader.Option.HISTORY_IGNORE_DUPS, true)
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
.build()
@@ -1,7 +1,6 @@
package network
import kotlinx.serialization.SerializationException
import lab6.prog.network.AppJson
import java.net.InetSocketAddress
import java.net.PortUnreachableException
import java.nio.ByteBuffer
@@ -0,0 +1,56 @@
package runner
import commands.ClientCommand
import commands.CommandType
import input.HumanBeingBuilder
import input.IOManager
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
class CommandInvoker(private val io: IOManager) {
private val commands = mutableMapOf<String, ClientCommand>()
private val history = ArrayDeque<String>(12)
fun register(command: ClientCommand) {
commands[command.name] = command
}
fun execute(input: String) {
val parts = input.trim().split(" ")
val name = parts[0].lowercase().trim()
val args = parts.drop(1)
val command = commands[name]
if (command == null) {
logger.info { "Неизвестная команда: '$name'. Введите 'help' для справки."}
return
}
addToHistory(name)
try {
if (command.type == CommandType.MODEL) {
val model = HumanBeingBuilder(io).build()
val res = command.execute(args, model)
io.print(res)
} else {
val res = command.execute(args)
io.print(res)
}
} catch (e: IllegalStateException) {
logger.info {"[Ошибка выполнения] ${e.message}"}
}
}
fun getHistory(): List<String> = history.toList()
fun getCommands(): Map<String, ClientCommand> = commands.toMap()
private fun addToHistory(name: String) {
if (history.size >= 12) history.removeFirst()
history.addLast(name)
}
fun getCommandNames(): List<String> = commands.keys.toList()
}
@@ -1,42 +0,0 @@
package runner
class CommandValidator {
// Команды, которые требуют ввода HumanBeing
private val humanBeingCommands = setOf("add", "add_if_max", "add_if_min")
// Команды с аргументом
private val commandsWArgs = setOf("update", "remove_by_id", "execute_script")
// Все команды, доступные клиенту
private val knownCommands = setOf(
"help", "info", "show", "add", "update",
"remove_by_id", "clear", "execute_script",
"add_if_max", "add_if_min", "history",
"sum_of_minutes_of_waiting", "min_by_name",
"print_field_descending_minutes_of_waiting",
"exit"
)
fun getKnownCommands(): Set<String> {return knownCommands}
fun validate(command: String, args: List<String>?): Pair<Boolean, String?> {
if (command !in knownCommands){
return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")
}
if (command in commandsWArgs && args?.isEmpty() == true) {
return if (command == "execute_script") {
Pair(false, "[Ошибка] Укажите путь к файлу. Пример: execute_script script.txt")
} else {
// maybe create another set w commands w file args and another for other args
Pair(false, "[Ошибка] Укажите id элемента. Пример: $command <uuid>")
}
}
return Pair(true, null)
}
fun isBuildsHumanBeing(command: String): Boolean {
return command in humanBeingCommands
}
}
@@ -0,0 +1,7 @@
package runner
class ModelValidator {
}