chore: fmt/lint

This commit is contained in:
Keisuke Hirata 2026-03-03 22:20:08 +09:00
parent 2670443135
commit 1e2476a27b
15 changed files with 223 additions and 182 deletions

View File

@ -34,9 +34,10 @@ paper {
description = "Command library" description = "Command library"
version = getVersion().toString() version = getVersion().toString()
apiVersion = "1.21.10" apiVersion = "1.21.10"
authors = listOf( authors =
"Hare-K02" listOf(
) "Hare-K02",
)
serverDependencies { serverDependencies {
register("permits-lib") { register("permits-lib") {
load = PaperPluginDescription.RelativeLoadOrder.BEFORE load = PaperPluginDescription.RelativeLoadOrder.BEFORE

@ -1 +1 @@
Subproject commit 72312a45e029d2e5f0e9d5af0123e701b023974f Subproject commit 7eb0534d2114c6f59c40a10783dbf49d53e00c4d

View File

@ -6,9 +6,11 @@ import net.hareworks.kommand_lib.permissions.PermissionOptions
import net.hareworks.kommand_lib.permissions.PermissionRuntime import net.hareworks.kommand_lib.permissions.PermissionRuntime
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
import org.bukkit.plugin.Plugin
fun kommand(plugin: JavaPlugin, block: KommandRegistry.() -> Unit): KommandLib { fun kommand(
plugin: JavaPlugin,
block: KommandRegistry.() -> Unit,
): KommandLib {
val registry = KommandRegistry(plugin) val registry = KommandRegistry(plugin)
registry.block() registry.block()
return registry.build() return registry.build()
@ -20,7 +22,7 @@ fun kommand(plugin: JavaPlugin, block: KommandRegistry.() -> Unit): KommandLib {
class KommandLib internal constructor( class KommandLib internal constructor(
private val plugin: JavaPlugin, private val plugin: JavaPlugin,
private val definitions: List<CommandDefinition>, private val definitions: List<CommandDefinition>,
private val permissionRuntime: PermissionRuntime? private val permissionRuntime: PermissionRuntime?,
) { ) {
init { init {
registerAll() registerAll()
@ -58,5 +60,5 @@ internal data class CommandDefinition(
val rootCondition: (CommandSender) -> Boolean, val rootCondition: (CommandSender) -> Boolean,
val rootExecutor: (KommandContext.() -> Unit)?, val rootExecutor: (KommandContext.() -> Unit)?,
val nodes: List<net.hareworks.kommand_lib.nodes.KommandNode>, val nodes: List<net.hareworks.kommand_lib.nodes.KommandNode>,
val permissionOptions: PermissionOptions val permissionOptions: PermissionOptions,
) )

View File

@ -1,6 +1,6 @@
package net.hareworks.kommand_lib.plugin; package net.hareworks.kommand_lib.plugin
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.java.JavaPlugin
@Suppress("unused") @Suppress("unused")
public class Plugin : JavaPlugin() {} public class Plugin : JavaPlugin()

View File

@ -5,7 +5,6 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder
import com.mojang.brigadier.builder.RequiredArgumentBuilder import com.mojang.brigadier.builder.RequiredArgumentBuilder
import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.suggestion.SuggestionsBuilder import com.mojang.brigadier.suggestion.SuggestionsBuilder
import com.mojang.brigadier.tree.CommandNode
import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands import io.papermc.paper.command.brigadier.Commands
import net.hareworks.kommand_lib.context.KommandContext import net.hareworks.kommand_lib.context.KommandContext
@ -16,13 +15,13 @@ import org.bukkit.plugin.java.JavaPlugin
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
internal object TreeCompiler { internal object TreeCompiler {
fun compile( fun compile(
plugin: JavaPlugin, plugin: JavaPlugin,
definition: CommandDefinition definition: CommandDefinition,
): LiteralArgumentBuilder<CommandSourceStack> { ): LiteralArgumentBuilder<CommandSourceStack> {
val root = Commands.literal(definition.name) val root =
.requires { source -> definition.rootCondition(source.sender) } Commands.literal(definition.name)
.requires { source -> definition.rootCondition(source.sender) }
// Root execution // Root execution
definition.rootExecutor?.let { executor -> definition.rootExecutor?.let { executor ->
@ -43,18 +42,19 @@ internal object TreeCompiler {
private fun compileNode( private fun compileNode(
plugin: JavaPlugin, plugin: JavaPlugin,
node: KommandNode node: KommandNode,
): ArgumentBuilder<CommandSourceStack, *>? { ): ArgumentBuilder<CommandSourceStack, *>? {
val builder = when (node) { val builder =
is LiteralNode -> { when (node) {
Commands.literal(node.literal) is LiteralNode -> {
Commands.literal(node.literal)
}
is ValueNode<*> -> {
val argType = node.argument.build()
Commands.argument(node.name, argType)
}
else -> return null
} }
is ValueNode<*> -> {
val argType = node.argument.build()
Commands.argument(node.name, argType)
}
else -> return null
}
builder.requires { source -> node.isVisible(source.sender) } builder.requires { source -> node.isVisible(source.sender) }
@ -70,11 +70,14 @@ internal object TreeCompiler {
// Custom Suggestions (if any) // Custom Suggestions (if any)
if (node is ValueNode<*> && node.suggestionProvider != null && builder is RequiredArgumentBuilder<*, *>) { if (node is ValueNode<*> && node.suggestionProvider != null && builder is RequiredArgumentBuilder<*, *>) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
(builder as RequiredArgumentBuilder<CommandSourceStack, Any>).suggests { ctx: CommandContext<CommandSourceStack>, suggestionsBuilder: SuggestionsBuilder -> (builder as RequiredArgumentBuilder<CommandSourceStack, Any>).suggests {
val context = KommandContext(plugin, ctx) ctx: CommandContext<CommandSourceStack>,
val suggestions = node.suggestionProvider!!.invoke(context, suggestionsBuilder.remaining) suggestionsBuilder: SuggestionsBuilder,
suggestions.forEach { suggestionsBuilder.suggest(it) } ->
suggestionsBuilder.buildFuture() val context = KommandContext(plugin, ctx)
val suggestions = node.suggestionProvider!!.invoke(context, suggestionsBuilder.remaining)
suggestions.forEach { suggestionsBuilder.suggest(it) }
suggestionsBuilder.buildFuture()
} }
} }

View File

@ -6,13 +6,10 @@ import com.mojang.brigadier.arguments.DoubleArgumentType
import com.mojang.brigadier.arguments.IntegerArgumentType import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.arguments.StringArgumentType import com.mojang.brigadier.arguments.StringArgumentType
import io.papermc.paper.command.brigadier.argument.ArgumentTypes import io.papermc.paper.command.brigadier.argument.ArgumentTypes
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.util.Vector
import org.bukkit.Location
import org.bukkit.command.CommandSender
/** /**
* A holder for the Brigadier ArgumentType and any metadata needed for the DSL. * A holder for the Brigadier ArgumentType and any metadata needed for the DSL.
@ -33,17 +30,16 @@ class GreedyStringArgument : KommandArgument<String> {
override fun build(): ArgumentType<String> = StringArgumentType.greedyString() override fun build(): ArgumentType<String> = StringArgumentType.greedyString()
} }
class IntegerArgument( class IntegerArgument(
private val min: Int = Int.MIN_VALUE, private val min: Int = Int.MIN_VALUE,
private val max: Int = Int.MAX_VALUE private val max: Int = Int.MAX_VALUE,
) : KommandArgument<Int> { ) : KommandArgument<Int> {
override fun build(): ArgumentType<Int> = IntegerArgumentType.integer(min, max) override fun build(): ArgumentType<Int> = IntegerArgumentType.integer(min, max)
} }
class FloatArgument( class FloatArgument(
private val min: Double = -Double.MAX_VALUE, private val min: Double = -Double.MAX_VALUE,
private val max: Double = Double.MAX_VALUE private val max: Double = Double.MAX_VALUE,
) : KommandArgument<Double> { ) : KommandArgument<Double> {
override fun build(): ArgumentType<Double> = DoubleArgumentType.doubleArg(min, max) override fun build(): ArgumentType<Double> = DoubleArgumentType.doubleArg(min, max)
} }

View File

@ -2,12 +2,10 @@ package net.hareworks.kommand_lib.context
import com.mojang.brigadier.context.CommandContext import com.mojang.brigadier.context.CommandContext
import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver
import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver
import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver
import io.papermc.paper.math.Position import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver
import org.bukkit.entity.Entity import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
import org.bukkit.entity.Player import org.bukkit.entity.Player
/** /**
@ -18,12 +16,14 @@ import org.bukkit.entity.Player
* but it's not intended for direct use by library consumers. * but it's not intended for direct use by library consumers.
*/ */
object ArgumentResolver { object ArgumentResolver {
/** /**
* Resolves an argument from the command context. * Resolves an argument from the command context.
* Handles special cases for Paper's selector resolvers and position resolvers. * Handles special cases for Paper's selector resolvers and position resolvers.
*/ */
inline fun <reified T> resolve(context: CommandContext<CommandSourceStack>, name: String): T { inline fun <reified T> resolve(
context: CommandContext<CommandSourceStack>,
name: String,
): T {
val rawValue = context.getArgument(name, Any::class.java) val rawValue = context.getArgument(name, Any::class.java)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -64,7 +64,10 @@ object ArgumentResolver {
/** /**
* Resolves an argument or returns null if not found. * Resolves an argument or returns null if not found.
*/ */
inline fun <reified T> resolveOrNull(context: CommandContext<CommandSourceStack>, name: String): T? { inline fun <reified T> resolveOrNull(
context: CommandContext<CommandSourceStack>,
name: String,
): T? {
return try { return try {
resolve<T>(context, name) resolve<T>(context, name)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {

View File

@ -7,7 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin
class KommandContext internal constructor( class KommandContext internal constructor(
val plugin: JavaPlugin, val plugin: JavaPlugin,
val internal: CommandContext<CommandSourceStack> val internal: CommandContext<CommandSourceStack>,
) { ) {
val sender: CommandSender val sender: CommandSender
get() = internal.source.sender get() = internal.source.sender

View File

@ -2,13 +2,13 @@ package net.hareworks.kommand_lib.dsl
import net.hareworks.kommand_lib.CommandDefinition import net.hareworks.kommand_lib.CommandDefinition
import net.hareworks.kommand_lib.arguments.* import net.hareworks.kommand_lib.arguments.*
import net.hareworks.kommand_lib.nodes.KommandNode
import net.hareworks.kommand_lib.nodes.LiteralNode
import net.hareworks.kommand_lib.nodes.ValueNode
import net.hareworks.kommand_lib.permissions.PermissionConfigBuilder import net.hareworks.kommand_lib.permissions.PermissionConfigBuilder
import net.hareworks.kommand_lib.permissions.PermissionOptions import net.hareworks.kommand_lib.permissions.PermissionOptions
import net.hareworks.kommand_lib.permissions.PermissionPlanner import net.hareworks.kommand_lib.permissions.PermissionPlanner
import net.hareworks.kommand_lib.permissions.PermissionRuntime import net.hareworks.kommand_lib.permissions.PermissionRuntime
import net.hareworks.kommand_lib.nodes.KommandNode
import net.hareworks.kommand_lib.nodes.LiteralNode
import net.hareworks.kommand_lib.nodes.ValueNode
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.entity.Player import org.bukkit.entity.Player
@ -19,13 +19,21 @@ class KommandRegistry internal constructor(private val plugin: JavaPlugin) {
private val definitions = mutableListOf<CommandDefinition>() private val definitions = mutableListOf<CommandDefinition>()
private var permissionConfigBuilder: PermissionConfigBuilder? = null private var permissionConfigBuilder: PermissionConfigBuilder? = null
fun command(name: String, vararg aliases: String, block: CommandBuilder.() -> Unit) { fun command(
name: String,
vararg aliases: String,
block: CommandBuilder.() -> Unit,
) {
val builder = CommandBuilder(name, aliases.toList()) val builder = CommandBuilder(name, aliases.toList())
builder.block() builder.block()
definitions += builder.build() definitions += builder.build()
} }
fun command(name: String, aliases: Iterable<String>, block: CommandBuilder.() -> Unit) { fun command(
name: String,
aliases: Iterable<String>,
block: CommandBuilder.() -> Unit,
) {
val builder = CommandBuilder(name, aliases.toList()) val builder = CommandBuilder(name, aliases.toList())
builder.block() builder.block()
definitions += builder.build() definitions += builder.build()
@ -39,10 +47,11 @@ class KommandRegistry internal constructor(private val plugin: JavaPlugin) {
internal fun build(): net.hareworks.kommand_lib.KommandLib { internal fun build(): net.hareworks.kommand_lib.KommandLib {
val snapshot = definitions.toList() val snapshot = definitions.toList()
val config = permissionConfigBuilder?.build() val config = permissionConfigBuilder?.build()
val runtime = config?.let { val runtime =
val plan = PermissionPlanner(plugin, it, snapshot).plan() config?.let {
if (plan.isEmpty()) null else PermissionRuntime(plugin, plan) val plan = PermissionPlanner(plugin, it, snapshot).plan()
} if (plan.isEmpty()) null else PermissionRuntime(plugin, plan)
}
return net.hareworks.kommand_lib.KommandLib(plugin, snapshot, runtime) return net.hareworks.kommand_lib.KommandLib(plugin, snapshot, runtime)
} }
} }
@ -50,7 +59,7 @@ class KommandRegistry internal constructor(private val plugin: JavaPlugin) {
@KommandDsl @KommandDsl
class CommandBuilder internal constructor( class CommandBuilder internal constructor(
val name: String, val name: String,
val aliases: List<String> val aliases: List<String>,
) : BranchScope(mutableListOf()) { ) : BranchScope(mutableListOf()) {
var description: String? = null var description: String? = null
var usage: String? = null var usage: String? = null
@ -98,18 +107,21 @@ class CommandBuilder internal constructor(
rootCondition = condition, rootCondition = condition,
rootExecutor = rootExecutor, rootExecutor = rootExecutor,
nodes = children.toList(), nodes = children.toList(),
permissionOptions = permissionOptions permissionOptions = permissionOptions,
) )
} }
@KommandDsl @KommandDsl
abstract class BranchScope internal constructor( abstract class BranchScope internal constructor(
protected val children: MutableList<KommandNode> protected val children: MutableList<KommandNode>,
) { ) {
protected abstract val inheritedPermission: String? protected abstract val inheritedPermission: String?
protected abstract val inheritedCondition: (CommandSender) -> Boolean protected abstract val inheritedCondition: (CommandSender) -> Boolean
fun literal(name: String, block: LiteralBuilder.() -> Unit = {}) { fun literal(
name: String,
block: LiteralBuilder.() -> Unit = {},
) {
val node = LiteralNode(name) val node = LiteralNode(name)
node.permission = inheritedPermission node.permission = inheritedPermission
node.condition = inheritedCondition node.condition = inheritedCondition
@ -117,7 +129,11 @@ abstract class BranchScope internal constructor(
LiteralBuilder(node).apply(block) LiteralBuilder(node).apply(block)
} }
fun <T> argument(name: String, type: KommandArgument<T>, block: ValueBuilder<T>.() -> Unit = {}) { fun <T> argument(
name: String,
type: KommandArgument<T>,
block: ValueBuilder<T>.() -> Unit = {},
) {
val node = ValueNode(name, type) val node = ValueNode(name, type)
node.permission = inheritedPermission node.permission = inheritedPermission
node.condition = inheritedCondition node.condition = inheritedCondition
@ -126,64 +142,69 @@ abstract class BranchScope internal constructor(
ValueBuilder(node).apply(block) ValueBuilder(node).apply(block)
} }
fun string(name: String, block: ValueBuilder<String>.() -> Unit = {}) = argument(name, WordArgument(), block) fun string(
name: String,
fun greedyString(name: String, block: ValueBuilder<String>.() -> Unit = {}) = argument(name, GreedyStringArgument(), block) block: ValueBuilder<String>.() -> Unit = {},
) = argument(name, WordArgument(), block)
fun greedyString(
name: String,
block: ValueBuilder<String>.() -> Unit = {},
) = argument(name, GreedyStringArgument(), block)
fun integer( fun integer(
name: String, name: String,
min: Int = Int.MIN_VALUE, min: Int = Int.MIN_VALUE,
max: Int = Int.MAX_VALUE, max: Int = Int.MAX_VALUE,
block: ValueBuilder<Int>.() -> Unit = {} block: ValueBuilder<Int>.() -> Unit = {},
) = argument(name, IntegerArgument(min, max), block) ) = argument(name, IntegerArgument(min, max), block)
fun float( fun float(
name: String, name: String,
min: Double = -Double.MAX_VALUE, min: Double = -Double.MAX_VALUE,
max: Double = Double.MAX_VALUE, max: Double = Double.MAX_VALUE,
block: ValueBuilder<Double>.() -> Unit = {} block: ValueBuilder<Double>.() -> Unit = {},
) = argument(name, FloatArgument(min, max), block) ) = argument(name, FloatArgument(min, max), block)
fun bool( fun bool(
name: String, name: String,
block: ValueBuilder<Boolean>.() -> Unit = {} block: ValueBuilder<Boolean>.() -> Unit = {},
) = argument(name, BooleanArgument(), block) ) = argument(name, BooleanArgument(), block)
fun player( fun player(
name: String, name: String,
allowSelectors: Boolean = true, // Ignored logic-wise if using native, assuming it handles selectors allowSelectors: Boolean = true, // Ignored logic-wise if using native, assuming it handles selectors
block: ValueBuilder<Player>.() -> Unit = {} block: ValueBuilder<Player>.() -> Unit = {},
) = argument(name, PlayerArgument(), block) ) = argument(name, PlayerArgument(), block)
fun players( fun players(
name: String, name: String,
allowDirectNames: Boolean = true, allowDirectNames: Boolean = true,
block: ValueBuilder<List<Player>>.() -> Unit = {} block: ValueBuilder<List<Player>>.() -> Unit = {},
) = argument(name, PlayersArgument(), block) ) = argument(name, PlayersArgument(), block)
fun selector( fun selector(
name: String, name: String,
requireMatch: Boolean = true, requireMatch: Boolean = true,
block: ValueBuilder<List<Entity>>.() -> Unit = {} block: ValueBuilder<List<Entity>>.() -> Unit = {},
) = argument(name, EntityArgument(), block) ) = argument(name, EntityArgument(), block)
fun coordinates( fun coordinates(
name: String, name: String,
allowRelative: Boolean = true, allowRelative: Boolean = true,
block: ValueBuilder<io.papermc.paper.math.Position>.() -> Unit = {} block: ValueBuilder<io.papermc.paper.math.Position>.() -> Unit = {},
) = argument(name, CoordinatesArgument(), block) ) = argument(name, CoordinatesArgument(), block)
fun blockCoordinates( fun blockCoordinates(
name: String, name: String,
allowRelative: Boolean = true, allowRelative: Boolean = true,
block: ValueBuilder<io.papermc.paper.math.Position>.() -> Unit = {} block: ValueBuilder<io.papermc.paper.math.Position>.() -> Unit = {},
) = argument(name, BlockPositionArgument(), block) ) = argument(name, BlockPositionArgument(), block)
} }
@KommandDsl @KommandDsl
abstract class NodeScope internal constructor( abstract class NodeScope internal constructor(
protected val node: KommandNode protected val node: KommandNode,
) : BranchScope(node.children) { ) : BranchScope(node.children) {
override val inheritedPermission: String? override val inheritedPermission: String?
get() = node.permission get() = node.permission
@ -215,12 +236,12 @@ abstract class NodeScope internal constructor(
@KommandDsl @KommandDsl
class LiteralBuilder internal constructor( class LiteralBuilder internal constructor(
private val literalNode: LiteralNode private val literalNode: LiteralNode,
) : NodeScope(literalNode) ) : NodeScope(literalNode)
@KommandDsl @KommandDsl
class ValueBuilder<T> internal constructor( class ValueBuilder<T> internal constructor(
private val valueNode: ValueNode<T> private val valueNode: ValueNode<T>,
) : NodeScope(valueNode) { ) : NodeScope(valueNode) {
/** /**
* Overrides the default suggestion provider (wrapper around Brigadier logic) * Overrides the default suggestion provider (wrapper around Brigadier logic)

View File

@ -27,7 +27,7 @@ class LiteralNode internal constructor(val literal: String) : KommandNode() {
class ValueNode<T> internal constructor( class ValueNode<T> internal constructor(
val name: String, val name: String,
val argument: KommandArgument<T> val argument: KommandArgument<T>,
) : KommandNode() { ) : KommandNode() {
var suggestionProvider: ((KommandContext, String) -> List<String>)? = null var suggestionProvider: ((KommandContext, String) -> List<String>)? = null

View File

@ -15,7 +15,7 @@ class PermissionConfig internal constructor(
val defaultDescription: (PermissionContext) -> String?, val defaultDescription: (PermissionContext) -> String?,
val defaultValue: PermissionDefault, val defaultValue: PermissionDefault,
val defaultWildcard: Boolean, val defaultWildcard: Boolean,
private val sessionProvider: (JavaPlugin) -> MutationSession private val sessionProvider: (JavaPlugin) -> MutationSession,
) { ) {
fun session(plugin: JavaPlugin): MutationSession = sessionProvider(plugin) fun session(plugin: JavaPlugin): MutationSession = sessionProvider(plugin)
} }
@ -61,18 +61,18 @@ class PermissionConfigBuilder internal constructor(private val plugin: JavaPlugi
defaultDescription = descriptionTemplate, defaultDescription = descriptionTemplate,
defaultValue = defaultValue, defaultValue = defaultValue,
defaultWildcard = wildcard, defaultWildcard = wildcard,
sessionProvider = sessionFactory ?: { PermitsLib.session(it) } sessionProvider = sessionFactory ?: { PermitsLib.session(it) },
) )
} }
data class PermissionContext( data class PermissionContext(
val commandName: String, val commandName: String,
val path: List<String>, val path: List<String>,
val kind: PermissionNodeKind val kind: PermissionNodeKind,
) )
enum class PermissionNodeKind { enum class PermissionNodeKind {
COMMAND, COMMAND,
LITERAL, LITERAL,
ARGUMENT ARGUMENT,
} }

View File

@ -16,10 +16,11 @@ class PermissionOptions {
private set private set
fun rename(vararg segments: String) { fun rename(vararg segments: String) {
customPath = segments customPath =
.map { it.trim() } segments
.filter { it.isNotEmpty() } .map { it.trim() }
.toMutableList() .filter { it.isNotEmpty() }
.toMutableList()
} }
internal fun renameOverride(): List<String>? = customPath?.toList() internal fun renameOverride(): List<String>? = customPath?.toList()
@ -42,13 +43,14 @@ class PermissionOptions {
} }
class WildcardOptions internal constructor( class WildcardOptions internal constructor(
private val sink: MutableList<List<String>> private val sink: MutableList<List<String>>,
) { ) {
fun exclude(vararg segments: String) { fun exclude(vararg segments: String) {
val normalized = segments val normalized =
.flatMap { it.split('.') } segments
.map { it.trim().lowercase() } .flatMap { it.split('.') }
.filter { it.isNotEmpty() } .map { it.trim().lowercase() }
.filter { it.isNotEmpty() }
if (normalized.isNotEmpty()) { if (normalized.isNotEmpty()) {
sink += normalized sink += normalized
} }

View File

@ -5,9 +5,10 @@ import org.bukkit.permissions.PermissionDefault
data class PermissionPlan( data class PermissionPlan(
val config: PermissionConfig, val config: PermissionConfig,
val entries: List<PlannedPermission> val entries: List<PlannedPermission>,
) { ) {
val namespace: String get() = config.namespace val namespace: String get() = config.namespace
fun isEmpty(): Boolean = entries.isEmpty() fun isEmpty(): Boolean = entries.isEmpty()
} }
@ -20,5 +21,5 @@ data class PlannedPermission(
val wildcardExclusions: List<List<String>>, val wildcardExclusions: List<List<String>>,
val inheritsParentDefault: Boolean, val inheritsParentDefault: Boolean,
val wildcard: Boolean, val wildcard: Boolean,
val registration: NodeRegistration val registration: NodeRegistration,
) )

View File

@ -11,40 +11,44 @@ import org.bukkit.plugin.java.JavaPlugin
internal class PermissionPlanner( internal class PermissionPlanner(
private val plugin: JavaPlugin, private val plugin: JavaPlugin,
private val config: PermissionConfig, private val config: PermissionConfig,
private val definitions: List<CommandDefinition> private val definitions: List<CommandDefinition>,
) { ) {
fun plan(): PermissionPlan { fun plan(): PermissionPlan {
val entries = linkedMapOf<String, PlannedPermission>() val entries = linkedMapOf<String, PlannedPermission>()
val (rootPath, rootDefault) = if (config.includeRootNode && config.rootSegment.isNotBlank()) { val (rootPath, rootDefault) =
val path = listOf(config.rootSegment) if (config.includeRootNode && config.rootSegment.isNotBlank()) {
val entry = createEntry( val path = listOf(config.rootSegment)
options = PermissionOptions().apply { id = buildId(path) }, val entry =
pathSegments = path, createEntry(
context = PermissionContext(commandName = "", path = path, kind = PermissionNodeKind.LITERAL), options = PermissionOptions().apply { id = buildId(path) },
parentDefault = config.defaultValue, pathSegments = path,
registration = NodeRegistration.STRUCTURAL context = PermissionContext(commandName = "", path = path, kind = PermissionNodeKind.LITERAL),
) parentDefault = config.defaultValue,
if (entry != null) entries[entry.id] = entry registration = NodeRegistration.STRUCTURAL,
path to (entry?.defaultValue ?: config.defaultValue) )
} else { if (entry != null) entries[entry.id] = entry
emptyList<String>() to config.defaultValue path to (entry?.defaultValue ?: config.defaultValue)
} } else {
emptyList<String>() to config.defaultValue
}
definitions.forEach { definition -> definitions.forEach { definition ->
val overridePath = definition.permissionOptions.renameOverride() val overridePath = definition.permissionOptions.renameOverride()
val commandPath = if (overridePath != null) { val commandPath =
normalizeSegments(overridePath) if (overridePath != null) {
} else { normalizeSegments(overridePath)
val sanitized = sanitize(definition.name) } else {
val base = if (rootPath.isNotEmpty()) rootPath else emptyList() val sanitized = sanitize(definition.name)
base + sanitized val base = if (rootPath.isNotEmpty()) rootPath else emptyList()
} base + sanitized
val commandEntry = createEntry( }
options = definition.permissionOptions, val commandEntry =
pathSegments = commandPath, createEntry(
context = PermissionContext(definition.name, commandPath, PermissionNodeKind.COMMAND), options = definition.permissionOptions,
parentDefault = rootDefault pathSegments = commandPath,
) context = PermissionContext(definition.name, commandPath, PermissionNodeKind.COMMAND),
parentDefault = rootDefault,
)
if (commandEntry != null) { if (commandEntry != null) {
entries[commandEntry.id] = commandEntry entries[commandEntry.id] = commandEntry
if (definition.permission.isNullOrBlank()) { if (definition.permission.isNullOrBlank()) {
@ -64,14 +68,16 @@ internal class PermissionPlanner(
basePath: List<String>, basePath: List<String>,
entries: MutableMap<String, PlannedPermission>, entries: MutableMap<String, PlannedPermission>,
commandName: String, commandName: String,
parentDefault: PermissionDefault parentDefault: PermissionDefault,
) { ) {
val rawOverride = node.permissionOptions.renameOverride() val rawOverride = node.permissionOptions.renameOverride()
val shouldSkip = val shouldSkip =
node.permissionOptions.skip || node.permissionOptions.skip ||
(node.permissionOptions.preferSkipByDefault && (
node.permissionOptions.id.isNullOrBlank() && node.permissionOptions.preferSkipByDefault &&
rawOverride == null) node.permissionOptions.id.isNullOrBlank() &&
rawOverride == null
)
if (shouldSkip) { if (shouldSkip) {
node.children.forEach { child -> node.children.forEach { child ->
planNode(child, basePath, entries, commandName, parentDefault) planNode(child, basePath, entries, commandName, parentDefault)
@ -80,44 +86,48 @@ internal class PermissionPlanner(
} }
val segment = node.segment()?.let { sanitize(it) } val segment = node.segment()?.let { sanitize(it) }
val pathAddition = rawOverride?.let { normalizeSegments(it) } val pathAddition = rawOverride?.let { normalizeSegments(it) }
val path = when { val path =
pathAddition != null -> basePath + pathAddition when {
segment != null -> basePath + segment pathAddition != null -> basePath + pathAddition
else -> basePath segment != null -> basePath + segment
} else -> basePath
val entry = createEntry( }
options = node.permissionOptions, val entry =
pathSegments = path, createEntry(
context = PermissionContext(commandName, path, node.toKind()), options = node.permissionOptions,
parentDefault = parentDefault pathSegments = path,
) context = PermissionContext(commandName, path, node.toKind()),
val currentBase = if (entry != null) { parentDefault = parentDefault,
entries[entry.id] = entry )
if (node.permission.isNullOrBlank()) { val currentBase =
node.permission = entry.id if (entry != null) {
entries[entry.id] = entry
if (node.permission.isNullOrBlank()) {
node.permission = entry.id
}
path
} else {
basePath
} }
path
} else {
basePath
}
val nextDefault = entry?.defaultValue ?: parentDefault val nextDefault = entry?.defaultValue ?: parentDefault
node.children.forEach { child -> node.children.forEach { child ->
planNode(child, currentBase, entries, commandName, nextDefault) planNode(child, currentBase, entries, commandName, nextDefault)
} }
} }
private fun KommandNode.toKind(): PermissionNodeKind = when (this) { private fun KommandNode.toKind(): PermissionNodeKind =
is LiteralNode -> PermissionNodeKind.LITERAL when (this) {
is ValueNode<*> -> PermissionNodeKind.ARGUMENT is LiteralNode -> PermissionNodeKind.LITERAL
else -> PermissionNodeKind.LITERAL is ValueNode<*> -> PermissionNodeKind.ARGUMENT
} else -> PermissionNodeKind.LITERAL
}
private fun createEntry( private fun createEntry(
options: PermissionOptions, options: PermissionOptions,
pathSegments: List<String>, pathSegments: List<String>,
context: PermissionContext, context: PermissionContext,
parentDefault: PermissionDefault, parentDefault: PermissionDefault,
registration: NodeRegistration = NodeRegistration.PERMISSION registration: NodeRegistration = NodeRegistration.PERMISSION,
): PlannedPermission? { ): PlannedPermission? {
val finalId = (options.id?.takeIf { it.isNotBlank() } ?: buildId(pathSegments)).trim() val finalId = (options.id?.takeIf { it.isNotBlank() } ?: buildId(pathSegments)).trim()
if (finalId.isEmpty()) return null if (finalId.isEmpty()) return null
@ -132,9 +142,10 @@ internal class PermissionPlanner(
val explicitDefault = options.defaultValue val explicitDefault = options.defaultValue
val defaultValue = explicitDefault ?: parentDefault val defaultValue = explicitDefault ?: parentDefault
val wildcard = options.wildcard ?: config.defaultWildcard val wildcard = options.wildcard ?: config.defaultWildcard
val wildcardExclusions = options.wildcardExclusions val wildcardExclusions =
.map { normalizeSegments(it) } options.wildcardExclusions
.filter { it.isNotEmpty() } .map { normalizeSegments(it) }
.filter { it.isNotEmpty() }
options.resolve(finalId) options.resolve(finalId)
val parentPath = if (relativePath.isNotEmpty()) relativePath.dropLast(1).takeIf { it.isNotEmpty() } else null val parentPath = if (relativePath.isNotEmpty()) relativePath.dropLast(1).takeIf { it.isNotEmpty() } else null
return PlannedPermission( return PlannedPermission(
@ -146,16 +157,14 @@ internal class PermissionPlanner(
wildcardExclusions = wildcardExclusions, wildcardExclusions = wildcardExclusions,
inheritsParentDefault = explicitDefault == null, inheritsParentDefault = explicitDefault == null,
wildcard = wildcard, wildcard = wildcard,
registration = registration registration = registration,
) )
} }
private fun buildId(pathSegments: List<String>): String = private fun buildId(pathSegments: List<String>): String =
(listOf(config.namespace) + pathSegments).filter { it.isNotBlank() }.joinToString(".") (listOf(config.namespace) + pathSegments).filter { it.isNotBlank() }.joinToString(".")
private fun sanitize(segment: String): String = private fun sanitize(segment: String): String = segment.trim().lowercase().replace(Regex("[^a-z0-9._-]"), "-").trim('-')
segment.trim().lowercase().replace(Regex("[^a-z0-9._-]"), "-").trim('-')
private fun normalizeSegments(segments: List<String>): List<String> = private fun normalizeSegments(segments: List<String>): List<String> = segments.map { sanitize(it) }.filter { it.isNotBlank() }
segments.map { sanitize(it) }.filter { it.isNotBlank() }
} }

View File

@ -7,7 +7,7 @@ import org.bukkit.plugin.java.JavaPlugin
internal class PermissionRuntime( internal class PermissionRuntime(
private val plugin: JavaPlugin, private val plugin: JavaPlugin,
private val plan: PermissionPlan private val plan: PermissionPlan,
) { ) {
private val session: MutationSession by lazy { plan.config.session(plugin) } private val session: MutationSession by lazy { plan.config.session(plugin) }
val config: PermissionConfig get() = plan.config val config: PermissionConfig get() = plan.config
@ -16,25 +16,28 @@ internal class PermissionRuntime(
if (plan.isEmpty()) return if (plan.isEmpty()) return
val mutable = MutablePermissionTree.create(plan.config.namespace) val mutable = MutablePermissionTree.create(plan.config.namespace)
val sorted = plan.entries.sortedBy { it.relativePath.size } val sorted = plan.entries.sortedBy { it.relativePath.size }
val registrations = sorted val registrations =
.mapNotNull { entry -> sorted
entry.relativePath.takeIf { it.isNotEmpty() }?.joinToString(".")?.let { it to entry.registration } .mapNotNull { entry ->
} entry.relativePath.takeIf { it.isNotEmpty() }?.joinToString(".")?.let { it to entry.registration }
.toMap() }
val entriesByPath = sorted .toMap()
.filter { it.relativePath.isNotEmpty() } val entriesByPath =
.associateBy { it.relativePath.joinToString(".") } sorted
.filter { it.relativePath.isNotEmpty() }
.associateBy { it.relativePath.joinToString(".") }
sorted.forEach { entry -> sorted.forEach { entry ->
if (entry.relativePath.isEmpty()) { if (entry.relativePath.isEmpty()) {
plugin.logger.warning("Skipping permission '${entry.id}' because it resolved to the namespace root.") plugin.logger.warning("Skipping permission '${entry.id}' because it resolved to the namespace root.")
return@forEach return@forEach
} }
val nodeId = entry.relativePath.joinToString(".") val nodeId = entry.relativePath.joinToString(".")
val currentNode = mutable.node(nodeId, entry.registration) { val currentNode =
entry.description?.let { description = it } mutable.node(nodeId, entry.registration) {
defaultValue = entry.defaultValue entry.description?.let { description = it }
wildcard = entry.wildcard defaultValue = entry.defaultValue
} wildcard = entry.wildcard
}
if (entry.wildcard && entry.wildcardExclusions.isNotEmpty()) { if (entry.wildcard && entry.wildcardExclusions.isNotEmpty()) {
entry.wildcardExclusions.forEach { exclusion -> entry.wildcardExclusions.forEach { exclusion ->
val absolutePath = entry.relativePath + exclusion val absolutePath = entry.relativePath + exclusion