permits-lib/README.md

131 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# permits-lib
Permits Lib provides a declarative way to describe Bukkit/Paper permission hierarchies using a Kotlin
DSL. Once a tree is built you can hand it to `PermissionRegistry` (or the higher-level
`MutationSession`) and the library will register/unregister the relevant `org.bukkit.permissions.Permission`
instances and keep `PermissionAttachment`s in sync.
## Usage
```kotlin
class ExamplePlugin : JavaPlugin() {
private val permits = PermitsLib.session(this)
override fun onEnable() {
val tree = permissionTree("example") {
node("command") {
description = "Access to all example commands"
defaultValue = PermissionDefault.OP
node("reload") {
description = "Allows /example reload (permission example.command.reload)"
}
// Link to a helper node defined elsewhere under the command branch:
child("helper")
// Link to a permission outside the current branch (must be fully-qualified):
childAbsolute("example.tools.repair")
node("cooldown") {
description = "Allows /example cooldown tweaks"
wildcard = false // opt-out if you do not want example.command.* to include it
}
}
node("command.helper") {
description = "Allows /example helper (referenced via child(\"helper\"))"
}
node("tools.repair") {
description = "Allows /example tools repair (linked with childAbsolute(\"example.tools.repair\"))"
}
}
permits.applyTree(tree)
// 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 that references every child (visible if you
// export to plugin.yml or inspect Bukkit's /permissions output).
configureRuntimePermissions()
}
fun grantHelper(player: Player) {
permits.attachments.grant(player, PermissionId.of("example.command.reload"))
}
private fun configureRuntimePermissions() {
// Later in runtime you can mutate the previously applied structure without rebuilding it:
permits.edit("example") {
// Update an existing node and link it to new children
node("command") {
description = "Admins for every command path"
node("debug") {
description = "Allows /example debug"
defaultValue = PermissionDefault.OP
}
}
// Remove deprecated permissions entirely
remove("command.cooldown")
}
}
}
```
### Procedural Edits with `MutablePermissionTree`
If you prefer an imperative style before handing the structure to Bukkit, you can clone any existing tree,
mutate it procedurally, and then apply the result:
```kotlin
val baseTree = permissionTree("example") {
node("command") { node("reload") }
}
val mutable = MutablePermissionTree.from(baseTree)
mutable.node("command") {
node("debug") {
description = "Allows /example debug"
defaultValue = PermissionDefault.OP
}
child("helper", value = false) // unlink helper if present
}
mutable.removeNode("command.legacy")
permits.applyTree(mutable.build())
```
The mutable API mirrors the DSL (`node`, `child`, `childAbsolute`, `removeNode`, `renameNode`, etc.) so you can
stage edits procedurally before ever touching `MutationSession`.
### Concepts
- **Permission tree** immutable graph of `PermissionNode`s. Nodes specify description, default value,
boolean children map, optional tags, and the `wildcard` flag (enabled by default) that makes the library
create/update `namespace.path.*` aggregate permissions automatically.
- **DSL** `permissionTree("namespace") { ... }` ensures consistent prefixes and validation (no cycles). Top-level `node("command")` / `remove("command")` treat `command` as relative to that namespace, so you never include the namespace manually at the root.
- **Nested nodes** `node("command") { node("reload") { ... } }` automatically produces
`namespace.command` and `namespace.command.reload` plus wires the parent/child relationship so you don't
have to repeat the full id.
- **Flexible references** `child("reload")`, `node("command") { node("reload") { ... } }`, or
even `node("command.reload")` inside `edit` all resolve to the same node; children are auto-created on
first reference but you can demand explicit nodes by adding a `node` block later, and you can unlink
specific children via `node("command") { removeNode("cooldown") }` and the entire subtree disappears.
Nested `child(...)` calls are relative to the current node by default, while `childAbsolute(...)` now
expects a fully-qualified permission ID (e.g., `example.tools.repair`) so you can also point at nodes in
other namespaces.
- **PermissionRegistry** calculates a diff between snapshots and performs the minimum additions,
removals, or updates via Bukkit's `PluginManager`.
- **Wildcards** with `wildcard = true`, the generated `namespace.command.*` child always exists and stays
in sync so granting `example.command.*` automatically grants every nested node; set it to `false` to opt
out for specific permissions.
- **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
helpers (`grant`, `revoke`, `applyPatch`).
- **MutationSession** ties everything together for plugins that just want to push new trees and manage
attachments without worrying about the lower-level services.