Go to file
2025-12-04 05:13:56 +09:00
gradle init: project initialize 2025-11-28 04:07:36 +09:00
src/main/kotlin/net/hareworks/permits_lib feat: 構造ノードの定義を可能に 2025-12-04 05:13:56 +09:00
.envrc init: project initialize 2025-11-28 04:07:36 +09:00
.gitattributes init: project initialize 2025-11-28 04:07:36 +09:00
.gitignore init: project initialize 2025-11-28 04:07:36 +09:00
AGENT.md init: project initialize 2025-11-28 04:07:36 +09:00
build.gradle.kts chore: ビルド設定の変更 2025-11-29 04:26:32 +09:00
flake.lock init: project initialize 2025-11-28 04:07:36 +09:00
flake.nix init: project initialize 2025-11-28 04:07:36 +09:00
gradle.properties init: project initialize 2025-11-28 04:07:36 +09:00
gradlew init: project initialize 2025-11-28 04:07:36 +09:00
gradlew.bat init: project initialize 2025-11-28 04:07:36 +09:00
README.md feat: 構造ノードの定義を可能に 2025-12-04 05:13:56 +09:00
settings.gradle.kts 1.1 2025-11-28 07:36:26 +09:00

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 PermissionAttachments in sync.

Usage

import net.hareworks.permits_lib.domain.NodeRegistration

class ExamplePlugin : JavaPlugin() {
    private val permits = PermitsLib.session(this)

    override fun onEnable() {
        val tree = permissionTree("example") {
            node("command", NodeRegistration.STRUCTURAL) {
                description = "Access to all example commands"
                defaultValue = PermissionDefault.OP

                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:
                child("helper")

                // Link to a permission outside the current branch (must be fully-qualified):
                childAbsolute("example.tools.repair")

                node("cooldown", NodeRegistration.PERMISSION) {
                    description = "Allows /example cooldown tweaks"
                    wildcard = true // opt in so example.command.* grants cooldown
                }
            }

            node("command.helper", NodeRegistration.PERMISSION) {
                description = "Allows /example helper (referenced via child(\"helper\"))"
            }

            node("tools.repair", NodeRegistration.PERMISSION) {
                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 (because reload/cooldown set wildcard = true)
        // 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", NodeRegistration.STRUCTURAL) {
                description = "Admins for every command path"
                node("debug", NodeRegistration.PERMISSION) {
                    description = "Allows /example debug"
                    defaultValue = PermissionDefault.OP
                }
            }
            // Remove deprecated permissions entirely
            removeNode("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:

val baseTree = permissionTree("example") {
    node("command", NodeRegistration.STRUCTURAL) {
        node("reload", NodeRegistration.PERMISSION)
    }
}

val mutable = MutablePermissionTree.from(baseTree)
mutable.node("command", NodeRegistration.STRUCTURAL) {
    node("debug", NodeRegistration.PERMISSION) {
        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 PermissionNodes. Nodes specify description, default value, boolean children map, optional tags, and the wildcard flag (disabled by default) that, when enabled, makes the library create/update namespace.path.* aggregate permissions 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 have to repeat the full id.
  • Flexible references child("reload"), node("command", NodeRegistration.STRUCTURAL) { node("reload", NodeRegistration.PERMISSION) { ... } }, or even node("command.reload", NodeRegistration.PERMISSION) 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", NodeRegistration.STRUCTURAL) { removeNode("cooldown") } and the entire subtree disappears.
  • Node registration NodeRegistration.PERMISSION materializes the node as a Bukkit permission, while NodeRegistration.STRUCTURAL keeps it purely for grouping (still participates in wildcard aggregation) so you can avoid ambiguous intermediate permissions like hoge.command. 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 opt-in via wildcard = true to have the generated namespace.command.* child kept in sync, so granting example.command.* automatically grants every nested node you marked; leave it false (default) to keep nodes out of the wildcard.
  • 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 PermissionAttachments 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.