Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge dev branch #20

Merged
merged 29 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4287981
Testing string command impl
Matt-MX Feb 10, 2024
12e1d94
Update build.yml
Matt-MX Feb 10, 2024
2a680db
Create test-build.yml
Matt-MX Feb 10, 2024
45e8445
Update build.yml
Matt-MX Feb 10, 2024
d0b02a7
Update build.yml
Matt-MX Feb 10, 2024
6b5761c
Update test-build.yml
Matt-MX Feb 10, 2024
406e332
Update test-build.yml
Matt-MX Feb 10, 2024
e0f44a0
Update build.yml
Matt-MX Feb 10, 2024
fab6d07
Merge branch 'refactor' of https://github.com/Matt-MX/KtPaperGui into…
Matt-MX Feb 10, 2024
9906628
impl parser for string command dsl
Matt-MX Feb 12, 2024
fa01cdf
working parsing commands (will not build rn tho)
Matt-MX Feb 12, 2024
6b330e5
working arguments, missing args, descriptions etc
Matt-MX Feb 13, 2024
9f80711
Adding additional java support.
Matt-MX Feb 14, 2024
a8b8744
Update test-build.yml
Matt-MX Feb 14, 2024
f07a8f7
remove conflicting `effect` method name
Matt-MX Feb 14, 2024
40b339a
Merge branch 'dev' of https://github.com/Matt-MX/KtPaperGui into dev
Matt-MX Feb 14, 2024
f84c7c1
make click assignable to child classes
Matt-MX Feb 14, 2024
e2329f8
Update SignalsListExample.kt
Matt-MX Feb 14, 2024
af7ce0a
Further java support, adding numeric formatting utility functions i c…
Matt-MX Feb 14, 2024
0be1424
Merge pull request #18 from Matt-MX/master
Matt-MX Feb 14, 2024
bfaed7b
Created new adaptable conversation api design
Matt-MX Feb 14, 2024
da0f560
Merge branch 'dev' of https://github.com/Matt-MX/KtPaperGui into dev
Matt-MX Feb 14, 2024
0633569
#19 Can now access `Conversation` object from `ConversationResult` ca…
Matt-MX Feb 15, 2024
edfcfc0
add infix modifier
Matt-MX Feb 15, 2024
3d37a75
testing performance for string commands parsing (is decent)
Matt-MX Feb 15, 2024
c99f2aa
could have encountered multithreading issues? not sure
Matt-MX Feb 15, 2024
49fa87e
Conversation example and wrapper changes
Matt-MX Feb 28, 2024
d59d143
Implemented refresh block + example
Matt-MX Feb 28, 2024
69e75c9
fix build issues
Matt-MX Mar 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Build Gradle project

on:
push:
branches:
- "main"

jobs:
build-gradle-project:
build-and-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
on:
push:
branches:
- "dev"

jobs:
test-build-gradle-project:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
- name: Checkout project sources
uses: actions/checkout@v3
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run build with Gradle Wrapper
run: ./gradlew build
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`maven-publish`
kotlin("jvm") version "1.7.10"
id("com.github.johnrengelman.shadow") version "7.0.0"
`maven-publish`
}

repositories {
mavenCentral()
}

dependencies {
compileOnly(kotlin("reflect"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10")
}

tasks.test {
Expand All @@ -25,6 +31,13 @@ tasks {
build {
dependsOn(shadowJar)
}
shadowJar {
mergeServiceFiles()
// exclude {
//// it.path.startsWith("kotlin") && !it.path.contains("reactive")
// it.name.startsWith("kotlin")
// }
}
}

tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
Expand Down
12 changes: 9 additions & 3 deletions api/src/main/kotlin/com/mattmx/ktgui/GuiManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.server.PluginDisableEvent
import org.bukkit.plugin.java.JavaPlugin
import org.jetbrains.annotations.ApiStatus
import java.util.Collections

/**
* Handles all GUI click events, as well
* as events we might need to know while in a GUI.
*/
object GuiManager : Listener {
private val players = hashMapOf<Player, IGuiScreen>()
private val players = Collections.synchronizedMap(hashMapOf<Player, IGuiScreen>())
private var initialized = false
private val defaultConfiguration = Configuration()
private val configurations = hashMapOf<JavaPlugin, Configuration>()
Expand Down Expand Up @@ -106,7 +107,9 @@ object GuiManager : Listener {
val gui = e.player.getOpenGui()
gui?.let {
gui.quit(e)
gui.destroy()
if (getPlayers(gui).isEmpty()) {
gui.destroy()
}
players.remove(e.player)
}
}
Expand All @@ -126,8 +129,11 @@ object GuiManager : Listener {
fun forceClose(player: Player) {
val gui = player.getOpenGui()
gui?.let {
gui.destroy()
if (getPlayers(gui).isEmpty()) {
gui.destroy()
}
players.remove(player)
player.openInventory.close()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ open class SimpleCommandBuilder(
var noPermissions: (CommandInvocation.() -> Unit)? = null
private set

constructor(name: String) : this(name, null)
constructor(name: String, vararg alias: String) : this(name, null, *alias)

infix fun permission(permission: String) : SimpleCommandBuilder {
this.permission = permission
return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.mattmx.ktgui.commands.declarative
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class CommandContext<S : CommandSender>(
open class CommandContext<S : CommandSender>(
val sender: S,
val args: Array<String>,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mattmx.ktgui.commands.stringbuilder

import com.mattmx.ktgui.commands.stringbuilder.arg.Argument
import org.bukkit.command.CommandSender

class MissingArgContext<T : CommandSender>(
val missingArgument: Argument<*>,
args: List<String>
) : RawCommandContext<T>(args)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mattmx.ktgui.commands.stringbuilder

import org.bukkit.command.CommandSender

open class RawCommandContext<T : CommandSender>(
val rawArgs: List<String>
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mattmx.ktgui.commands.stringbuilder

import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext
import com.mattmx.ktgui.utils.JavaCompatibility
import org.bukkit.command.CommandSender
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class RunnableCommandContext<T : CommandSender>(
private val providedArgs: HashMap<String, ArgumentContext<*>>,
rawArgs: List<String>
) : RawCommandContext<T>(rawArgs) {

fun argument() = ReadOnlyProperty { owner: Nothing?, prop: KProperty<*> ->
// todo might be optional
providedArgs[prop.name] ?: error("Unknown argument '${prop.name}'")
}

@JavaCompatibility
fun <S> getArgument(name: String) = providedArgs[name]?.let { it as ArgumentContext<S> }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.mattmx.ktgui.commands.stringbuilder

import com.mattmx.ktgui.commands.stringbuilder.arg.Argument
import com.mattmx.ktgui.commands.stringbuilder.arg.ArgumentContext
import com.mattmx.ktgui.commands.stringbuilder.syntax.CommandDeclarationSyntax
import com.mattmx.ktgui.commands.stringbuilder.syntax.Parser
import com.mattmx.ktgui.commands.stringbuilder.syntax.SubCommandDeclarationSyntax
import com.mattmx.ktgui.commands.stringbuilder.syntax.VariableDeclarationSyntax
import com.mattmx.ktgui.commands.usage.CommandUsageOptions
import com.mattmx.ktgui.configuration.Configuration
import org.bukkit.command.CommandSender
import java.util.Optional

class StringCommand<T : CommandSender>(
source: String
) {
lateinit var name: String
var aliases = arrayOf<String>()
var subcommands = arrayOf<StringCommand<*>>()
var expectedArguments = arrayOf<Argument<*>>()
private var permission: Optional<(RunnableCommandContext<T>) -> Boolean> = Optional.empty()
private var runs: Optional<(RunnableCommandContext<T>) -> Unit> = Optional.empty()
private var missing: Optional<(MissingArgContext<T>) -> Unit> = Optional.empty()

init {
val parsed = Parser(source).parse()

for (syntax in parsed) {
when (syntax) {
is VariableDeclarationSyntax -> {
expectedArguments += Argument<Any>(
syntax.getName(),
syntax.getType(),
null,
!syntax.getType().isOptional
)
}

is CommandDeclarationSyntax -> {
// todo should be top level only
name = syntax.getName()
}

is SubCommandDeclarationSyntax -> {
name = syntax.getName()
}
}
}
}

infix fun missing(block: MissingArgContext<T>.() -> Unit) = apply {
this.missing = Optional.of(block)
}

infix fun runs(block: RunnableCommandContext<T>.() -> Unit) = apply {
this.runs = Optional.of(block)
}

infix fun args(block: ArgumentOptions<T>.() -> Unit) = apply {
ArgumentOptions<T>(this).apply(block)
}

class ArgumentOptions<T : CommandSender>(private val command: StringCommand<T>) {
operator fun String.invoke(block: Argument<*>.() -> Unit) =
command.expectedArguments.firstOrNull { it.name() == this }
?.apply(block)
?: error("Unregistered argument '$this'.")
}

inline operator fun <V : CommandSender> String.invoke(block: StringCommand<V>.() -> Unit) =
StringCommand<V>(this).let {
// todo could also have parameters
subcommands += it
}

fun getSuggestions(context: RawCommandContext<T>): List<String> {
val currentArgument = getCurrentArgument(context)
val suggestions = currentArgument?.suggestions()?.invoke(context) ?: return emptyList()
val lastArgument = context.rawArgs.lastOrNull() ?: ""
return suggestions.filter { suggestion -> suggestion.startsWith(lastArgument, true) }.toList()
}

/**
* Used to find the current argument, presuming we are on the current sub-command
*
* @param args the current arguments of the command invocation
* @param sender command sender
* @return the current argument or null if it is invalid
*/
private fun getCurrentArgument(context: RawCommandContext<T>): Argument<*>? {
if (expectedArguments.isEmpty()) return null

// Greedy arguments will eat the rest of the arguments todo
if (expectedArguments.first().type().isVararg) return expectedArguments.first()

repeat(expectedArguments.size) { argIndex ->
val registered = expectedArguments.getOrNull(argIndex) ?: return null
val comparing = context.rawArgs.getOrNull(argIndex) ?: return null
// todo need to think about optional args!!! wtf do we do there
}

return null
}

operator fun unaryPlus() = register()

fun register() = apply {

}

fun invoke(context: RawCommandContext<T>) {

// Set variables
val argumentValues = hashMapOf<String, ArgumentContext<*>>()
for ((index, arg) in expectedArguments.withIndex()) {
// todo var offset for sub-commands?
val value = if (arg.type().isVararg) {
context.rawArgs.subList(index, context.rawArgs.size).joinToString(" ")
} else context.rawArgs.getOrNull(index)

if (arg.isRequired() && value == null) {
val missingArgContext = MissingArgContext<T>(arg, context.rawArgs)
missing.ifPresent { it.invoke(missingArgContext) }
return
} else {
argumentValues[arg.name()] = ArgumentContext(Optional.ofNullable(value), arg as Argument<String>)
}
}

val runnableContext = RunnableCommandContext<T>(argumentValues, context.rawArgs)
runs.ifPresent { it.invoke(runnableContext) }
}

/**
* Builds a usage for this command.
* You can create your own method with the [Configuration] class.
*
* @return a formatted string for usage of the command
*/
fun getUsage(options: CommandUsageOptions = CommandUsageOptions()): String {
var builder = "${options.namePrefix}$name${options.gap}"
if (subcommands.isNotEmpty())
builder += subcommands.joinToString(options.subCommands.divider) { subcommand -> subcommand.name }
else {
var end = ""
builder += expectedArguments.joinToString(" ") { arg ->

val suggestions =
if (options.arguments.showSuggestions) {
val suggestions = arg.getDefaultSuggestions()
if (!suggestions.isNullOrEmpty()) {
val opt = options.arguments
"${opt.suggestionsChar}${opt.suggestionsPrefix}${suggestions.joinToString(opt.suggestionsDivider)}${opt.suggestionsSuffix}"
} else "${options.arguments.typeChar}${arg.type().typeName}"
} else "${options.arguments.typeChar}${arg.type().typeName}"

// Apply descriptions
if (options.arguments.showDescriptions) {
val extra =
if (arg.isRequired()) options.arguments.descriptionsRequired else options.arguments.descriptionsOptional
end += "\n${options.arguments.descriptionsPrefix}${arg.name()}${options.arguments.descriptionDivider}${arg.description()}$extra"
}

"${options.arguments.prefix}${arg.name()}${if (arg.isRequired()) options.arguments.required else options.arguments.optional}$suggestions${options.arguments.suffix}"
}
builder += end
}
return builder
}
}

inline operator fun <T : CommandSender> String.invoke(block: StringCommand<T>.() -> Unit) =
StringCommand<T>(this).apply(block)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mattmx.ktgui.commands.stringbuilder.arg

import com.mattmx.ktgui.commands.stringbuilder.RawCommandContext
import com.mattmx.ktgui.commands.stringbuilder.syntax.VariableType
import org.bukkit.command.CommandSender
import java.util.*

class Argument<T>(
private val name: String,
private val type: VariableType,
var description: String? = null,
private val required: Boolean = true
) {
private var suggests = Optional.empty<(RawCommandContext<*>) -> List<String>?>()

fun name() = name

fun description() = description ?: ""

fun type() = type

fun isRequired() = required

fun suggests(suggest: RawCommandContext<*>.() -> List<String>?) = apply {
this.suggests = Optional.of(suggest)
}

fun suggestions() = suggests.orElse(null)

fun getDefaultSuggestions() = if (suggests.isPresent) {
val context = RawCommandContext<CommandSender>(emptyList())
suggests.get().invoke(context)
} else listOf()

enum class Type {
SINGLE,
GREEDY
}
}
Loading
Loading