Skip to content

Commit d4c1390

Browse files
committed
feat: finished InternalNameArgumentType, and added warning when trying to add literals or arguments after a greedy argument
1 parent af425a4 commit d4c1390

File tree

5 files changed

+190
-75
lines changed

5 files changed

+190
-75
lines changed

src/main/kotlin/com/github/itsempa/nautilus/commands/brigadier/BrigadierBuilder.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.github.itsempa.nautilus.commands.brigadier
22

33
import at.hannibal2.skyhanni.config.commands.CommandCategory
44
import com.github.itsempa.nautilus.commands.CommandData
5+
import com.github.itsempa.nautilus.commands.brigadier.BrigadierUtils.isGreedy
56
import com.github.itsempa.nautilus.commands.brigadier.BrigadierUtils.toSuggestionProvider
67
import com.github.itsempa.nautilus.utils.NautilusUtils.hasWhitespace
78
import com.github.itsempa.nautilus.utils.NautilusUtils.splitLastWhitespace
@@ -34,8 +35,12 @@ class BaseBrigadierBuilder(override val name: String) : CommandData, BrigadierBu
3435

3536
open class BrigadierBuilder<B : ArgumentBuilder<Any?, B>>(
3637
val builder: ArgumentBuilder<Any?, B>,
38+
private val hasGreedyArg: Boolean = false,
3739
) {
3840

41+
private fun checkGreedy() =
42+
require(!hasGreedyArg) { "Cannot add a callback to a builder that has a greedy argument." }
43+
3944
fun callback(block: ArgContext.() -> Unit) {
4045
this.builder.executes {
4146
block(ArgContext(it))
@@ -58,6 +63,7 @@ open class BrigadierBuilder<B : ArgumentBuilder<Any?, B>>(
5863
}
5964

6065
fun literal(vararg names: String, action: LiteralCommandBuilder.() -> Unit) {
66+
checkGreedy()
6167
for (name in names) {
6268
if (name.hasWhitespace()) {
6369
val (prevLiteral, nextLiteral) = name.splitLastWhitespace()
@@ -68,7 +74,7 @@ open class BrigadierBuilder<B : ArgumentBuilder<Any?, B>>(
6874
}
6975
continue
7076
}
71-
val builder = BrigadierBuilder(LiteralArgumentBuilder.literal(name))
77+
val builder = BrigadierBuilder(LiteralArgumentBuilder.literal(name), hasGreedyArg)
7278
builder.action()
7379
this.builder.then(builder.builder)
7480
}
@@ -103,17 +109,19 @@ open class BrigadierBuilder<B : ArgumentBuilder<Any?, B>>(
103109
suggestions: SuggestionProvider<Any?>? = null,
104110
action: ArgumentCommandBuilder<T>.() -> Unit,
105111
) {
112+
require(!hasGreedyArg) { "Cannot add an argument to a builder that has a greedy argument." }
106113
if (name.hasWhitespace()) {
107114
val (prevLiteral, nextLiteral) = name.splitLastWhitespace()
108115
literal(prevLiteral) {
109116
internalArg(nextLiteral, argument, suggestions, action)
110117
}
111118
return
112119
}
120+
val isGreedy = argument.isGreedy()
113121
val builder = BrigadierBuilder(
114122
RequiredArgumentBuilder.argument<Any?, T>(name, argument).apply {
115123
if (suggestions != null) suggests(suggestions)
116-
},
124+
}, isGreedy,
117125
)
118126
builder.action()
119127
this.builder.then(builder.builder)

src/main/kotlin/com/github/itsempa/nautilus/commands/brigadier/BrigadierUtils.kt

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import at.hannibal2.skyhanni.utils.NeuInternalName
44
import at.hannibal2.skyhanni.utils.NeuItems
55
import com.github.itsempa.nautilus.utils.NautilusUtils.hasWhitespace
66
import com.mojang.brigadier.StringReader
7+
import com.mojang.brigadier.arguments.ArgumentType
8+
import com.mojang.brigadier.arguments.StringArgumentType
79
import com.mojang.brigadier.exceptions.CommandSyntaxException
810
import com.mojang.brigadier.suggestion.SuggestionProvider
911
import com.mojang.brigadier.suggestion.Suggestions
@@ -13,20 +15,44 @@ import java.util.concurrent.CompletableFuture
1315
object BrigadierUtils {
1416

1517
const val DOUBLE_QUOTE = '"'
18+
private const val SINGLE_QUOTE = '\''
19+
20+
// Add greedy arguments here
21+
fun <T> ArgumentType<T>.isGreedy(): Boolean {
22+
return when (this) {
23+
is StringArgumentType -> this.type == StringArgumentType.StringType.GREEDY_PHRASE
24+
else -> false
25+
}
26+
}
1627

1728
fun Collection<String>.toSuggestionProvider(shouldEscape: Boolean = true) = SuggestionProvider<Any?> { _, builder ->
1829
if (shouldEscape) builder.addOptionalEscaped(this)
1930
else builder.addUnescaped(this)
2031
builder.buildFuture()
2132
}
2233

34+
private fun isCharAllowed(c: Char): Boolean = StringReader.isAllowedInUnquotedString(c) || c == SINGLE_QUOTE
35+
2336
/** The same as [StringReader.readString], except it doesn't accept escaping with `'`. */
2437
fun StringReader.readOptionalDoubleQuotedString(): String {
2538
if (!canRead()) return ""
2639
return if (peek() == DOUBLE_QUOTE) {
2740
skip()
2841
readStringUntil(DOUBLE_QUOTE)
29-
} else readUnquotedString()
42+
} else {
43+
val start = cursor
44+
while (canRead() && isCharAllowed(peek())) {
45+
skip()
46+
}
47+
return string.substring(start, cursor)
48+
}
49+
}
50+
51+
/** Returns the remaining text in the [StringReader] and sets the cursor at the end */
52+
fun StringReader.readGreedyString(): String {
53+
val text = remaining
54+
cursor = totalLength
55+
return text
3056
}
3157

3258
/** The same as [StringReader.readQuotedString], except it doesn't accept escaping with `'`. */
@@ -125,15 +151,32 @@ object BrigadierUtils {
125151
input: String,
126152
builder: SuggestionsBuilder,
127153
limit: Int = 200,
128-
isValidItem: (NeuInternalName) -> Boolean = { true },
154+
showWhenEmpty: Boolean = false,
155+
isValidItem: (NeuInternalName) -> Boolean,
129156
): CompletableFuture<Suggestions> {
130-
val first = input.firstOrNull() ?: return builder.buildFuture()
131-
val unEscaped = if (first == DOUBLE_QUOTE) input.drop(1) else input
132-
if (unEscaped.isBlank()) return builder.buildFuture()
157+
if (input.isEmpty() && !showWhenEmpty) return builder.buildFuture()
158+
val unEscaped = if (input.firstOrNull() == DOUBLE_QUOTE) input.drop(1) else input
159+
if (unEscaped.isBlank() && !showWhenEmpty) return builder.buildFuture()
133160

134161
val lowercaseStart = unEscaped.replace("_", " ")
135162
val items = NeuItems.findItemNameStartingWithWithoutNPCs(lowercaseStart, isValidItem).take(limit)
136163

137164
return builder.addOptionalEscaped(items).buildFuture()
138165
}
166+
167+
fun parseInternalNameTabComplete(
168+
input: String,
169+
builder: SuggestionsBuilder,
170+
limit: Int = 200,
171+
showWhenEmpty: Boolean = false,
172+
isValidItem: (NeuInternalName) -> Boolean,
173+
): CompletableFuture<Suggestions> {
174+
if (input.isEmpty() && !showWhenEmpty) return builder.buildFuture()
175+
val unEscaped = if (input.firstOrNull() == DOUBLE_QUOTE) input.drop(1) else input
176+
if (unEscaped.isBlank() && !showWhenEmpty) return builder.buildFuture()
177+
178+
val start = unEscaped.replace(" ", "_").uppercase()
179+
val items = NeuItems.findInternalNameStartingWithWithoutNPCs(start, isValidItem).take(limit)
180+
return builder.addOptionalEscaped(items).buildFuture()
181+
}
139182
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.github.itsempa.nautilus.commands.brigadier.arguments
2+
3+
import at.hannibal2.skyhanni.utils.NeuInternalName
4+
import com.github.itsempa.nautilus.commands.brigadier.BrigadierUtils
5+
import com.github.itsempa.nautilus.commands.brigadier.BrigadierUtils.readOptionalDoubleQuotedString
6+
import com.mojang.brigadier.LiteralMessage
7+
import com.mojang.brigadier.StringReader
8+
import com.mojang.brigadier.arguments.ArgumentType
9+
import com.mojang.brigadier.context.CommandContext
10+
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
11+
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
12+
import com.mojang.brigadier.suggestion.Suggestions
13+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
14+
import java.util.concurrent.CompletableFuture
15+
16+
private typealias ParsingFail = BrigadierUtils.ItemParsingFail
17+
18+
// TODO: Maybe add greedy argument support
19+
sealed class InternalNameArgumentType : ArgumentType<NeuInternalName> {
20+
21+
protected open val showWhenEmpty: Boolean = false
22+
23+
private val unknownValueException = DynamicCommandExceptionType { input ->
24+
LiteralMessage("Unknown item '$input'.")
25+
}
26+
27+
private val disallowedValueException = DynamicCommandExceptionType { input ->
28+
LiteralMessage("Disallowed item '$input'.")
29+
}
30+
31+
private val emptyValueException = SimpleCommandExceptionType { "Empty item name provided." }
32+
33+
protected fun parseString(input: String, reader: StringReader): NeuInternalName {
34+
val result = BrigadierUtils.parseItem(input, isValidItem = ::isValidItem)
35+
return when (result) {
36+
is NeuInternalName -> result
37+
ParsingFail.DISALLOWED_ITEM -> throw disallowedValueException.createWithContext(reader, input)
38+
ParsingFail.UNKNOWN_ITEM -> throw unknownValueException.createWithContext(reader, input)
39+
ParsingFail.EMPTY -> throw emptyValueException.createWithContext(reader)
40+
else -> throw IllegalArgumentException("Unexpected item parsing result: $result")
41+
}
42+
}
43+
44+
protected open fun isValidItem(item: NeuInternalName): Boolean = true
45+
46+
private open class ItemName : InternalNameArgumentType() {
47+
override fun parse(reader: StringReader): NeuInternalName {
48+
val input = reader.readOptionalDoubleQuotedString()
49+
return parseString(input, reader)
50+
}
51+
52+
override fun <S : Any?> listSuggestions(context: CommandContext<S>, builder: SuggestionsBuilder): CompletableFuture<Suggestions> {
53+
return BrigadierUtils.parseItemNameTabComplete(
54+
builder.remainingLowerCase,
55+
builder,
56+
showWhenEmpty = showWhenEmpty,
57+
isValidItem = ::isValidItem,
58+
)
59+
}
60+
}
61+
62+
private open class InternalName : InternalNameArgumentType() {
63+
override fun parse(reader: StringReader): NeuInternalName {
64+
val input = reader.readOptionalDoubleQuotedString()
65+
return parseString(input, reader)
66+
}
67+
68+
override fun <S : Any?> listSuggestions(context: CommandContext<S>, builder: SuggestionsBuilder): CompletableFuture<Suggestions> {
69+
return BrigadierUtils.parseInternalNameTabComplete(
70+
builder.remaining,
71+
builder,
72+
showWhenEmpty = showWhenEmpty,
73+
isValidItem = ::isValidItem,
74+
)
75+
}
76+
}
77+
78+
@Suppress("unused")
79+
companion object {
80+
fun itemName(): InternalNameArgumentType = ItemName()
81+
82+
fun itemName(isValid: (NeuInternalName) -> Boolean): InternalNameArgumentType {
83+
return object : ItemName() {
84+
private val isValid: (NeuInternalName) -> Boolean = isValid
85+
override fun isValidItem(item: NeuInternalName): Boolean = this.isValid(item)
86+
}
87+
}
88+
89+
fun itemName(showWhenEmpty: Boolean, isValid: (NeuInternalName) -> Boolean): InternalNameArgumentType {
90+
return object : ItemName() {
91+
override val showWhenEmpty: Boolean = showWhenEmpty
92+
private val isValid: (NeuInternalName) -> Boolean = isValid
93+
override fun isValidItem(item: NeuInternalName): Boolean = this.isValid(item)
94+
}
95+
}
96+
97+
fun itemName(allowed: Collection<NeuInternalName>, showWhenEmpty: Boolean = false): InternalNameArgumentType {
98+
return object : ItemName() {
99+
override val showWhenEmpty: Boolean = showWhenEmpty
100+
private val set = allowed.toSet()
101+
override fun isValidItem(item: NeuInternalName): Boolean = item in set
102+
}
103+
}
104+
105+
fun internalName(): InternalNameArgumentType = InternalName()
106+
107+
fun internalName(isValid: (NeuInternalName) -> Boolean): InternalNameArgumentType {
108+
return object : InternalName() {
109+
private val isValid: (NeuInternalName) -> Boolean = isValid
110+
override fun isValidItem(item: NeuInternalName): Boolean = this.isValid(item)
111+
}
112+
}
113+
114+
fun internalName(showWhenEmpty: Boolean, isValid: (NeuInternalName) -> Boolean): InternalNameArgumentType {
115+
return object : InternalName() {
116+
override val showWhenEmpty: Boolean = showWhenEmpty
117+
private val isValid: (NeuInternalName) -> Boolean = isValid
118+
override fun isValidItem(item: NeuInternalName): Boolean = this.isValid(item)
119+
}
120+
}
121+
122+
fun internalName(allowed: Collection<NeuInternalName>, showWhenEmpty: Boolean = false): InternalNameArgumentType {
123+
return object : InternalName() {
124+
override val showWhenEmpty: Boolean = showWhenEmpty
125+
private val set = allowed.toSet()
126+
override fun isValidItem(item: NeuInternalName): Boolean = item in set
127+
}
128+
}
129+
}
130+
}

src/main/kotlin/com/github/itsempa/nautilus/commands/brigadier/arguments/ItemNameArgumentType.kt

Lines changed: 0 additions & 66 deletions
This file was deleted.

src/main/kotlin/com/github/itsempa/nautilus/features/dev/GeneralTesting.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import at.hannibal2.skyhanni.api.event.HandleEvent
44
import at.hannibal2.skyhanni.config.commands.CommandCategory
55
import at.hannibal2.skyhanni.features.fishing.FishingApi.isLavaRod
66
import at.hannibal2.skyhanni.utils.NeuInternalName
7-
import com.github.itsempa.nautilus.commands.brigadier.arguments.ItemNameArgumentType
7+
import com.github.itsempa.nautilus.commands.brigadier.arguments.InternalNameArgumentType
88
import com.github.itsempa.nautilus.events.BrigadierRegisterEvent
99
import com.github.itsempa.nautilus.events.KillEvent
1010
import com.github.itsempa.nautilus.modules.DevModule
@@ -23,7 +23,7 @@ object GeneralTesting {
2323
aliases = listOf("ntbd")
2424
category = CommandCategory.DEVELOPER_TEST
2525

26-
argCallback("item", ItemNameArgumentType.itemName(::isValid)) { item ->
26+
argCallback("item", InternalNameArgumentType.itemName(::isValid)) { item ->
2727
NautilusChat.debug("Item: $item")
2828
}
2929
}

0 commit comments

Comments
 (0)