Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9af293122b | ||
|
|
6c62d3306e |
@@ -2,3 +2,4 @@
|
|||||||
.kotlin
|
.kotlin
|
||||||
.gradle
|
.gradle
|
||||||
build
|
build
|
||||||
|
din
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Paper/Bukkit サーバー向けのコマンド定義を DSL で記述するた
|
|||||||
- 1 つの定義から実行とタブ補完の両方を生成
|
- 1 つの定義から実行とタブ補完の両方を生成
|
||||||
- パーミッションや条件をノード単位で宣言し、子ノードへ自動伝播
|
- パーミッションや条件をノード単位で宣言し、子ノードへ自動伝播
|
||||||
- `suggests {}` で引数ごとの補完候補を柔軟に制御
|
- `suggests {}` で引数ごとの補完候補を柔軟に制御
|
||||||
|
- Brigadier (Paper 1.21 Lifecycle API) 対応により、クライアント側で `<speed> <count>` のような構文ヒントや、数値範囲の検証エラー(赤文字)が表示されます
|
||||||
- `permits-lib` との連携により、コマンドツリーから Bukkit パーミッションを自動生成し、`compileOnly` 依存として参照可能
|
- `permits-lib` との連携により、コマンドツリーから Bukkit パーミッションを自動生成し、`compileOnly` 依存として参照可能
|
||||||
|
|
||||||
## 依存関係
|
## 依存関係
|
||||||
@@ -162,6 +163,14 @@ commands = kommand(this) {
|
|||||||
|
|
||||||
`Coordinates3` は `coordinates("pos") { ... }` 直後のコンテキストで `argument<Coordinates3>("pos")` として取得でき、`resolve(baseLocation)` で基準座標に対して実座標を求められます。
|
`Coordinates3` は `coordinates("pos") { ... }` 直後のコンテキストで `argument<Coordinates3>("pos")` として取得でき、`resolve(baseLocation)` で基準座標に対して実座標を求められます。
|
||||||
|
|
||||||
|
## クライアント側構文ヒント (Brigadier)
|
||||||
|
|
||||||
|
Paper 1.21 以降の環境では、`LifecycleEventManager` を通じてコマンドが登録されるため、クライアントにコマンドの構造が送信されます。これにより以下のメリットがあります:
|
||||||
|
|
||||||
|
- **構文の可視化**: 入力中に `<speed> <amount>` のような引数名が表示されます。
|
||||||
|
- **クライアント側検証**: `integer("val", min=1, max=10)` などの範囲指定がクライアント側でも判定され、範囲外の値を入力すると赤字になります。
|
||||||
|
- **互換性**: 内部的には `Brigadier` のノードに変換されますが、実際のコマンド実行は `kommand-lib` の既存ロジック(`KommandContext`)を使用するため、古いコードの修正は不要です。
|
||||||
|
|
||||||
## ビルドとテスト
|
## ビルドとテスト
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+1
-1
Submodule permits-lib updated: 2275cd9993...660f9a3436
@@ -0,0 +1,135 @@
|
|||||||
|
package net.hareworks.kommand_lib
|
||||||
|
|
||||||
|
import com.mojang.brigadier.arguments.ArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.DoubleArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
||||||
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
|
import io.papermc.paper.command.brigadier.CommandSourceStack
|
||||||
|
import io.papermc.paper.command.brigadier.Commands
|
||||||
|
import io.papermc.paper.command.brigadier.argument.ArgumentTypes
|
||||||
|
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver
|
||||||
|
import net.hareworks.kommand_lib.arguments.*
|
||||||
|
import net.hareworks.kommand_lib.context.KommandContext
|
||||||
|
import net.hareworks.kommand_lib.execution.ParseMode
|
||||||
|
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
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
internal object BrigadierMapper {
|
||||||
|
|
||||||
|
fun map(
|
||||||
|
plugin: JavaPlugin,
|
||||||
|
definition: CommandDefinition
|
||||||
|
): LiteralArgumentBuilder<CommandSourceStack> {
|
||||||
|
val root = Commands.literal(definition.name)
|
||||||
|
.requires { source -> definition.rootCondition(source.sender) }
|
||||||
|
|
||||||
|
// Mapped execution for root if args empty
|
||||||
|
root.executes { ctx ->
|
||||||
|
definition.execute(plugin, ctx.source.sender, definition.name, emptyArray())
|
||||||
|
1 // Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map children
|
||||||
|
definition.nodes.forEach { child ->
|
||||||
|
mapNode(plugin, definition, child)?.let { root.then(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapNode(
|
||||||
|
plugin: JavaPlugin,
|
||||||
|
definition: CommandDefinition,
|
||||||
|
node: KommandNode
|
||||||
|
): CommandNode<CommandSourceStack>? {
|
||||||
|
val builder = when (node) {
|
||||||
|
is LiteralNode -> {
|
||||||
|
Commands.literal(node.segment())
|
||||||
|
}
|
||||||
|
is ValueNode<*> -> {
|
||||||
|
val argType = mapArgumentType(node)
|
||||||
|
Commands.argument(node.segment(), argType)
|
||||||
|
.suggests { ctx, builder ->
|
||||||
|
// Delegate suggestions back to Kommand
|
||||||
|
// We need to reconstruct the context vaguely or use existing helpers
|
||||||
|
// Ideally we grab the full input and pass it to a specialized suggestion handler
|
||||||
|
// For now we can try to use the node's simple suggestion logic
|
||||||
|
|
||||||
|
// Brigadier pass partial string as builder.remaining
|
||||||
|
// But Kommand expects a KommandContext.
|
||||||
|
// Constructing a dummy context might be hard without full chain.
|
||||||
|
// Simplification: Use standard suggestions from type if available.
|
||||||
|
|
||||||
|
val input = ctx.input
|
||||||
|
// TODO: more complex suggestion delegation
|
||||||
|
// For now let Brigadier handle types that it knows (Integer, etc)
|
||||||
|
// For custom types, we might need a custom SuggestionProvider
|
||||||
|
|
||||||
|
// Simple fallback for custom suggestions from node
|
||||||
|
val suggestions = node.suggestions(builder.remaining,
|
||||||
|
KommandContext(plugin, ctx.source.sender, "", emptyArray(), ParseMode.SUGGEST)
|
||||||
|
// Note: empty args is wrong here, but we can't easily reconstruction full stack
|
||||||
|
// without reimplementing the parser.
|
||||||
|
// However, Kommand's `suggestions` method usually only looks at the prefix for simple nodes.
|
||||||
|
)
|
||||||
|
suggestions.forEach { builder.suggest(it) }
|
||||||
|
builder.buildFuture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.requires { source -> node.isVisible(source.sender) }
|
||||||
|
|
||||||
|
// Execute wrapper
|
||||||
|
// Since we want to preserve Kommand's execution logic which relies on parsing the WHOLE string,
|
||||||
|
// we can just make every node executable and pass the raw input to the existing execute method.
|
||||||
|
builder.executes { ctx ->
|
||||||
|
// Reconstruct args from input string
|
||||||
|
// ctx.input is the full command line e.g. "/cmd arg1 arg2"
|
||||||
|
val input = ctx.input
|
||||||
|
val parts = input.trim().split("\\s+".toRegex())
|
||||||
|
// Implementation detail: parts[0] is command name normally.
|
||||||
|
val args = if (parts.size > 1) parts.drop(1).toTypedArray() else emptyArray()
|
||||||
|
|
||||||
|
// We use the alias from the input if possible, or fallback to main name
|
||||||
|
val alias = parts.firstOrNull()?.removePrefix("/") ?: definition.name
|
||||||
|
|
||||||
|
definition.execute(plugin, ctx.source.sender, alias, args)
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively map children
|
||||||
|
node.children.forEach { child ->
|
||||||
|
mapNode(plugin, definition, child)?.let { builder.then(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapArgumentType(node: ValueNode<*>): ArgumentType<*> {
|
||||||
|
return when (val type = node.argumentType) {
|
||||||
|
is net.hareworks.kommand_lib.arguments.IntegerArgumentType -> {
|
||||||
|
val min = type.min ?: Int.MIN_VALUE
|
||||||
|
val max = type.max ?: Int.MAX_VALUE
|
||||||
|
com.mojang.brigadier.arguments.IntegerArgumentType.integer(min, max)
|
||||||
|
}
|
||||||
|
is net.hareworks.kommand_lib.arguments.FloatArgumentType -> {
|
||||||
|
val min = type.min ?: -Double.MAX_VALUE
|
||||||
|
val max = type.max ?: Double.MAX_VALUE
|
||||||
|
DoubleArgumentType.doubleArg(min, max)
|
||||||
|
}
|
||||||
|
is WordArgumentType -> StringArgumentType.word()
|
||||||
|
is CoordinateComponentArgumentType -> StringArgumentType.string()
|
||||||
|
is PlayerArgumentType -> StringArgumentType.word()
|
||||||
|
is PlayerSelectorArgumentType -> StringArgumentType.greedyString()
|
||||||
|
else -> StringArgumentType.string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,17 @@ class KommandLib internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun registerAll() {
|
private fun registerAll() {
|
||||||
|
// Register via Paper Lifecycle API for 1.21+
|
||||||
|
val manager = plugin.lifecycleManager
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
manager.registerEventHandler(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS) { event ->
|
||||||
|
val registrar = event.registrar()
|
||||||
|
for (definition in definitions) {
|
||||||
|
val node = BrigadierMapper.map(plugin, definition)
|
||||||
|
registrar.register(node.build(), definition.description, definition.aliases)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (definition in definitions) {
|
for (definition in definitions) {
|
||||||
commandMap.getCommand(definition.name)?.unregister(commandMap)
|
commandMap.getCommand(definition.name)?.unregister(commandMap)
|
||||||
val command = newPluginCommand(definition.name)
|
val command = newPluginCommand(definition.name)
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ object WordArgumentType : KommandArgumentType<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IntegerArgumentType(
|
class IntegerArgumentType(
|
||||||
private val min: Int? = null,
|
val min: Int? = null,
|
||||||
private val max: Int? = null
|
val max: Int? = null
|
||||||
) : KommandArgumentType<Int> {
|
) : KommandArgumentType<Int> {
|
||||||
override fun parse(input: String, context: KommandContext): ArgumentParseResult<Int> {
|
override fun parse(input: String, context: KommandContext): ArgumentParseResult<Int> {
|
||||||
val value = input.toIntOrNull()
|
val value = input.toIntOrNull()
|
||||||
@@ -41,8 +41,8 @@ class IntegerArgumentType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FloatArgumentType(
|
class FloatArgumentType(
|
||||||
private val min: Double? = null,
|
val min: Double? = null,
|
||||||
private val max: Double? = null
|
val max: Double? = null
|
||||||
) : KommandArgumentType<Double> {
|
) : KommandArgumentType<Double> {
|
||||||
override fun parse(input: String, context: KommandContext): ArgumentParseResult<Double> {
|
override fun parse(input: String, context: KommandContext): ArgumentParseResult<Double> {
|
||||||
val value = input.toDoubleOrNull()
|
val value = input.toDoubleOrNull()
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ class LiteralNode internal constructor(private val literal: String) : KommandNod
|
|||||||
|
|
||||||
open class ValueNode<T> internal constructor(
|
open class ValueNode<T> internal constructor(
|
||||||
private val name: String,
|
private val name: String,
|
||||||
private val type: KommandArgumentType<T>
|
val argumentType: KommandArgumentType<T>
|
||||||
) : KommandNode() {
|
) : KommandNode() {
|
||||||
var suggestionProvider: ((KommandContext, String) -> List<String>)? = null
|
var suggestionProvider: ((KommandContext, String) -> List<String>)? = null
|
||||||
|
|
||||||
override fun consume(token: String, context: KommandContext, mode: ParseMode): Boolean {
|
override fun consume(token: String, context: KommandContext, mode: ParseMode): Boolean {
|
||||||
return when (val result = type.parse(token, context)) {
|
return when (val result = argumentType.parse(token, context)) {
|
||||||
is net.hareworks.kommand_lib.arguments.ArgumentParseResult.Success -> {
|
is net.hareworks.kommand_lib.arguments.ArgumentParseResult.Success -> {
|
||||||
context.remember(name, result.value)
|
context.remember(name, result.value)
|
||||||
true
|
true
|
||||||
@@ -68,7 +68,7 @@ open class ValueNode<T> internal constructor(
|
|||||||
override fun suggestions(prefix: String, context: KommandContext): List<String> {
|
override fun suggestions(prefix: String, context: KommandContext): List<String> {
|
||||||
val custom = suggestionProvider?.invoke(context, prefix)
|
val custom = suggestionProvider?.invoke(context, prefix)
|
||||||
if (custom != null) return custom
|
if (custom != null) return custom
|
||||||
return type.suggestions(context, prefix)
|
return argumentType.suggestions(context, prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun segment(): String = name
|
override fun segment(): String = name
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class PermissionOptions {
|
|||||||
var wildcard: Boolean? = null
|
var wildcard: Boolean? = null
|
||||||
var skip: Boolean = false
|
var skip: Boolean = false
|
||||||
private var customPath: MutableList<String>? = null
|
private var customPath: MutableList<String>? = null
|
||||||
|
private val wildcardExclusionSpecs: MutableList<List<String>> = mutableListOf()
|
||||||
internal var preferSkipByDefault: Boolean = false
|
internal var preferSkipByDefault: Boolean = false
|
||||||
|
|
||||||
internal var resolvedId: String? = null
|
internal var resolvedId: String? = null
|
||||||
@@ -30,4 +31,26 @@ class PermissionOptions {
|
|||||||
fun skipPermission() {
|
fun skipPermission() {
|
||||||
skip = true
|
skip = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val wildcardExclusions: List<List<String>>
|
||||||
|
get() = wildcardExclusionSpecs.map { it.toList() }
|
||||||
|
|
||||||
|
fun wildcard(block: WildcardOptions.() -> Unit) {
|
||||||
|
wildcard = true
|
||||||
|
WildcardOptions(wildcardExclusionSpecs).apply(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WildcardOptions internal constructor(
|
||||||
|
private val sink: MutableList<List<String>>
|
||||||
|
) {
|
||||||
|
fun exclude(vararg segments: String) {
|
||||||
|
val normalized = segments
|
||||||
|
.flatMap { it.split('.') }
|
||||||
|
.map { it.trim().lowercase() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
if (normalized.isNotEmpty()) {
|
||||||
|
sink += normalized
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ data class PlannedPermission(
|
|||||||
val parentPath: List<String>?,
|
val parentPath: List<String>?,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val defaultValue: PermissionDefault,
|
val defaultValue: PermissionDefault,
|
||||||
|
val wildcardExclusions: List<List<String>>,
|
||||||
|
val inheritsParentDefault: Boolean,
|
||||||
val wildcard: Boolean,
|
val wildcard: Boolean,
|
||||||
val registration: NodeRegistration
|
val registration: NodeRegistration
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import net.hareworks.kommand_lib.nodes.KommandNode
|
|||||||
import net.hareworks.kommand_lib.nodes.LiteralNode
|
import net.hareworks.kommand_lib.nodes.LiteralNode
|
||||||
import net.hareworks.kommand_lib.nodes.ValueNode
|
import net.hareworks.kommand_lib.nodes.ValueNode
|
||||||
import net.hareworks.permits_lib.domain.NodeRegistration
|
import net.hareworks.permits_lib.domain.NodeRegistration
|
||||||
|
import org.bukkit.permissions.PermissionDefault
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
|
||||||
internal class PermissionPlanner(
|
internal class PermissionPlanner(
|
||||||
@@ -14,18 +15,19 @@ internal class PermissionPlanner(
|
|||||||
) {
|
) {
|
||||||
fun plan(): PermissionPlan {
|
fun plan(): PermissionPlan {
|
||||||
val entries = linkedMapOf<String, PlannedPermission>()
|
val entries = linkedMapOf<String, PlannedPermission>()
|
||||||
val rootPath = if (config.includeRootNode && config.rootSegment.isNotBlank()) {
|
val (rootPath, rootDefault) = if (config.includeRootNode && config.rootSegment.isNotBlank()) {
|
||||||
val path = listOf(config.rootSegment)
|
val path = listOf(config.rootSegment)
|
||||||
val entry = createEntry(
|
val entry = createEntry(
|
||||||
options = PermissionOptions().apply { id = buildId(path) },
|
options = PermissionOptions().apply { id = buildId(path) },
|
||||||
pathSegments = path,
|
pathSegments = path,
|
||||||
context = PermissionContext(commandName = "", path = path, kind = PermissionNodeKind.LITERAL),
|
context = PermissionContext(commandName = "", path = path, kind = PermissionNodeKind.LITERAL),
|
||||||
|
parentDefault = config.defaultValue,
|
||||||
registration = NodeRegistration.STRUCTURAL
|
registration = NodeRegistration.STRUCTURAL
|
||||||
)
|
)
|
||||||
if (entry != null) entries[entry.id] = entry
|
if (entry != null) entries[entry.id] = entry
|
||||||
path
|
path to (entry?.defaultValue ?: config.defaultValue)
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList<String>() to config.defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
definitions.forEach { definition ->
|
definitions.forEach { definition ->
|
||||||
@@ -40,7 +42,8 @@ internal class PermissionPlanner(
|
|||||||
val commandEntry = createEntry(
|
val commandEntry = createEntry(
|
||||||
options = definition.permissionOptions,
|
options = definition.permissionOptions,
|
||||||
pathSegments = commandPath,
|
pathSegments = commandPath,
|
||||||
context = PermissionContext(definition.name, commandPath, PermissionNodeKind.COMMAND)
|
context = PermissionContext(definition.name, commandPath, PermissionNodeKind.COMMAND),
|
||||||
|
parentDefault = rootDefault
|
||||||
)
|
)
|
||||||
if (commandEntry != null) {
|
if (commandEntry != null) {
|
||||||
entries[commandEntry.id] = commandEntry
|
entries[commandEntry.id] = commandEntry
|
||||||
@@ -48,8 +51,9 @@ internal class PermissionPlanner(
|
|||||||
definition.permission = commandEntry.id
|
definition.permission = commandEntry.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val childDefault = commandEntry?.defaultValue ?: rootDefault
|
||||||
definition.nodes.forEach { node ->
|
definition.nodes.forEach { node ->
|
||||||
planNode(node, commandPath, entries, definition.name)
|
planNode(node, commandPath, entries, definition.name, childDefault)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PermissionPlan(config, entries.values.toList())
|
return PermissionPlan(config, entries.values.toList())
|
||||||
@@ -59,7 +63,8 @@ internal class PermissionPlanner(
|
|||||||
node: KommandNode,
|
node: KommandNode,
|
||||||
basePath: List<String>,
|
basePath: List<String>,
|
||||||
entries: MutableMap<String, PlannedPermission>,
|
entries: MutableMap<String, PlannedPermission>,
|
||||||
commandName: String
|
commandName: String,
|
||||||
|
parentDefault: PermissionDefault
|
||||||
) {
|
) {
|
||||||
val rawOverride = node.permissionOptions.renameOverride()
|
val rawOverride = node.permissionOptions.renameOverride()
|
||||||
val shouldSkip =
|
val shouldSkip =
|
||||||
@@ -69,7 +74,7 @@ internal class PermissionPlanner(
|
|||||||
rawOverride == null)
|
rawOverride == null)
|
||||||
if (shouldSkip) {
|
if (shouldSkip) {
|
||||||
node.children.forEach { child ->
|
node.children.forEach { child ->
|
||||||
planNode(child, basePath, entries, commandName)
|
planNode(child, basePath, entries, commandName, parentDefault)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -83,7 +88,8 @@ internal class PermissionPlanner(
|
|||||||
val entry = createEntry(
|
val entry = createEntry(
|
||||||
options = node.permissionOptions,
|
options = node.permissionOptions,
|
||||||
pathSegments = path,
|
pathSegments = path,
|
||||||
context = PermissionContext(commandName, path, node.toKind())
|
context = PermissionContext(commandName, path, node.toKind()),
|
||||||
|
parentDefault = parentDefault
|
||||||
)
|
)
|
||||||
val currentBase = if (entry != null) {
|
val currentBase = if (entry != null) {
|
||||||
entries[entry.id] = entry
|
entries[entry.id] = entry
|
||||||
@@ -94,8 +100,9 @@ internal class PermissionPlanner(
|
|||||||
} else {
|
} else {
|
||||||
basePath
|
basePath
|
||||||
}
|
}
|
||||||
|
val nextDefault = entry?.defaultValue ?: parentDefault
|
||||||
node.children.forEach { child ->
|
node.children.forEach { child ->
|
||||||
planNode(child, currentBase, entries, commandName)
|
planNode(child, currentBase, entries, commandName, nextDefault)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +116,7 @@ internal class PermissionPlanner(
|
|||||||
options: PermissionOptions,
|
options: PermissionOptions,
|
||||||
pathSegments: List<String>,
|
pathSegments: List<String>,
|
||||||
context: PermissionContext,
|
context: PermissionContext,
|
||||||
|
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()
|
||||||
@@ -121,8 +129,12 @@ internal class PermissionPlanner(
|
|||||||
val relative = finalId.removePrefix(config.namespace).trimStart('.')
|
val relative = finalId.removePrefix(config.namespace).trimStart('.')
|
||||||
val relativePath = if (relative.isEmpty()) emptyList() else relative.split('.')
|
val relativePath = if (relative.isEmpty()) emptyList() else relative.split('.')
|
||||||
val description = options.description ?: config.defaultDescription(context)
|
val description = options.description ?: config.defaultDescription(context)
|
||||||
val defaultValue = options.defaultValue ?: config.defaultValue
|
val explicitDefault = options.defaultValue
|
||||||
|
val defaultValue = explicitDefault ?: parentDefault
|
||||||
val wildcard = options.wildcard ?: config.defaultWildcard
|
val wildcard = options.wildcard ?: config.defaultWildcard
|
||||||
|
val wildcardExclusions = options.wildcardExclusions
|
||||||
|
.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(
|
||||||
@@ -131,6 +143,8 @@ internal class PermissionPlanner(
|
|||||||
parentPath = parentPath,
|
parentPath = parentPath,
|
||||||
description = description,
|
description = description,
|
||||||
defaultValue = defaultValue,
|
defaultValue = defaultValue,
|
||||||
|
wildcardExclusions = wildcardExclusions,
|
||||||
|
inheritsParentDefault = explicitDefault == null,
|
||||||
wildcard = wildcard,
|
wildcard = wildcard,
|
||||||
registration = registration
|
registration = registration
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,23 +21,38 @@ internal class PermissionRuntime(
|
|||||||
entry.relativePath.takeIf { it.isNotEmpty() }?.joinToString(".")?.let { it to entry.registration }
|
entry.relativePath.takeIf { it.isNotEmpty() }?.joinToString(".")?.let { it to entry.registration }
|
||||||
}
|
}
|
||||||
.toMap()
|
.toMap()
|
||||||
|
val entriesByPath = 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(".")
|
||||||
mutable.node(nodeId, entry.registration) {
|
val currentNode = mutable.node(nodeId, entry.registration) {
|
||||||
entry.description?.let { description = it }
|
entry.description?.let { description = it }
|
||||||
defaultValue = entry.defaultValue
|
defaultValue = entry.defaultValue
|
||||||
wildcard = entry.wildcard
|
wildcard = entry.wildcard
|
||||||
}
|
}
|
||||||
|
if (entry.wildcard && entry.wildcardExclusions.isNotEmpty()) {
|
||||||
|
entry.wildcardExclusions.forEach { exclusion ->
|
||||||
|
val absolutePath = entry.relativePath + exclusion
|
||||||
|
if (absolutePath.isNotEmpty()) {
|
||||||
|
currentNode.excludeWildcardChildAbsolute(buildId(absolutePath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val parent = entry.parentPath
|
val parent = entry.parentPath
|
||||||
if (parent != null && parent.isNotEmpty()) {
|
if (parent != null && parent.isNotEmpty()) {
|
||||||
val parentId = parent.joinToString(".")
|
val parentId = parent.joinToString(".")
|
||||||
val parentRegistration = registrations[parentId] ?: NodeRegistration.STRUCTURAL
|
val parentRegistration = registrations[parentId] ?: NodeRegistration.STRUCTURAL
|
||||||
|
val parentEntry = entriesByPath[parentId]
|
||||||
|
val shouldLinkChildren = parentEntry?.registration == NodeRegistration.STRUCTURAL || parentEntry?.wildcard == true
|
||||||
mutable.node(parentId, parentRegistration) {
|
mutable.node(parentId, parentRegistration) {
|
||||||
child(entry.relativePath.last())
|
if (shouldLinkChildren) {
|
||||||
|
child(entry.relativePath.last())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,4 +65,7 @@ internal class PermissionRuntime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun attachments() = session.attachments
|
fun attachments() = session.attachments
|
||||||
|
|
||||||
|
private fun buildId(pathSegments: List<String>): String =
|
||||||
|
(listOf(plan.config.namespace) + pathSegments).filter { it.isNotBlank() }.joinToString(".")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user