From 8b69ad484ddc347fc7ced1c69b70673202e39e00 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 28 Nov 2025 09:02:25 +0900 Subject: [PATCH] 1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Permits-libを用いたダイナミックPermission対応 - ビルド設定の見直し --- README.md | 37 +++++ build.gradle.kts | 27 ++-- gradle.properties | 1 + settings.gradle.kts | 10 +- .../kotlin/net/hareworks/kommand-lib/App.kt | 14 -- .../net/hareworks/kommand-lib/Kommand.kt | 38 ++--- .../net/hareworks/kommand-lib/Plugin.kt | 6 + .../kommand-lib/dsl/RegistryBuilders.kt | 51 ++++++- .../net/hareworks/kommand-lib/nodes/Nodes.kt | 8 + .../permissions/PermissionConfig.kt | 85 +++++++++++ .../permissions/PermissionOptions.kt | 37 +++++ .../kommand-lib/permissions/PermissionPlan.kt | 21 +++ .../permissions/PermissionPlanner.kt | 139 ++++++++++++++++++ .../permissions/PermissionRuntime.kt | 41 ++++++ 14 files changed, 454 insertions(+), 61 deletions(-) delete mode 100644 src/main/kotlin/net/hareworks/kommand-lib/App.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/Plugin.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionConfig.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionOptions.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlan.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlanner.kt create mode 100644 src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionRuntime.kt diff --git a/README.md b/README.md index d67fb56..f2286a8 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Paper/Bukkit サーバー向けのコマンド定義を DSL で記述するた - 1 つの定義から実行とタブ補完の両方を生成 - パーミッションや条件をノード単位で宣言し、子ノードへ自動伝播 - `suggests {}` で引数ごとの補完候補を柔軟に制御 +- `permits-lib` との連携により、コマンドツリーから Bukkit パーミッションを自動生成し、`compileOnly` 依存として参照可能 ## 依存関係 @@ -24,6 +25,8 @@ plugins { dependencies { compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") compileOnly("org.jetbrains.kotlin:kotlin-stdlib") + // ../permits-lib を includeBuild しているので module 参照でOK + compileOnly("net.hareworks.hcu:permits-lib:1.0") } ``` @@ -107,6 +110,40 @@ class EconomyPlugin : JavaPlugin() { - `command` や各ノードの `condition { sender -> ... }` で実行条件 (例: コンソール禁止) を追加できます。 - ルートレベルで `executes { ... }` を指定すると、引数なしで `/eco` を実行した場合に呼び出されます。 +## 自動パーミッション生成 (`permits-lib`) + +`permits-lib` を `compileOnly` で参照している場合、DSL から Bukkit パーミッションツリーを自動生成できます。 + +```kotlin +commands = kommand(this) { + permissions { + namespace = "hareworks" + rootSegment = "command" + defaultDescription { ctx -> + "Allows /${ctx.commandName} (${ctx.path.joinToString(" ")})" + } + } + + command("eco") { + permission { + description = "Allows /eco" + tags("economy") + } + + literal("give") { + permission { + description = "Allows /eco give" + } + } + } +} +``` + +- `permissions { ... }` で名前空間やルートセグメント (`rootSegment = "command"`) を定義すると、`hareworks.command.eco`, `hareworks.command.eco.give` のような ID が自動生成され、`permits-lib` の `MutationSession` を使って Bukkit に適用されます。 +- 各 `command`/`literal`/`argument` ブロック内で `permission { ... }` を宣言すると、説明文・デフォルト値・タグ・パスの上書きを細かく制御できます。`skipPermission()` を呼び出せば、そのノードだけ自動生成から除外されます。 +- `requires("custom.id")` を指定した場合も、同じ ID が DSL の実行と `permits-lib` への登録の両方で利用されます (名前空間外の ID は登録対象外になります)。 +- `KommandLib` のライフサイクルに合わせて `MutationSession` が適用/解除されるため、プラグインの有効化・無効化に伴い Bukkit のパーミッションリストも最新状態に保たれます。 + ## 組み込み引数の一覧 | DSL | 返り値 | 補足 | diff --git a/build.gradle.kts b/build.gradle.kts index 4eb4bd5..2a401d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ -import net.minecrell.pluginyml.bukkit.BukkitPluginDescription +import net.minecrell.pluginyml.paper.PaperPluginDescription -group = "net.hareworks.hcu" -version = "1.0" +group = "net.hareworks" +version = "1.1" plugins { kotlin("jvm") version "2.2.21" @@ -18,22 +18,31 @@ val exposedVersion = "1.0.0-rc-3" dependencies { compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") compileOnly("org.jetbrains.kotlin:kotlin-stdlib") + compileOnly("net.hareworks:permits-lib:1.1") } tasks { shadowJar { - archiveBaseName.set("economy") + minimize() + archiveBaseName.set("Kommand-Lib") archiveClassifier.set("") } + jar { + enabled = false + } } paper { - main = "net.hareworks.kommand_lib.App" + main = "net.hareworks.kommand_lib.plugin.Plugin" name = "kommand-lib" description = "Command library" version = getVersion().toString() apiVersion = "1.21.10" - authors = - listOf( - "Hare-K02" - ) + authors = listOf( + "Hare-K02" + ) + serverDependencies { + register("permits-lib") { + load = PaperPluginDescription.RelativeLoadOrder.BEFORE + } + } } diff --git a/gradle.properties b/gradle.properties index 5154008..ef5e4b3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,4 @@ org.gradle.configuration-cache=true org.gradle.parallel=true org.gradle.caching=true +kotlin.stdlib.default.dependency=false diff --git a/settings.gradle.kts b/settings.gradle.kts index 292e9b0..2862be1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,9 +1,3 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.14.3/userguide/multi_project_builds.html in the Gradle documentation. - * This project uses @Incubating APIs which are subject to change. - */ - rootProject.name = "kommand-lib" + +includeBuild("../permits-lib") diff --git a/src/main/kotlin/net/hareworks/kommand-lib/App.kt b/src/main/kotlin/net/hareworks/kommand-lib/App.kt deleted file mode 100644 index 38514f6..0000000 --- a/src/main/kotlin/net/hareworks/kommand-lib/App.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.hareworks.kommand_lib; - -import org.bukkit.plugin.java.JavaPlugin - -public class App : JavaPlugin() { - companion object { - lateinit var instance: App - private set - } - - override fun onEnable() { - instance = this - } -} diff --git a/src/main/kotlin/net/hareworks/kommand-lib/Kommand.kt b/src/main/kotlin/net/hareworks/kommand-lib/Kommand.kt index 5c6a366..2e71ff7 100644 --- a/src/main/kotlin/net/hareworks/kommand-lib/Kommand.kt +++ b/src/main/kotlin/net/hareworks/kommand-lib/Kommand.kt @@ -4,6 +4,8 @@ import net.hareworks.kommand_lib.context.KommandContext import net.hareworks.kommand_lib.execution.CommandTree import net.hareworks.kommand_lib.execution.ParseMode import net.hareworks.kommand_lib.dsl.KommandRegistry +import net.hareworks.kommand_lib.permissions.PermissionOptions +import net.hareworks.kommand_lib.permissions.PermissionRuntime import org.bukkit.Bukkit import org.bukkit.command.CommandMap import org.bukkit.command.CommandSender @@ -11,30 +13,6 @@ import org.bukkit.command.PluginCommand import org.bukkit.command.TabCompleter import org.bukkit.plugin.java.JavaPlugin -/** - * Entry-point for registering commands via the Kommand DSL. - * - * Example: - * ```kotlin - * kommand(plugin) { - * command("eco", "economy") { - * description = "Economy management" - * permission = "example.eco" - * - * literal("give") { - * string("player") - * integer("amount", min = 0) { - * executes { - * val targetName = string("player") - * val amount = int("amount") - * sender.sendMessage("Giving $amount to $targetName") - * } - * } - * } - * } - * } - * ``` - */ fun kommand(plugin: JavaPlugin, block: KommandRegistry.() -> Unit): KommandLib { val registry = KommandRegistry(plugin) registry.block() @@ -46,7 +24,8 @@ fun kommand(plugin: JavaPlugin, block: KommandRegistry.() -> Unit): KommandLib { */ class KommandLib internal constructor( private val plugin: JavaPlugin, - private val definitions: List + private val definitions: List, + private val permissionRuntime: PermissionRuntime? ) { private val commandMap: CommandMap by lazy { val field = Bukkit.getServer().javaClass.getDeclaredField("commandMap") @@ -57,6 +36,9 @@ class KommandLib internal constructor( init { registerAll() + permissionRuntime?.let { + if (it.config.autoApply) it.apply() + } } private fun registerAll() { @@ -82,6 +64,7 @@ class KommandLib internal constructor( fun unregister() { registered.forEach { it.unregister(commandMap) } registered.clear() + permissionRuntime?.clear() } private fun newPluginCommand(name: String): PluginCommand { @@ -96,10 +79,11 @@ internal data class CommandDefinition( val aliases: List, val description: String?, val usage: String?, - val permission: String?, + var permission: String?, val rootCondition: (CommandSender) -> Boolean, val rootExecutor: (KommandContext.() -> Unit)?, - val nodes: List + val nodes: List, + val permissionOptions: PermissionOptions ) { private val tree = CommandTree(nodes) diff --git a/src/main/kotlin/net/hareworks/kommand-lib/Plugin.kt b/src/main/kotlin/net/hareworks/kommand-lib/Plugin.kt new file mode 100644 index 0000000..d1b0290 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/Plugin.kt @@ -0,0 +1,6 @@ +package net.hareworks.kommand_lib.plugin; + +import org.bukkit.plugin.java.JavaPlugin + +@Suppress("unused") +public class Plugin : JavaPlugin() {} diff --git a/src/main/kotlin/net/hareworks/kommand-lib/dsl/RegistryBuilders.kt b/src/main/kotlin/net/hareworks/kommand-lib/dsl/RegistryBuilders.kt index 79883b9..733e47f 100644 --- a/src/main/kotlin/net/hareworks/kommand-lib/dsl/RegistryBuilders.kt +++ b/src/main/kotlin/net/hareworks/kommand-lib/dsl/RegistryBuilders.kt @@ -2,6 +2,10 @@ package net.hareworks.kommand_lib.dsl import net.hareworks.kommand_lib.CommandDefinition import net.hareworks.kommand_lib.arguments.* +import net.hareworks.kommand_lib.permissions.PermissionConfigBuilder +import net.hareworks.kommand_lib.permissions.PermissionOptions +import net.hareworks.kommand_lib.permissions.PermissionPlanner +import net.hareworks.kommand_lib.permissions.PermissionRuntime import net.hareworks.kommand_lib.nodes.Axis import net.hareworks.kommand_lib.nodes.CoordinateAxisNode import net.hareworks.kommand_lib.nodes.KommandNode @@ -15,6 +19,7 @@ import org.bukkit.plugin.java.JavaPlugin @KommandDsl class KommandRegistry internal constructor(private val plugin: JavaPlugin) { private val definitions = mutableListOf() + private var permissionConfigBuilder: PermissionConfigBuilder? = null /** * Declares a new command root. @@ -31,8 +36,20 @@ class KommandRegistry internal constructor(private val plugin: JavaPlugin) { definitions += builder.build() } - internal fun build(): net.hareworks.kommand_lib.KommandLib = - net.hareworks.kommand_lib.KommandLib(plugin, definitions.toList()) + fun permissions(block: PermissionConfigBuilder.() -> Unit) { + val builder = permissionConfigBuilder ?: PermissionConfigBuilder(plugin).also { permissionConfigBuilder = it } + builder.block() + } + + internal fun build(): net.hareworks.kommand_lib.KommandLib { + val snapshot = definitions.toList() + val config = permissionConfigBuilder?.build() + val runtime = config?.let { + val plan = PermissionPlanner(plugin, it, snapshot).plan() + if (plan.isEmpty()) null else PermissionRuntime(plugin, plan) + } + return net.hareworks.kommand_lib.KommandLib(plugin, snapshot, runtime) + } } @KommandDsl @@ -43,6 +60,13 @@ class CommandBuilder internal constructor( var description: String? = null var usage: String? = null var permission: String? = null + set(value) { + field = value + if (!value.isNullOrBlank()) { + permissionOptions.id = value + } + } + val permissionOptions: PermissionOptions = PermissionOptions() private var condition: (CommandSender) -> Boolean = { true } private var rootExecutor: (net.hareworks.kommand_lib.context.KommandContext.() -> Unit)? = null @@ -61,6 +85,14 @@ class CommandBuilder internal constructor( override val inheritedCondition: (CommandSender) -> Boolean get() = condition + fun permission(block: PermissionOptions.() -> Unit) { + permissionOptions.block() + } + + fun skipPermission() { + permissionOptions.skipPermission() + } + internal fun build(): CommandDefinition = CommandDefinition( name = name, @@ -70,7 +102,8 @@ class CommandBuilder internal constructor( permission = permission, rootCondition = condition, rootExecutor = rootExecutor, - nodes = children.toList() + nodes = children.toList(), + permissionOptions = permissionOptions ) } @@ -144,6 +177,9 @@ abstract class BranchScope internal constructor( node.permission = inheritedPermission node.condition = inheritedCondition } + xNode.permissionOptions.skipPermission() + yNode.permissionOptions.skipPermission() + zNode.permissionOptions.path(name) xNode.children += yNode yNode.children += zNode children += xNode @@ -163,6 +199,7 @@ abstract class NodeScope internal constructor( fun requires(permission: String) { node.permission = permission + node.permissionOptions.id = permission } fun condition(predicate: (CommandSender) -> Boolean) { @@ -172,6 +209,14 @@ abstract class NodeScope internal constructor( fun executes(block: net.hareworks.kommand_lib.context.KommandContext.() -> Unit) { node.executor = block } + + fun permission(block: PermissionOptions.() -> Unit) { + node.permissionOptions.block() + } + + fun skipPermission() { + node.permissionOptions.skipPermission() + } } @KommandDsl diff --git a/src/main/kotlin/net/hareworks/kommand-lib/nodes/Nodes.kt b/src/main/kotlin/net/hareworks/kommand-lib/nodes/Nodes.kt index d7d2d8a..a06e87b 100644 --- a/src/main/kotlin/net/hareworks/kommand-lib/nodes/Nodes.kt +++ b/src/main/kotlin/net/hareworks/kommand-lib/nodes/Nodes.kt @@ -5,6 +5,7 @@ import net.hareworks.kommand_lib.arguments.Coordinates3 import net.hareworks.kommand_lib.arguments.KommandArgumentType import net.hareworks.kommand_lib.context.KommandContext import net.hareworks.kommand_lib.execution.ParseMode +import net.hareworks.kommand_lib.permissions.PermissionOptions import org.bukkit.command.CommandSender abstract class KommandNode internal constructor() { @@ -12,6 +13,7 @@ abstract class KommandNode internal constructor() { var executor: (KommandContext.() -> Unit)? = null var permission: String? = null var condition: (CommandSender) -> Boolean = { true } + val permissionOptions: PermissionOptions = PermissionOptions() fun isVisible(sender: CommandSender): Boolean { val perm = permission @@ -22,6 +24,8 @@ abstract class KommandNode internal constructor() { abstract fun consume(token: String, context: KommandContext, mode: ParseMode): Boolean open fun undo(context: KommandContext) {} abstract fun suggestions(prefix: String, context: KommandContext): List + + open fun segment(): String? = null } class LiteralNode internal constructor(private val literal: String) : KommandNode() { @@ -32,6 +36,8 @@ class LiteralNode internal constructor(private val literal: String) : KommandNod override fun suggestions(prefix: String, context: KommandContext): List { return if (literal.startsWith(prefix, ignoreCase = true)) listOf(literal) else emptyList() } + + override fun segment(): String = literal } open class ValueNode internal constructor( @@ -64,6 +70,8 @@ open class ValueNode internal constructor( if (custom != null) return custom return type.suggestions(context, prefix) } + + override fun segment(): String = name } private fun coordinateAxisKey(base: String, axis: Axis): String = "$base::__${axis.name.lowercase()}" diff --git a/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionConfig.kt b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionConfig.kt new file mode 100644 index 0000000..5283366 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionConfig.kt @@ -0,0 +1,85 @@ +package net.hareworks.kommand_lib.permissions + +import net.hareworks.permits_lib.PermitsLib +import net.hareworks.permits_lib.bukkit.MutationSession +import org.bukkit.permissions.PermissionDefault +import org.bukkit.plugin.java.JavaPlugin + +class PermissionConfig internal constructor( + val namespace: String, + val rootSegment: String, + val autoApply: Boolean, + val removeOnDisable: Boolean, + val includeRootNode: Boolean, + val argumentPrefix: String, + val defaultDescription: (PermissionContext) -> String?, + val defaultValue: PermissionDefault, + val defaultWildcard: Boolean, + val defaultTags: Set, + private val sessionProvider: (JavaPlugin) -> MutationSession +) { + fun session(plugin: JavaPlugin): MutationSession = sessionProvider(plugin) +} + +class PermissionConfigBuilder internal constructor(private val plugin: JavaPlugin) { + var namespace: String = plugin.name.lowercase() + var rootSegment: String = "command" + var autoApply: Boolean = true + var removeOnDisable: Boolean = true + var includeRootNode: Boolean = true + var argumentPrefix: String = "arg" + var defaultValue: PermissionDefault = PermissionDefault.OP + var wildcard: Boolean = true + private val tags: MutableSet = linkedSetOf("kommand") + private var descriptionTemplate: (PermissionContext) -> String? = { ctx -> + when (ctx.kind) { + PermissionNodeKind.COMMAND -> "Allows /${ctx.commandName}" + PermissionNodeKind.LITERAL -> "Allows '${ctx.path.lastOrNull() ?: ctx.commandName}' sub-command" + PermissionNodeKind.ARGUMENT -> "Allows argument '${ctx.path.lastOrNull()}'" + } + } + private var sessionFactory: ((JavaPlugin) -> MutationSession)? = null + + fun defaultDescription(block: (PermissionContext) -> String?) { + descriptionTemplate = block + } + + fun tags(vararg values: String) { + values.filter { it.isNotBlank() }.forEach { tags += it.trim() } + } + + fun session(factory: (JavaPlugin) -> MutationSession) { + sessionFactory = factory + } + + fun session(instance: MutationSession) { + sessionFactory = { instance } + } + + fun build(): PermissionConfig = + PermissionConfig( + namespace = namespace.trim().lowercase(), + rootSegment = rootSegment.trim().lowercase(), + autoApply = autoApply, + removeOnDisable = removeOnDisable, + includeRootNode = includeRootNode, + argumentPrefix = argumentPrefix.trim().lowercase(), + defaultDescription = descriptionTemplate, + defaultValue = defaultValue, + defaultWildcard = wildcard, + defaultTags = tags.toSet(), + sessionProvider = sessionFactory ?: { PermitsLib.session(it) } + ) +} + +data class PermissionContext( + val commandName: String, + val path: List, + val kind: PermissionNodeKind +) + +enum class PermissionNodeKind { + COMMAND, + LITERAL, + ARGUMENT +} diff --git a/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionOptions.kt b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionOptions.kt new file mode 100644 index 0000000..1b9c5b4 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionOptions.kt @@ -0,0 +1,37 @@ +package net.hareworks.kommand_lib.permissions + +import org.bukkit.permissions.PermissionDefault + +class PermissionOptions { + var id: String? = null + var description: String? = null + var defaultValue: PermissionDefault? = null + var wildcard: Boolean? = null + val tags: MutableSet = linkedSetOf() + var skip: Boolean = false + private var customPath: MutableList? = null + + internal var resolvedId: String? = null + private set + + fun path(vararg segments: String) { + customPath = segments + .map { it.trim() } + .filter { it.isNotEmpty() } + .toMutableList() + } + + internal fun pathOverride(): List? = customPath?.toList() + + internal fun resolve(id: String) { + resolvedId = id + } + + fun tags(vararg values: String) { + values.filter { it.isNotBlank() }.forEach { tags += it.trim() } + } + + fun skipPermission() { + skip = true + } +} diff --git a/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlan.kt b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlan.kt new file mode 100644 index 0000000..f28d115 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlan.kt @@ -0,0 +1,21 @@ +package net.hareworks.kommand_lib.permissions + +import org.bukkit.permissions.PermissionDefault + +data class PermissionPlan( + val config: PermissionConfig, + val entries: List +) { + val namespace: String get() = config.namespace + fun isEmpty(): Boolean = entries.isEmpty() +} + +data class PlannedPermission( + val id: String, + val relativePath: List, + val parentPath: List?, + val description: String?, + val defaultValue: PermissionDefault, + val wildcard: Boolean, + val tags: Set +) diff --git a/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlanner.kt b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlanner.kt new file mode 100644 index 0000000..e70fce7 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionPlanner.kt @@ -0,0 +1,139 @@ +package net.hareworks.kommand_lib.permissions + +import net.hareworks.kommand_lib.CommandDefinition +import net.hareworks.kommand_lib.nodes.KommandNode +import net.hareworks.kommand_lib.nodes.LiteralNode +import net.hareworks.kommand_lib.nodes.ValueNode +import org.bukkit.plugin.java.JavaPlugin + +internal class PermissionPlanner( + private val plugin: JavaPlugin, + private val config: PermissionConfig, + private val definitions: List +) { + fun plan(): PermissionPlan { + val entries = linkedMapOf() + val rootPath = if (config.includeRootNode && config.rootSegment.isNotBlank()) { + val path = listOf(config.rootSegment) + val entry = createEntry( + options = PermissionOptions().apply { id = buildId(path) }, + pathSegments = path, + context = PermissionContext(commandName = "", path = path, kind = PermissionNodeKind.LITERAL) + ) + if (entry != null) entries[entry.id] = entry + path + } else { + emptyList() + } + + definitions.forEach { definition -> + val overridePath = definition.permissionOptions.pathOverride() + val commandPath = if (overridePath != null) { + normalizeSegments(overridePath) + } else { + val sanitized = sanitize(definition.name) + val base = if (rootPath.isNotEmpty()) rootPath else emptyList() + base + sanitized + } + val commandEntry = createEntry( + options = definition.permissionOptions, + pathSegments = commandPath, + context = PermissionContext(definition.name, commandPath, PermissionNodeKind.COMMAND) + ) + if (commandEntry != null) { + entries[commandEntry.id] = commandEntry + if (definition.permission.isNullOrBlank()) { + definition.permission = commandEntry.id + } + } + definition.nodes.forEach { node -> + planNode(node, commandPath, entries, definition.name) + } + } + return PermissionPlan(config, entries.values.toList()) + } + + private fun planNode( + node: KommandNode, + basePath: List, + entries: MutableMap, + commandName: String + ) { + if (node.permissionOptions.skip) { + node.children.forEach { child -> + planNode(child, basePath, entries, commandName) + } + return + } + val segment = node.segment()?.let { sanitize(it) } + val pathAddition = node.permissionOptions.pathOverride()?.let { normalizeSegments(it) } + val path = when { + pathAddition != null -> basePath + pathAddition + segment != null -> basePath + segment + else -> basePath + } + val entry = createEntry( + options = node.permissionOptions, + pathSegments = path, + context = PermissionContext(commandName, path, node.toKind()) + ) + val currentBase = if (entry != null) { + entries[entry.id] = entry + if (node.permission.isNullOrBlank()) { + node.permission = entry.id + } + path + } else { + basePath + } + node.children.forEach { child -> + planNode(child, currentBase, entries, commandName) + } + } + + private fun KommandNode.toKind(): PermissionNodeKind = when (this) { + is LiteralNode -> PermissionNodeKind.LITERAL + is ValueNode<*> -> PermissionNodeKind.ARGUMENT + else -> PermissionNodeKind.LITERAL + } + + private fun createEntry( + options: PermissionOptions, + pathSegments: List, + context: PermissionContext + ): PlannedPermission? { + val finalId = (options.id?.takeIf { it.isNotBlank() } ?: buildId(pathSegments)).trim() + if (finalId.isEmpty()) return null + if (!finalId.startsWith(config.namespace)) { + plugin.logger.warning("Permission '$finalId' is outside namespace '${config.namespace}', skipping auto-registration.") + options.resolve(finalId) + return null + } + val relative = finalId.removePrefix(config.namespace).trimStart('.') + val relativePath = if (relative.isEmpty()) emptyList() else relative.split('.') + val description = options.description ?: config.defaultDescription(context) + val defaultValue = options.defaultValue ?: config.defaultValue + val wildcard = options.wildcard ?: config.defaultWildcard + val tags = (config.defaultTags + options.tags).filter { it.isNotBlank() }.toSet() + options.resolve(finalId) + val parentPath = if (relativePath.isNotEmpty()) relativePath.dropLast(1).takeIf { it.isNotEmpty() } else null + return PlannedPermission( + id = finalId, + relativePath = relativePath, + parentPath = parentPath, + description = description, + defaultValue = defaultValue, + wildcard = wildcard, + tags = tags + ) + } + + private fun buildId(pathSegments: List): String = + (listOf(config.namespace) + pathSegments).filter { it.isNotBlank() }.joinToString(".") + + private fun sanitize(segment: String): String = + segment.trim().lowercase().replace(Regex("[^a-z0-9._-]"), "-").trim('-') + + private fun normalizeSegments(segments: List): List = + segments.map { sanitize(it) }.filter { it.isNotBlank() } +} diff --git a/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionRuntime.kt b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionRuntime.kt new file mode 100644 index 0000000..a641040 --- /dev/null +++ b/src/main/kotlin/net/hareworks/kommand-lib/permissions/PermissionRuntime.kt @@ -0,0 +1,41 @@ +package net.hareworks.kommand_lib.permissions + +import net.hareworks.permits_lib.bukkit.MutationSession +import net.hareworks.permits_lib.domain.MutablePermissionTree +import org.bukkit.plugin.java.JavaPlugin + +internal class PermissionRuntime( + private val plugin: JavaPlugin, + private val plan: PermissionPlan +) { + private val session: MutationSession by lazy { plan.config.session(plugin) } + val config: PermissionConfig get() = plan.config + + fun apply() { + if (plan.isEmpty()) return + val mutable = MutablePermissionTree.create(plan.config.namespace) + val sorted = plan.entries.sortedBy { it.relativePath.size } + sorted.forEach { entry -> + mutable.node(entry.relativePath.joinToString(".")) { + entry.description?.let { description = it } + defaultValue = entry.defaultValue + wildcard = entry.wildcard + entry.tags.forEach(::tag) + } + val parent = entry.parentPath + if (parent != null && parent.isNotEmpty()) { + mutable.node(parent.joinToString(".")) { + child(entry.relativePath.last()) + } + } + } + session.applyTree(mutable.build()) + } + + fun clear() { + if (!plan.config.removeOnDisable) return + session.clearAll() + } + + fun attachments() = session.attachments +}