diff --git a/build.gradle.kts b/build.gradle.kts index c4ebb15..dba5cac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { allprojects { group = "pw.dotdash" - version = "0.14.0" + version = "0.15.0" apply(plugin = "maven") } diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/exception/CommandPermissionException.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/exception/CommandPermissionException.kt new file mode 100644 index 0000000..5f77626 --- /dev/null +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/exception/CommandPermissionException.kt @@ -0,0 +1,3 @@ +package pw.dotdash.director.core.exception + +class CommandPermissionException(message: String = "You do not have permission to use this command.") : CommandException(message, true) \ No newline at end of file diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ArgumentCommandTree.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ArgumentCommandTree.kt index 4251846..e71416d 100644 --- a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ArgumentCommandTree.kt +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ArgumentCommandTree.kt @@ -25,6 +25,8 @@ interface ArgumentCommandTree, V, out R> : override fun setExecutor(executor: (S, HCons) -> R): Builder + override fun setAccessibility(test: (S, HCons) -> Boolean): Builder + fun build(): ArgumentCommandTree } diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ChildCommandTree.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ChildCommandTree.kt index c787c8f..8b668a7 100644 --- a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ChildCommandTree.kt +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/ChildCommandTree.kt @@ -26,6 +26,8 @@ interface ChildCommandTree, out R> : Comma override fun setExecutor(executor: (S, P) -> R): Builder + override fun setAccessibility(test: (S, P) -> Boolean): Builder + fun build(): ChildCommandTree } diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/CommandTree.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/CommandTree.kt index 05e3d94..19195f6 100644 --- a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/CommandTree.kt +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/CommandTree.kt @@ -13,6 +13,8 @@ interface CommandTree, out R> : TreeExecut fun getUsage(source: S): String + fun canAccess(source: S, previous: V): Boolean + interface Builder, R> { fun addChild(child: ChildCommandTree): Builder @@ -26,5 +28,7 @@ interface CommandTree, out R> : TreeExecut fun setArgument(parameter: Parameter, init: ArgumentCommandTree.Builder.() -> Unit): Builder fun setExecutor(executor: (S, V) -> R): Builder + + fun setAccessibility(test: (S, V) -> Boolean): Builder } } \ No newline at end of file diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/RootCommandTree.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/RootCommandTree.kt index a881c5f..c8875ae 100644 --- a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/RootCommandTree.kt +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/RootCommandTree.kt @@ -44,6 +44,8 @@ interface RootCommandTree, out R> : CommandTree { override fun setExecutor(executor: (S, V) -> R): Builder + override fun setAccessibility(test: (S, V) -> Boolean): Builder + fun build(): RootCommandTree } diff --git a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/simple/SimpleCommandTree.kt b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/simple/SimpleCommandTree.kt index dfc5b22..d9e3375 100644 --- a/director-core/src/main/kotlin/pw/dotdash/director/core/tree/simple/SimpleCommandTree.kt +++ b/director-core/src/main/kotlin/pw/dotdash/director/core/tree/simple/SimpleCommandTree.kt @@ -11,14 +11,17 @@ import pw.dotdash.director.core.lexer.InputTokenizer import pw.dotdash.director.core.lexer.QuotedInputTokenizer import pw.dotdash.director.core.tree.* import pw.dotdash.director.core.util.StartsWithPredicate -import java.util.function.Consumer internal sealed class SimpleCommandTree, R>( final override val children: Map>, final override val argument: SimpleArgumentCommandTree?, - final override val executor: ((S, V) -> R)? + final override val executor: ((S, V) -> R)?, + private val accessibility: ((S, V) -> Boolean)? ) : CommandTree { + override fun canAccess(source: S, previous: V): Boolean = + this.accessibility == null || this.accessibility.invoke(source, previous) + override fun execute(source: S, tokens: CommandTokens, previous: V): R { return execute(source, tokens, previous, ArrayList()) } @@ -36,11 +39,16 @@ internal sealed class SimpleCommandTree, R>( if (this.argument != null) { try { val parsed: Any? = this.argument.parameter.value.parse(source, tokens, previous) - usageParts += this.argument.parameter.getUsage(source) + val next: HCons = HCons(parsed, previous) + + if (this.argument.canAccess(source, next)) { + usageParts += this.argument.parameter.getUsage(source) + return this.argument.execute(source, tokens, next, usageParts) + } - return this.argument.execute(source, tokens, HCons(parsed, previous), usageParts) + // Otherwise ignore access errors here. } catch (ignored: ArgumentParseException) { - // Ignore parsing errors since we don't have any tokens left. + // Ignore parsing errors since we don't have any tokens left anyways. tokens.snapshot = snapshot } catch (e: CommandException) { throw e.wrap(usageParts) @@ -66,8 +74,11 @@ internal sealed class SimpleCommandTree, R>( val child: SimpleChildCommandTree? = this.children[alias] if (child != null) { - usageParts += alias + if (!child.canAccess(source, previous)) { + throw tokens.createError("You do not have access to this subcommand.").wrap(usageParts) + } + usageParts += alias return child.execute(source, tokens, previous, usageParts) } @@ -76,15 +87,20 @@ internal sealed class SimpleCommandTree, R>( if (this.argument != null) { try { val parsed: Any? = this.argument.parameter.value.parse(source, tokens, previous) - usageParts += this.argument.parameter.getUsage(source) + val next: HCons = HCons(parsed, previous) + + if (!this.argument.canAccess(source, next)) { + throw tokens.createError("You do not have access to this argument.").wrap(usageParts) + } - return this.argument.execute(source, tokens, HCons(parsed, previous), usageParts) + usageParts += this.argument.parameter.getUsage(source) + return this.argument.execute(source, tokens, next, usageParts) } catch (e: CommandException) { throw e.wrap(usageParts) } } - throw tokens.createError("Invalid subcommand.").wrap(usageParts) + throw tokens.createError("Too many arguments!").wrap(usageParts) } protected fun complete(source: S, tokens: CommandTokens, previous: V, usageParts: MutableList): List { @@ -94,11 +110,16 @@ internal sealed class SimpleCommandTree, R>( if (this.argument != null) { try { val parsed: Any? = this.argument.parameter.value.parse(source, tokens, previous) - usageParts += this.argument.parameter.getUsage(source) + val next: HCons = HCons(parsed, previous) + + if (this.argument.canAccess(source, next)) { + usageParts += this.argument.parameter.getUsage(source) + return this.argument.complete(source, tokens, next, usageParts) + } - return this.argument.complete(source, tokens, HCons(parsed, previous), usageParts) + // Otherwise ignore access errors here. } catch (ignored: ArgumentParseException) { - // Ignore parsing errors since we don't have any tokens left. + // Ignore parsing errors since we don't have any tokens left anyways. tokens.snapshot = snapshot } catch (e: CommandException) { throw e.wrap(usageParts) @@ -112,11 +133,12 @@ internal sealed class SimpleCommandTree, R>( val child: SimpleChildCommandTree? = this.children[alias] if (tokens.hasNext()) { - if (child != null) { + if (child != null && child.canAccess(source, previous)) { usageParts += alias - return child.complete(source, tokens, previous, usageParts) } + + // Otherwise ignore access errors since we're completing. } else { return this.subCompletions(source, tokens, previous, usageParts).filter(StartsWithPredicate(alias)) } @@ -127,10 +149,15 @@ internal sealed class SimpleCommandTree, R>( if (this.argument != null) { try { val parsed: Any? = this.argument.parameter.value.parse(source, tokens, previous) - usageParts += this.argument.parameter.getUsage(source) + val next: HCons = HCons(parsed, previous) + + if (this.argument.canAccess(source, next)) { + // Argument successfully parsed; complete its subtree. + usageParts += this.argument.parameter.getUsage(source) + return this.argument.complete(source, tokens, next, usageParts) + } - // Argument successfully parsed; complete its subtree. - return this.argument.complete(source, tokens, HCons(parsed, previous), usageParts) + // Otherwise ignore access errors since we're completing. } catch (e: CommandException) { // Failed to parse argument; rollback. tokens.snapshot = snapshot @@ -141,9 +168,13 @@ internal sealed class SimpleCommandTree, R>( } private fun subCompletions(source: S, tokens: CommandTokens, previous: V, usageParts: MutableList): List { - val result = ArrayList() + val result = HashSet() - result += this.children.keys + for (child: SimpleChildCommandTree in this.children.values) { + if (child.canAccess(source, previous)) { + result += child.aliases + } + } if (this.argument != null) { try { @@ -153,7 +184,7 @@ internal sealed class SimpleCommandTree, R>( } } - return result + return result.toList() } private fun CommandException.wrap(usageParts: List): TreeCommandException = @@ -182,6 +213,7 @@ internal sealed class SimpleCommandTree, R>( protected val children = HashMap>() protected var argument: SimpleArgumentCommandTree? = null protected var executor: ((S, V) -> R)? = null + protected var accessibility: ((S, V) -> Boolean)? = null override fun addChild(child: ChildCommandTree): B { require(child is SimpleChildCommandTree) { "Child trees must be made with ChildCommandTree.builder()" } @@ -213,6 +245,11 @@ internal sealed class SimpleCommandTree, R>( this.executor = executor return this as B } + + override fun setAccessibility(test: (S, V) -> Boolean): B { + this.accessibility = test + return this as B + } } } @@ -224,8 +261,9 @@ internal class SimpleRootCommandTree, R>( override val extendedDescription: String?, children: Map>, argument: SimpleArgumentCommandTree?, - executor: ((S, V) -> R)? -) : SimpleCommandTree(children, argument, executor), RootCommandTree { + executor: ((S, V) -> R)?, + accessibility: ((S, V) -> Boolean)? +) : SimpleCommandTree(children, argument, executor, accessibility), RootCommandTree { class Builder, R> : SimpleCommandTree.Builder, S, V, R>(), @@ -275,7 +313,8 @@ internal class SimpleRootCommandTree, R>( extendedDescription = this.extendedDescription, children = this.children, argument = this.argument, - executor = this.executor + executor = this.executor, + accessibility = this.accessibility ) } } @@ -284,8 +323,9 @@ internal class SimpleChildCommandTree, R>( override val aliases: List, children: Map>, argument: SimpleArgumentCommandTree?, - executor: ((S, P) -> R)? -) : SimpleCommandTree(children, argument, executor), ChildCommandTree { + executor: ((S, P) -> R)?, + accessibility: ((S, P) -> Boolean)? +) : SimpleCommandTree(children, argument, executor, accessibility), ChildCommandTree { class Builder, R> : SimpleCommandTree.Builder, S, P, R>(), @@ -307,7 +347,8 @@ internal class SimpleChildCommandTree, R>( aliases = checkNotNull(this.aliases), children = this.children, argument = this.argument, - executor = this.executor + executor = this.executor, + accessibility = this.accessibility ) } } @@ -316,8 +357,9 @@ internal class SimpleArgumentCommandTree, V, R>( override val parameter: Parameter, children: Map, R>>, argument: SimpleArgumentCommandTree, in Any?, R>?, - executor: ((S, HCons) -> R)? -) : SimpleCommandTree, R>(children, argument, executor), ArgumentCommandTree { + executor: ((S, HCons) -> R)?, + accessibility: ((S, HCons) -> Boolean)? +) : SimpleCommandTree, R>(children, argument, executor, accessibility), ArgumentCommandTree { class Builder, V, R> : SimpleCommandTree.Builder, S, HCons, R>(), @@ -334,7 +376,8 @@ internal class SimpleArgumentCommandTree, V, R>( parameter = checkNotNull(this.parameter), children = this.children, argument = this.argument, - executor = this.executor + executor = this.executor, + accessibility = this.accessibility ) } } \ No newline at end of file diff --git a/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/CommandTreeCallable.kt b/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/CommandTreeCallable.kt index 0dc9175..fb594de 100644 --- a/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/CommandTreeCallable.kt +++ b/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/CommandTreeCallable.kt @@ -1,9 +1,6 @@ package pw.dotdash.director.sponge -import org.spongepowered.api.command.CommandCallable -import org.spongepowered.api.command.CommandException -import org.spongepowered.api.command.CommandResult -import org.spongepowered.api.command.CommandSource +import org.spongepowered.api.command.* import org.spongepowered.api.text.Text import org.spongepowered.api.text.Text.NEW_LINE import org.spongepowered.api.text.format.TextColors.RED @@ -21,10 +18,9 @@ import pw.dotdash.director.core.tree.RootCommandTree import java.util.* -class CommandTreeCallable> @JvmOverloads constructor( - private val root: RootCommandTree, - private val initial: T, - private val permission: String? = null +class CommandTreeCallable>( + private val root: RootCommandTree, + private val initial: V ) : CommandCallable { companion object { @@ -36,14 +32,17 @@ class CommandTreeCallable> @JvmOverloads constructor( @JvmStatic @JvmName("of") - @JvmOverloads - operator fun invoke(root: RootCommandTree, permission: String? = null): CommandTreeCallable = - CommandTreeCallable(root, HNil, permission) + operator fun invoke(root: RootCommandTree): CommandTreeCallable = + CommandTreeCallable(root, HNil) } private val rootAlias: Text = Text.of(YELLOW, this.root.aliases.first()) override fun process(source: CommandSource, arguments: String): CommandResult { + if (!this.testPermission(source)) { + throw CommandPermissionException() + } + try { val tokens = SimpleCommandTokens(arguments, this.root.tokenizer.tokenize(arguments, false).toMutableList()) return this.root.execute(source, tokens, this.initial) @@ -53,6 +52,10 @@ class CommandTreeCallable> @JvmOverloads constructor( } override fun getSuggestions(source: CommandSource, arguments: String, targetPosition: Location?): List { + if (!this.testPermission(source)) { + return emptyList() + } + try { val tokens = SimpleCommandTokens(arguments, this.root.tokenizer.tokenize(arguments, true).toMutableList()) return this.root.complete(source, tokens, this.initial) @@ -88,7 +91,7 @@ class CommandTreeCallable> @JvmOverloads constructor( } override fun testPermission(source: CommandSource): Boolean = - this.permission == null || source.hasPermission(this.permission) + this.root.canAccess(source, this.initial) override fun getShortDescription(source: CommandSource): Optional = Optional.ofNullable(this.root.description?.let(Text::of)) diff --git a/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/SpongeCommandTree.kt b/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/SpongeCommandTree.kt index 44bf4d1..53d88e1 100644 --- a/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/SpongeCommandTree.kt +++ b/director-sponge/src/main/kotlin/pw/dotdash/director/sponge/SpongeCommandTree.kt @@ -7,6 +7,7 @@ import pw.dotdash.director.core.HNil import pw.dotdash.director.core.Parameter import pw.dotdash.director.core.tree.ArgumentCommandTree import pw.dotdash.director.core.tree.ChildCommandTree +import pw.dotdash.director.core.tree.CommandTree import pw.dotdash.director.core.tree.RootCommandTree object SpongeCommandTree { @@ -46,4 +47,9 @@ object SpongeCommandTree { @JvmStatic fun

, V> argument(parameter: Parameter): ArgumentCommandTree.Builder = ArgumentCommandTree.builder(parameter) +} + +fun , S : CommandSource> B.setPermission(permission: String): B { + this.setAccessibility { source, _ -> source.hasPermission(permission) } + return this } \ No newline at end of file