diff --git a/README.md b/README.md index fbe6b71..b8b0ece 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNode.kt b/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNode.kt index ac27aa5..ad153a8 100644 --- a/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNode.kt +++ b/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNode.kt @@ -13,7 +13,7 @@ data class PermissionNode( val description: String? = null, val defaultValue: PermissionDefault = PermissionDefault.FALSE, val children: Map = emptyMap(), - val wildcard: Boolean = true, + val wildcard: Boolean = false, val registration: NodeRegistration = NodeRegistration.PERMISSION ) { init { diff --git a/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNodeDraft.kt b/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNodeDraft.kt index c3e6c9d..72ecce0 100644 --- a/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNodeDraft.kt +++ b/src/main/kotlin/net/hareworks/permits_lib/domain/PermissionNodeDraft.kt @@ -7,7 +7,7 @@ internal data class PermissionNodeDraft( var description: String? = null, var defaultValue: PermissionDefault = PermissionDefault.FALSE, val children: MutableMap = linkedMapOf(), - var wildcard: Boolean = true, + var wildcard: Boolean = false, var registration: NodeRegistration = NodeRegistration.PERMISSION ) { fun toNode(): PermissionNode = diff --git a/src/main/kotlin/net/hareworks/permits_lib/domain/WildcardAugmentor.kt b/src/main/kotlin/net/hareworks/permits_lib/domain/WildcardAugmentor.kt index 6812fc7..1cf084d 100644 --- a/src/main/kotlin/net/hareworks/permits_lib/domain/WildcardAugmentor.kt +++ b/src/main/kotlin/net/hareworks/permits_lib/domain/WildcardAugmentor.kt @@ -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 }