fix: wildcardの挙動を修正

This commit is contained in:
Keisuke Hirata 2025-12-04 06:44:59 +09:00
parent a6e951b48c
commit 00d5860457
4 changed files with 21 additions and 34 deletions

View File

@ -18,10 +18,10 @@ class ExamplePlugin : JavaPlugin() {
node("command", NodeRegistration.STRUCTURAL) {
description = "Access to all example commands"
defaultValue = PermissionDefault.OP
wildcard = true // create example.command.*
node("reload", NodeRegistration.PERMISSION) {
description = "Allows /example reload (permission example.command.reload)"
wildcard = true // include reload in example.command.*
}
// Link to a helper node defined elsewhere under the command branch:
@ -32,7 +32,7 @@ class ExamplePlugin : JavaPlugin() {
node("cooldown", NodeRegistration.PERMISSION) {
description = "Allows /example cooldown tweaks"
wildcard = true // opt in so example.command.* grants cooldown
wildcard = false // keep cooldown out of example.command.*
}
}
@ -50,7 +50,7 @@ class ExamplePlugin : JavaPlugin() {
// The tree above materializes as permissions such as:
// example.command, example.command.reload, example.command.helper, example.command.cooldown,
// example.tools.repair,
// plus the auto-generated example.command.* wildcard (because reload/cooldown set wildcard = true)
// plus the auto-generated example.command.* wildcard (command opted in while cooldown did not).
// export to plugin.yml or inspect Bukkit's /permissions output).
configureRuntimePermissions()
@ -66,9 +66,11 @@ class ExamplePlugin : JavaPlugin() {
// Update an existing node and link it to new children
node("command", NodeRegistration.STRUCTURAL) {
description = "Admins for every command path"
wildcard = true
node("debug", NodeRegistration.PERMISSION) {
description = "Allows /example debug"
defaultValue = PermissionDefault.OP
wildcard = true
}
}
// Remove deprecated permissions entirely
@ -86,15 +88,18 @@ mutate it procedurally, and then apply the result:
```kotlin
val baseTree = permissionTree("example") {
node("command", NodeRegistration.STRUCTURAL) {
wildcard = true
node("reload", NodeRegistration.PERMISSION)
}
}
val mutable = MutablePermissionTree.from(baseTree)
mutable.node("command", NodeRegistration.STRUCTURAL) {
wildcard = true
node("debug", NodeRegistration.PERMISSION) {
description = "Allows /example debug"
defaultValue = PermissionDefault.OP
wildcard = true
}
child("helper", value = false) // unlink helper if present
}
@ -109,8 +114,8 @@ stage edits procedurally before ever touching `MutationSession`.
### Concepts
- **Permission tree** immutable graph of `PermissionNode`s. Nodes specify description, default value,
boolean children map, and the `wildcard` flag (enabled by default) that keeps `namespace.path.*`
aggregate permissions in sync automatically.
boolean children map, and the `wildcard` flag (disabled by default) that, when enabled per node, keeps
`namespace.path.*` aggregate permissions in sync automatically.
- **DSL** `permissionTree("namespace") { ... }` ensures consistent prefixes and validation (no cycles). Every `node("command", NodeRegistration.PERMISSION)` (or `.STRUCTURAL`) is relative to that namespace, so you never include the namespace manually at the root.
- **Nested nodes** `node("command", NodeRegistration.STRUCTURAL) { node("reload", NodeRegistration.PERMISSION) { ... } }` automatically produces
`namespace.command` and `namespace.command.reload` plus wires the parent/child relationship so you don't
@ -125,9 +130,9 @@ stage edits procedurally before ever touching `MutationSession`.
other namespaces.
- **PermissionRegistry** calculates a diff between snapshots and performs the minimum additions,
removals, or updates via Bukkit's `PluginManager`.
- **Wildcards** enabled by default; the generated `namespace.command.*` child stays in sync so granting
`example.command.*` automatically grants every nested node plus wildcard descendants such as
`example.command.debug.*`. Set `wildcard = false` on specific nodes to opt them out.
- **Wildcards** disabled by default; opt in by setting `wildcard = true` on any permission you want pulled
into its parent `namespace.command.*`. Enabled nodes automatically add their wildcard descendants (e.g.,
`example.command.debug.*`) so granting a parent wildcard cascades through the tree.
- **Mutable edits** `permits.edit { ... }` clones the currently registered tree, lets you mutate nodes
imperatively, re-validates, and only pushes the structural diff to Bukkit.
- **AttachmentSynchronizer** manages identity-based `PermissionAttachment`s and exposes high-level

View File

@ -13,7 +13,7 @@ data class PermissionNode(
val description: String? = null,
val defaultValue: PermissionDefault = PermissionDefault.FALSE,
val children: Map<PermissionId, Boolean> = emptyMap(),
val wildcard: Boolean = true,
val wildcard: Boolean = false,
val registration: NodeRegistration = NodeRegistration.PERMISSION
) {
init {

View File

@ -7,7 +7,7 @@ internal data class PermissionNodeDraft(
var description: String? = null,
var defaultValue: PermissionDefault = PermissionDefault.FALSE,
val children: MutableMap<PermissionId, Boolean> = linkedMapOf(),
var wildcard: Boolean = true,
var wildcard: Boolean = false,
var registration: NodeRegistration = NodeRegistration.PERMISSION
) {
fun toNode(): PermissionNode =

View File

@ -9,43 +9,25 @@ internal object WildcardAugmentor {
nodes.values.forEach { node ->
if (!node.wildcard) return@forEach
if (node.id.value.endsWith(".*")) return@forEach
val wildcardId = PermissionId.of("${node.id.value}.*")
val updatedChildren = node.children.toMutableMap()
val wildcardId = parentWildcardId(node.id) ?: return@forEach
val existing = result[wildcardId]
val updatedChildren = (existing?.children ?: emptyMap()).toMutableMap()
val alreadyPresent = updatedChildren[node.id] == true
if (!alreadyPresent) {
updatedChildren[node.id] = true
}
if (existing == null) {
result[wildcardId] = PermissionNode(
id = wildcardId,
description = "Wildcard for ${wildcardId.value}",
description = "Wildcard for ${node.id.value}",
defaultValue = node.defaultValue,
children = updatedChildren,
wildcard = false
)
} else if (!alreadyPresent) {
} else {
result[wildcardId] = existing.copy(children = updatedChildren)
}
}
// Ensure wildcard permissions include descendant wildcard nodes so granting parent.* cascades.
val wildcardEntries = result
.filterKeys { it.value.endsWith(".*") }
.entries
.sortedBy { it.key.value.length } // parents before grandparents not necessary but deterministic
wildcardEntries.forEach { (childWildcardId, _) ->
val parentWildcardId = parentWildcardId(childWildcardId) ?: return@forEach
val parent = result[parentWildcardId] ?: return@forEach
val updatedChildren = parent.children.toMutableMap()
if (updatedChildren[childWildcardId] == true) return@forEach
updatedChildren[childWildcardId] = true
result[parentWildcardId] = parent.copy(children = updatedChildren)
}
return result
}