Skip to content

Commit ed3965c

Browse files
authored
Clean up location parser & suggestions, add more tests (#61)
* Add failing test for location flag suggestions * Adjust LocationParser * Add more location suggestion tests * fix formatting * remove test command
1 parent f1a7eb9 commit ed3965c

File tree

3 files changed

+104
-26
lines changed

3 files changed

+104
-26
lines changed

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/parser/location/LocationCoordinateParser.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,8 @@ public final class LocationCoordinateParser<C> implements ArgumentParser<C, Loca
6161
try {
6262
final boolean empty = commandInput.peekString().isEmpty() || commandInput.peek() == ' ';
6363
coordinate = empty ? 0 : commandInput.readDouble();
64-
65-
// You can have a prefix without a number, in which case we wouldn't consume the
66-
// subsequent whitespace. We do it manually.
67-
if (commandInput.hasRemainingInput() && commandInput.peek() == ' ') {
68-
commandInput.read();
64+
if (commandInput.hasRemainingInput()) {
65+
commandInput.skipWhitespace();
6966
}
7067
} catch (final Exception e) {
7168
return ArgumentParseResult.failure(new DoubleParser.DoubleParseException(

cloud-bukkit/src/main/java/org/incendo/cloud/bukkit/parser/location/LocationParser.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ public final class LocationParser<C> implements ArgumentParser<C, Location>, Blo
130130
} else if (bukkitSender instanceof Entity) {
131131
originalLocation = ((Entity) bukkitSender).getLocation();
132132
} else {
133-
originalLocation = new Location(Bukkit.getWorlds().get(0), 0, 0, 0);
133+
if (Bukkit.getWorlds().isEmpty()) {
134+
// For testing
135+
originalLocation = new Location(null, 0, 0, 0);
136+
} else {
137+
originalLocation = new Location(Bukkit.getWorlds().get(0), 0, 0, 0);
138+
}
134139
}
135140

136141
if (((coordinates[0].type() == LocationCoordinateType.LOCAL)
@@ -216,19 +221,33 @@ private static float toRadians(final float degrees) {
216221
final @NonNull CommandContext<C> commandContext,
217222
final @NonNull CommandInput input
218223
) {
219-
final int toSkip = Math.min(components, input.remainingTokens()) - 1;
220-
final StringBuilder prefix = new StringBuilder();
221-
for (int i = 0; i < toSkip; i++) {
222-
prefix.append(input.readStringSkipWhitespace()).append(" ");
224+
final CommandInput inputCopy = input.copy();
225+
226+
int idx = input.cursor();
227+
for (int i = 0; i < components; i++) {
228+
idx = input.cursor();
229+
if (!input.hasRemainingInput(true)) {
230+
break;
231+
}
232+
final ArgumentParseResult<LocationCoordinate> coordinateResult = new LocationCoordinateParser<C>().parse(
233+
commandContext,
234+
input
235+
);
236+
if (coordinateResult.failure().isPresent()) {
237+
break;
238+
}
223239
}
240+
input.cursor(idx);
224241

225242
if (input.hasRemainingInput() && (input.peek() == '~' || input.peek() == '^')) {
226-
prefix.append(input.read());
243+
input.read();
227244
}
228245

246+
final String prefix = inputCopy.difference(input, true);
247+
229248
return IntegerParser.getSuggestions(
230-
SUGGESTION_RANGE,
231-
input
249+
SUGGESTION_RANGE,
250+
input
232251
).stream().map(string -> prefix + string).collect(Collectors.toList());
233252
}
234253

cloud-bukkit/src/test/java/org/incendo/cloud/bukkit/parser/LocationArgumentTest.java

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,38 @@
2323
//
2424
package org.incendo.cloud.bukkit.parser;
2525

26+
import java.util.Arrays;
2627
import java.util.Collections;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Set;
31+
import java.util.function.Function;
32+
import java.util.stream.Collectors;
2733
import java.util.stream.Stream;
34+
import java.util.stream.StreamSupport;
2835
import org.bukkit.Location;
2936
import org.bukkit.World;
3037
import org.bukkit.command.CommandSender;
3138
import org.bukkit.util.Vector;
3239
import org.checkerframework.checker.nullness.qual.NonNull;
40+
import org.incendo.cloud.CommandManager;
41+
import org.incendo.cloud.bukkit.BukkitCommandContextKeys;
3342
import org.incendo.cloud.bukkit.parser.location.LocationParser;
3443
import org.incendo.cloud.bukkit.util.ServerTest;
3544
import org.incendo.cloud.context.CommandInput;
45+
import org.incendo.cloud.execution.ExecutionCoordinator;
46+
import org.incendo.cloud.internal.CommandRegistrationHandler;
3647
import org.incendo.cloud.parser.ArgumentParseResult;
48+
import org.incendo.cloud.parser.flag.CommandFlag;
49+
import org.incendo.cloud.suggestion.Suggestion;
3750
import org.junit.jupiter.params.ParameterizedTest;
3851
import org.junit.jupiter.params.provider.Arguments;
3952
import org.junit.jupiter.params.provider.MethodSource;
4053
import org.junit.jupiter.params.provider.ValueSource;
4154
import org.mockito.Mock;
4255

4356
import static com.google.common.truth.Truth.assertThat;
57+
import static org.junit.jupiter.api.Assertions.assertEquals;
4458
import static org.junit.jupiter.params.provider.Arguments.arguments;
4559
import static org.mockito.Mockito.when;
4660

@@ -49,6 +63,54 @@ class LocationArgumentTest extends ServerTest {
4963
@Mock
5064
private World world;
5165

66+
@ParameterizedTest
67+
@MethodSource
68+
void suggestions(final String input, final List<String> expectedSuggestions) {
69+
final LocationParser<CommandSender> parser = new LocationParser<>();
70+
71+
final Set<String> suggestions =
72+
StreamSupport.stream(parser.suggestionProvider()
73+
.suggestionsFuture(this.commandContext(), CommandInput.of(input)).join().spliterator(), false)
74+
.map(Suggestion::suggestion)
75+
.collect(Collectors.toSet());
76+
77+
assertEquals(new HashSet<>(expectedSuggestions), suggestions);
78+
}
79+
80+
@ParameterizedTest
81+
@MethodSource("suggestions")
82+
void suggestionsFlag(final String input, final List<String> expectedSuggestions) {
83+
final CommandManager<CommandSender> manager = new CommandManager<CommandSender>(
84+
ExecutionCoordinator.simpleCoordinator(), CommandRegistrationHandler.nullCommandRegistrationHandler()
85+
) {
86+
@Override
87+
public boolean hasPermission(@NonNull CommandSender sender, @NonNull String permission) {
88+
return true;
89+
}
90+
};
91+
manager.registerCommandPreProcessor(ctx -> ctx.commandContext().store(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER, ctx.commandContext().sender()));
92+
manager.command(
93+
manager.commandBuilder("flag")
94+
.flag(CommandFlag.builder("loc").withComponent(LocationParser.locationParser()).build())
95+
);
96+
final Function<String, String> flagSuggestion = s -> "flag --loc " + s;
97+
assertEquals(
98+
new HashSet<>(expectedSuggestions),
99+
manager.suggestionFactory().suggestImmediately(this.commandContext().sender(), flagSuggestion.apply(input))
100+
.list().stream().map(Suggestion::suggestion).collect(Collectors.toSet())
101+
);
102+
}
103+
104+
static Stream<Arguments> suggestions() {
105+
return Stream.of(
106+
arguments("", Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")),
107+
arguments("1 ", Arrays.asList("1 0", "1 1", "1 2", "1 3", "1 4", "1 5", "1 6", "1 7", "1 8", "1 9")),
108+
arguments("1 1", Arrays.asList("1 10", "1 11", "1 12", "1 13", "1 14", "1 15", "1 16", "1 17", "1 18", "1 19")),
109+
arguments("1 1 ", Arrays.asList("1 1 0", "1 1 1", "1 1 2", "1 1 3", "1 1 4", "1 1 5", "1 1 6", "1 1 7", "1 1 8", "1 1 9")),
110+
arguments("1 1 1", Arrays.asList("1 1 1", "1 1 10", "1 1 11", "1 1 12", "1 1 13", "1 1 14", "1 1 15", "1 1 16", "1 1 17", "1 1 18", "1 1 19"))
111+
);
112+
}
113+
52114
@ParameterizedTest
53115
@MethodSource("Parse_HappyFlow_Success_Source")
54116
void Parse_HappyFlow_Success(final @NonNull String input, final @NonNull Vector expectedLocation) {
@@ -59,8 +121,8 @@ void Parse_HappyFlow_Success(final @NonNull String input, final @NonNull Vector
59121

60122
// Act
61123
final ArgumentParseResult<Location> result = parser.parse(
62-
this.commandContext(),
63-
commandInput
124+
this.commandContext(),
125+
commandInput
64126
);
65127

66128
// Assert
@@ -72,28 +134,28 @@ void Parse_HappyFlow_Success(final @NonNull String input, final @NonNull Vector
72134

73135
static @NonNull Stream<@NonNull Arguments> Parse_HappyFlow_Success_Source() {
74136
return Stream.of(
75-
arguments("~ ~ ~", new Vector(0, 0, 0)),
76-
arguments("~10 ~10 ~10", new Vector(10, 10, 10)),
77-
arguments("~-10 ~-10 ~-10", new Vector(-10, -10, -10)),
78-
arguments("^ ^ ^", new Vector(0, 0, 0)),
79-
arguments("^0 ^0 ^0", new Vector(0, 0, 0)),
80-
arguments("0 0 0", new Vector(0, 0, 0)),
81-
arguments("10 10 10", new Vector(10, 10, 10)),
82-
arguments("-10 -10 -10", new Vector(-10, -10, -10))
137+
arguments("~ ~ ~", new Vector(0, 0, 0)),
138+
arguments("~10 ~10 ~10", new Vector(10, 10, 10)),
139+
arguments("~-10 ~-10 ~-10", new Vector(-10, -10, -10)),
140+
arguments("^ ^ ^", new Vector(0, 0, 0)),
141+
arguments("^0 ^0 ^0", new Vector(0, 0, 0)),
142+
arguments("0 0 0", new Vector(0, 0, 0)),
143+
arguments("10 10 10", new Vector(10, 10, 10)),
144+
arguments("-10 -10 -10", new Vector(-10, -10, -10))
83145
);
84146
}
85147

86148
@ParameterizedTest
87-
@ValueSource(strings = { "0 0", "not a location" })
149+
@ValueSource(strings = {"0 0", "not a location"})
88150
void Parse_InvalidLocation_Failure(final @NonNull String input) {
89151
// Arrange
90152
final LocationParser<CommandSender> parser = new LocationParser<>();
91153
final CommandInput commandInput = CommandInput.of(input);
92154

93155
// Act
94156
final ArgumentParseResult<Location> result = parser.parse(
95-
this.commandContext(),
96-
commandInput
157+
this.commandContext(),
158+
commandInput
97159
);
98160

99161
// Assert

0 commit comments

Comments
 (0)