brigadier: Add support for wrapped parsers
This commit is contained in:
parent
79006ac40f
commit
62caa2d641
12 changed files with 889 additions and 1 deletions
|
|
@ -2,4 +2,5 @@ dependencies {
|
||||||
implementation(project(":cloud-core"))
|
implementation(project(":cloud-core"))
|
||||||
/* Needs to be provided by the platform */
|
/* Needs to be provided by the platform */
|
||||||
compileOnly("com.mojang", "brigadier", Versions.brigadier)
|
compileOnly("com.mojang", "brigadier", Versions.brigadier)
|
||||||
|
testImplementation("com.mojang", "brigadier", Versions.brigadier)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import cloud.commandframework.arguments.standard.IntegerArgument;
|
||||||
import cloud.commandframework.arguments.standard.ShortArgument;
|
import cloud.commandframework.arguments.standard.ShortArgument;
|
||||||
import cloud.commandframework.arguments.standard.StringArgument;
|
import cloud.commandframework.arguments.standard.StringArgument;
|
||||||
import cloud.commandframework.arguments.standard.StringArrayArgument;
|
import cloud.commandframework.arguments.standard.StringArrayArgument;
|
||||||
|
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
|
||||||
import cloud.commandframework.context.CommandContext;
|
import cloud.commandframework.context.CommandContext;
|
||||||
import cloud.commandframework.permission.CommandPermission;
|
import cloud.commandframework.permission.CommandPermission;
|
||||||
import cloud.commandframework.permission.Permission;
|
import cloud.commandframework.permission.Permission;
|
||||||
|
|
@ -92,6 +93,7 @@ public final class CloudBrigadierManager<C, S> {
|
||||||
private final Supplier<CommandContext<C>> dummyContextProvider;
|
private final Supplier<CommandContext<C>> dummyContextProvider;
|
||||||
private final CommandManager<C> commandManager;
|
private final CommandManager<C> commandManager;
|
||||||
private Function<S, C> brigadierCommandSenderMapper;
|
private Function<S, C> brigadierCommandSenderMapper;
|
||||||
|
private Function<C, S> backwardsBrigadierCommandSenderMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new cloud brigadier manager
|
* Create a new cloud brigadier manager
|
||||||
|
|
@ -108,6 +110,14 @@ public final class CloudBrigadierManager<C, S> {
|
||||||
this.commandManager = commandManager;
|
this.commandManager = commandManager;
|
||||||
this.dummyContextProvider = dummyContextProvider;
|
this.dummyContextProvider = dummyContextProvider;
|
||||||
this.registerInternalMappings();
|
this.registerInternalMappings();
|
||||||
|
commandManager.registerCommandPreProcessor(ctx -> {
|
||||||
|
if (this.backwardsBrigadierCommandSenderMapper != null) {
|
||||||
|
ctx.getCommandContext().store(
|
||||||
|
WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER,
|
||||||
|
this.backwardsBrigadierCommandSenderMapper.apply(ctx.getCommandContext().getSender())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerInternalMappings() {
|
private void registerInternalMappings() {
|
||||||
|
|
@ -195,6 +205,14 @@ public final class CloudBrigadierManager<C, S> {
|
||||||
/* Map String[] to a greedy string */
|
/* Map String[] to a greedy string */
|
||||||
this.registerMapping(new TypeToken<StringArrayArgument.StringArrayParser<C>>() {
|
this.registerMapping(new TypeToken<StringArrayArgument.StringArrayParser<C>>() {
|
||||||
}, false, argument -> StringArgumentType.greedyString());
|
}, false, argument -> StringArgumentType.greedyString());
|
||||||
|
/* Map wrapped parsers to their native types */
|
||||||
|
this.registerWrapperMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <O> void registerWrapperMapping() {
|
||||||
|
/* a small hack to make type inference work properly... O doesn't behave as a wildcard */
|
||||||
|
this.registerMapping(new TypeToken<WrappedBrigadierParser<C, O>>() {
|
||||||
|
}, true, WrappedBrigadierParser::getNativeArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -219,6 +237,18 @@ public final class CloudBrigadierManager<C, S> {
|
||||||
return this.brigadierCommandSenderMapper;
|
return this.brigadierCommandSenderMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the backwards mapper from Cloud to Brigadier command senders.
|
||||||
|
*
|
||||||
|
* <p>This is passed to completion requests for mapped argument types.</p>
|
||||||
|
*
|
||||||
|
* @param mapper the reverse brigadier sender mapper
|
||||||
|
* @since 1.4.0
|
||||||
|
*/
|
||||||
|
public void backwardsBrigadierSenderMapper(final @NonNull Function<@NonNull C, @Nullable S> mapper) {
|
||||||
|
this.backwardsBrigadierCommandSenderMapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether to use Brigadier's native suggestions for number argument types.
|
* Set whether to use Brigadier's native suggestions for number argument types.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
final class QueueAsStringReader extends StringReader {
|
||||||
|
private boolean closed;
|
||||||
|
private final Queue<String> input;
|
||||||
|
|
||||||
|
QueueAsStringReader(final Queue<String> input) {
|
||||||
|
super(String.join(" ", input));
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the underlying queue based on the reader state.
|
||||||
|
*
|
||||||
|
* <p>Can only be run once.</p>
|
||||||
|
*/
|
||||||
|
void updateQueue() {
|
||||||
|
if (this.closed) {
|
||||||
|
throw new IllegalStateException("double-closed");
|
||||||
|
}
|
||||||
|
this.closed = true;
|
||||||
|
|
||||||
|
/* Update elements in the queue to align it with the Brigadier cursor position */
|
||||||
|
int idx = this.getCursor();
|
||||||
|
|
||||||
|
while (idx > 0) {
|
||||||
|
final String next = this.input.element();
|
||||||
|
this.input.remove();
|
||||||
|
if (idx >= next.length()) {
|
||||||
|
idx -= next.length() + 1 /* whitespace */;
|
||||||
|
} else {
|
||||||
|
/* we've gotten to a partial word consumed by brigadier... let's try and modify the underlying queue */
|
||||||
|
if (!(this.input instanceof Deque<?>)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
((Deque<String>) this.input).addFirst(next.substring(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface combining {@link Queue} behaviour with a Brigadier {@link StringReader}.
|
||||||
|
*
|
||||||
|
* <p>This can be implemented either by wrapping an existing {@link StringReader} instance, or extending {@link StringReader}
|
||||||
|
* at its creation time to implement this interface.</p>
|
||||||
|
*/
|
||||||
|
public interface StringReaderAsQueue extends Queue<String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an existing Brigadier {@code StringReader}, get a view of it as a {@link Queue}
|
||||||
|
* @param reader the input reader
|
||||||
|
* @return a view of the contents of the reader as a {@link Queue} split by word.
|
||||||
|
*/
|
||||||
|
static StringReaderAsQueue from(final StringReader reader) {
|
||||||
|
if (reader instanceof StringReaderAsQueue) {
|
||||||
|
return (StringReaderAsQueue) reader;
|
||||||
|
} else {
|
||||||
|
return new StringReaderAsQueueImpl.Wrapping(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the backing {@link StringReader} used to source data.
|
||||||
|
*
|
||||||
|
* @return the original reader
|
||||||
|
*/
|
||||||
|
StringReader getOriginal();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean isEmpty() {
|
||||||
|
return !this.getOriginal().canRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean contains(final Object element) {
|
||||||
|
if (element == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the string is in the collection, and
|
||||||
|
final int cursor = this.getOriginal().getCursor();
|
||||||
|
final String contents = this.getOriginal().getString();
|
||||||
|
final int idx = contents.indexOf((String) element, cursor);
|
||||||
|
if (idx == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int length = this.getOriginal().getTotalLength();
|
||||||
|
final int end = idx + contents.length();
|
||||||
|
|
||||||
|
return (idx == cursor || Character.isWhitespace(contents.charAt(idx - 1)))
|
||||||
|
&& (end == length || Character.isWhitespace(contents.charAt(end)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @NonNull Iterator<String> iterator() {
|
||||||
|
// lazily break into words -- doesn't consume though!
|
||||||
|
return new Iterator<String>() {
|
||||||
|
private final String contents = StringReaderAsQueue.this.getOriginal().getString();
|
||||||
|
private int rangeStart = StringReaderAsQueue.this.getOriginal().getCursor();
|
||||||
|
private int rangeEnd = this.calculateNextEnd();
|
||||||
|
|
||||||
|
private int calculateNextEnd() {
|
||||||
|
if (this.rangeStart >= this.contents.length()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
final int nextSpace = StringReaderAsQueueImpl.nextWhitespace(this.contents, this.rangeStart);
|
||||||
|
return nextSpace == -1 ? this.contents.length() : nextSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeNext() {
|
||||||
|
this.rangeStart = this.rangeEnd + 1;
|
||||||
|
this.rangeEnd = this.calculateNextEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.rangeEnd > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
if (!this.hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
final String next = this.contents.substring(this.rangeStart, this.rangeEnd);
|
||||||
|
this.computeNext();
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default Object[] toArray() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return new Object[0];
|
||||||
|
}
|
||||||
|
final ArrayList<String> out = new ArrayList<>(5);
|
||||||
|
for (final String element : this) {
|
||||||
|
/* addAll calls toArray on us... which would create a stack overflow */
|
||||||
|
//noinspection UseBulkOperation
|
||||||
|
out.add(element);
|
||||||
|
}
|
||||||
|
return out.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default <T> T[] toArray(final T[] a) {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return Arrays.copyOf(a, 0);
|
||||||
|
}
|
||||||
|
final ArrayList<String> out = new ArrayList<>(5);
|
||||||
|
for (final String element : this) {
|
||||||
|
/* addAll calls toArray on us... which would create a stack overflow */
|
||||||
|
//noinspection UseBulkOperation
|
||||||
|
out.add(element);
|
||||||
|
}
|
||||||
|
return out.toArray(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean add(final String element) {
|
||||||
|
throw new IllegalStateException("StringReaders cannot have elements appended");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean offer(final String element) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String remove() {
|
||||||
|
final String result = this.poll();
|
||||||
|
if (result == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default String element() {
|
||||||
|
final String result = this.peek();
|
||||||
|
if (result == null) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean containsAll(final @NonNull Collection<?> elements) {
|
||||||
|
throw new UnsupportedOperationException("Complex Queue operations are not yet implemented in Cloud");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean addAll(final @NonNull Collection<? extends String> elements) {
|
||||||
|
throw new UnsupportedOperationException("Complex Queue operations are not yet implemented in Cloud");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean removeAll(final @NonNull Collection<?> elements) {
|
||||||
|
throw new UnsupportedOperationException("Complex Queue operations are not yet implemented in Cloud");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default boolean retainAll(final @NonNull Collection<?> elements) {
|
||||||
|
throw new UnsupportedOperationException("Complex Queue operations are not yet implemented in Cloud");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void clear() { // consume all
|
||||||
|
this.getOriginal().setCursor(this.getOriginal().getTotalLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around Mojang's {@link StringReader} that implements the {@link Queue} interface.
|
||||||
|
*
|
||||||
|
* <p>This allows passing the full Brigadier state around through Cloud's parsing chain.</p>
|
||||||
|
*/
|
||||||
|
final class StringReaderAsQueueImpl {
|
||||||
|
|
||||||
|
private StringReaderAsQueueImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next whitespace index starting at startIdx, or -1 if none is found */
|
||||||
|
static int nextWhitespace(final String input, final int startIdx) {
|
||||||
|
for (int i = startIdx, length = input.length(); i < length; ++i) {
|
||||||
|
if (Character.isWhitespace(input.charAt(i))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapping variant is implemented here
|
||||||
|
* Extending variant is only implementable with Mixin, because of clashing return types on the two interfaces (on `peek`). */
|
||||||
|
|
||||||
|
static final class Wrapping implements StringReaderAsQueue {
|
||||||
|
private final StringReader original;
|
||||||
|
private int nextSpaceIdx; /* the character before the start of a new word */
|
||||||
|
private @Nullable String nextWord;
|
||||||
|
|
||||||
|
Wrapping(final StringReader original) {
|
||||||
|
this.original = original;
|
||||||
|
this.nextSpaceIdx = original.getCursor() - 1;
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringReader getOriginal() {
|
||||||
|
return this.original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Brigadier doesn't automatically consume whitespace... in order to get the matched behaviour, we consume whitespace
|
||||||
|
* after every popped string.
|
||||||
|
*/
|
||||||
|
private void advance() {
|
||||||
|
final int startOfNextWord = this.nextSpaceIdx + 1;
|
||||||
|
this.nextSpaceIdx = nextWhitespace(this.original.getString(), startOfNextWord);
|
||||||
|
if (this.nextSpaceIdx != -1) {
|
||||||
|
this.nextWord = this.original.getString().substring(startOfNextWord, this.nextSpaceIdx);
|
||||||
|
} else if (startOfNextWord < this.original.getTotalLength()) {
|
||||||
|
this.nextWord = this.original.getString().substring(startOfNextWord);
|
||||||
|
this.nextSpaceIdx = this.original.getTotalLength() + 1;
|
||||||
|
} else {
|
||||||
|
this.nextWord = null;
|
||||||
|
}
|
||||||
|
this.original.setCursor(startOfNextWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String poll() {
|
||||||
|
/* peek and then advance */
|
||||||
|
final String next = this.peek();
|
||||||
|
if (next != null) {
|
||||||
|
this.advance();
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String peek() {
|
||||||
|
return this.nextWord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
if (this.nextWord == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int counter = 1;
|
||||||
|
for (int i = this.nextSpaceIdx;
|
||||||
|
i != -1 && i < this.original.getTotalLength();
|
||||||
|
i = nextWhitespace(this.original.getString(), i + 1)) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(final Object o) {
|
||||||
|
if (Objects.equals(o, this.nextWord)) {
|
||||||
|
this.advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
StringReaderAsQueue.super.clear();
|
||||||
|
this.nextWord = null;
|
||||||
|
this.nextSpaceIdx = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import cloud.commandframework.arguments.parser.ArgumentParseResult;
|
||||||
|
import cloud.commandframework.arguments.parser.ArgumentParser;
|
||||||
|
import cloud.commandframework.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.arguments.ArgumentType;
|
||||||
|
import com.mojang.brigadier.context.StringRange;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestion;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapped argument parser that can expose Brigadier argument types to the Cloud world.
|
||||||
|
*
|
||||||
|
* @param <C> the sender type
|
||||||
|
* @param <T> the value type of the argument
|
||||||
|
*/
|
||||||
|
public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T> {
|
||||||
|
public static final String COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER = "_cloud_brigadier_native_sender";
|
||||||
|
|
||||||
|
private final ArgumentType<T> nativeType;
|
||||||
|
private final int expectedArgumentCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an argument parser based on a brigadier command.
|
||||||
|
*
|
||||||
|
* @param nativeType the native command type
|
||||||
|
*/
|
||||||
|
public WrappedBrigadierParser(final ArgumentType<T> nativeType) {
|
||||||
|
this(nativeType, DEFAULT_ARGUMENT_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an argument parser based on a brigadier command.
|
||||||
|
*
|
||||||
|
* @param nativeType the native command type
|
||||||
|
* @param expectedArgumentCount the number of arguments the brigadier type is expected to consume
|
||||||
|
*/
|
||||||
|
public WrappedBrigadierParser(
|
||||||
|
final ArgumentType<T> nativeType,
|
||||||
|
final int expectedArgumentCount
|
||||||
|
) {
|
||||||
|
this.nativeType = requireNonNull(nativeType, "brigadierType");
|
||||||
|
this.expectedArgumentCount = expectedArgumentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the backing Brigadier {@link ArgumentType} for this parser.
|
||||||
|
*
|
||||||
|
* @return the argument type
|
||||||
|
*/
|
||||||
|
public ArgumentType<T> getNativeArgument() {
|
||||||
|
return this.nativeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull ArgumentParseResult<@NonNull T> parse(
|
||||||
|
@NonNull final CommandContext<@NonNull C> commandContext,
|
||||||
|
@NonNull final Queue<@NonNull String> inputQueue
|
||||||
|
) {
|
||||||
|
// Convert to a brig reader
|
||||||
|
final StringReader reader;
|
||||||
|
|
||||||
|
if (inputQueue instanceof StringReader) {
|
||||||
|
reader = (StringReader) inputQueue;
|
||||||
|
} else if (inputQueue instanceof StringReaderAsQueue) {
|
||||||
|
reader = ((StringReaderAsQueue) inputQueue).getOriginal();
|
||||||
|
} else {
|
||||||
|
reader = new QueueAsStringReader(inputQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try to parse
|
||||||
|
try {
|
||||||
|
return ArgumentParseResult.success(this.nativeType.parse(reader));
|
||||||
|
} catch (final CommandSyntaxException ex) {
|
||||||
|
return ArgumentParseResult.failure(ex);
|
||||||
|
} finally {
|
||||||
|
if (reader instanceof QueueAsStringReader) {
|
||||||
|
((QueueAsStringReader) reader).updateQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull List<@NonNull String> suggestions(
|
||||||
|
final @NonNull CommandContext<C> commandContext,
|
||||||
|
final @NonNull String input
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* Strictly, this is incorrect.
|
||||||
|
* However, it seems that all Mojang really does with the context passed here
|
||||||
|
* is use it to query data on the native sender. Hopefully this hack holds up.
|
||||||
|
*/
|
||||||
|
final com.mojang.brigadier.context.CommandContext<Object> reverseMappedContext = new com.mojang.brigadier.context.CommandContext<>(
|
||||||
|
commandContext.getOrDefault(COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER, commandContext.getSender()),
|
||||||
|
commandContext.getRawInputJoined(),
|
||||||
|
Collections.emptyMap(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Collections.emptyList(),
|
||||||
|
StringRange.at(0),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
final CompletableFuture<Suggestions> result = this.nativeType.listSuggestions(reverseMappedContext,
|
||||||
|
new SuggestionsBuilder(input, 0));
|
||||||
|
|
||||||
|
/* again, avert your eyes */
|
||||||
|
final List<Suggestion> suggestions = result.join().getList();
|
||||||
|
final List<String> out = new ArrayList<>(suggestions.size());
|
||||||
|
for (final Suggestion suggestion : suggestions) {
|
||||||
|
out.add(suggestion.getText());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isContextFree() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRequestedArgumentCount() {
|
||||||
|
return this.expectedArgumentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for wrapping brigadier {@link com.mojang.brigadier.arguments.ArgumentType ArgumentTypes}
|
||||||
|
* as Cloud {@link cloud.commandframework.arguments.parser.ArgumentParser}.
|
||||||
|
*/
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
@ -23,6 +23,14 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Brigadier mappings
|
* Brigadier mappings.
|
||||||
|
*
|
||||||
|
* <p>For platform implementations using Brigadier, {@link cloud.commandframework.brigadier.CloudBrigadierManager} can map
|
||||||
|
* Cloud {@link cloud.commandframework.CommandTree command trees} to Brigadier nodes.</p>
|
||||||
|
*
|
||||||
|
* <p>To bridge Brigadier and Cloud argument types, an argument parser that wraps Brigadier argument types is available in
|
||||||
|
* {@link cloud.commandframework.brigadier.argument.WrappedBrigadierParser}. Other classes in that package allow constructing
|
||||||
|
* Brigadier {@link com.mojang.brigadier.StringReader} instances that can be used for efficient interoperability with
|
||||||
|
* Brigadier.</p>
|
||||||
*/
|
*/
|
||||||
package cloud.commandframework.brigadier;
|
package cloud.commandframework.brigadier;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class QueueAsStringReaderTest {
|
||||||
|
|
||||||
|
private static Queue<String> words(final String... elements) {
|
||||||
|
return new LinkedList<>(Arrays.asList(elements));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnchanged() {
|
||||||
|
final Queue<String> contents = words("hello", "world");
|
||||||
|
final QueueAsStringReader reader = new QueueAsStringReader(contents);
|
||||||
|
reader.updateQueue();
|
||||||
|
|
||||||
|
assertEquals(words("hello", "world"), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSingleWordRemoved() throws CommandSyntaxException {
|
||||||
|
final Queue<String> contents = words("hello", "some", "worlds");
|
||||||
|
final QueueAsStringReader reader = new QueueAsStringReader(contents);
|
||||||
|
assertEquals("hello", reader.readString());
|
||||||
|
reader.updateQueue();
|
||||||
|
|
||||||
|
assertEquals(words("some", "worlds"), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBeginningAndMiddleRemoved() throws CommandSyntaxException {
|
||||||
|
final Queue<String> contents = words("hello", "some", "worlds");
|
||||||
|
final QueueAsStringReader reader = new QueueAsStringReader(contents);
|
||||||
|
assertEquals("hello", reader.readString());
|
||||||
|
reader.skipWhitespace();
|
||||||
|
assertEquals("some", reader.readString());
|
||||||
|
reader.updateQueue();
|
||||||
|
|
||||||
|
assertEquals(words("worlds"), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAllThreeWordsRead() throws CommandSyntaxException {
|
||||||
|
final Queue<String> contents = words("hello", "some", "worlds");
|
||||||
|
final QueueAsStringReader reader = new QueueAsStringReader(contents);
|
||||||
|
assertEquals("hello", reader.readString());
|
||||||
|
reader.skipWhitespace();
|
||||||
|
assertEquals("some", reader.readString());
|
||||||
|
reader.skipWhitespace();
|
||||||
|
assertEquals("worlds", reader.readString());
|
||||||
|
reader.updateQueue();
|
||||||
|
|
||||||
|
assertTrue(contents.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPartialWordRead() throws CommandSyntaxException {
|
||||||
|
final Queue<String> contents = words("hi", "minecraft:pig");
|
||||||
|
final QueueAsStringReader reader = new QueueAsStringReader(contents);
|
||||||
|
assertEquals("hi", reader.readString());
|
||||||
|
reader.skipWhitespace();
|
||||||
|
assertEquals("minecraft", reader.readStringUntil(':'));
|
||||||
|
reader.updateQueue();
|
||||||
|
|
||||||
|
assertEquals(words("pig"), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package cloud.commandframework.brigadier.argument;
|
||||||
|
|
||||||
|
import cloud.commandframework.types.tuples.Pair;
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link StringReaderAsQueue}
|
||||||
|
*
|
||||||
|
* Most operations have 4 cases: 0 arguments, 1 argument, 2 arguments, and 3 or more arguments.
|
||||||
|
* At that point every whitespace handling path should be exercised.
|
||||||
|
*/
|
||||||
|
class StringReaderAsQueueTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIsNotEmpty() {
|
||||||
|
final StringReader reader = new StringReader("hello there");
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(reader);
|
||||||
|
assertFalse(queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateEmpty() {
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(new StringReader(""));
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
assertNull(queue.peek());
|
||||||
|
assertNull(queue.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReadWord() {
|
||||||
|
final StringReader original = new StringReader("meow purr");
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(original);
|
||||||
|
assertEquals("meow", queue.poll());
|
||||||
|
assertEquals("purr", original.getRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReadSingleWordContents() {
|
||||||
|
final StringReader reader = new StringReader("hello");
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(reader);
|
||||||
|
|
||||||
|
assertFalse(queue.isEmpty());
|
||||||
|
assertEquals("hello", queue.peek());
|
||||||
|
assertEquals(1, queue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReadTwoWords() {
|
||||||
|
final StringReader original = new StringReader("meow purr");
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(original);
|
||||||
|
assertEquals("meow", queue.poll());
|
||||||
|
assertEquals("purr", queue.poll());
|
||||||
|
assertTrue(queue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReadThreeWords() {
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(new StringReader("we enjoy commands"));
|
||||||
|
assertEquals("we", queue.poll());
|
||||||
|
assertEquals("enjoy", queue.poll());
|
||||||
|
assertEquals("commands", queue.poll());
|
||||||
|
assertNull(queue.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPeekRepeatedly() {
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(new StringReader("commands are fun"));
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
assertEquals("commands", queue.peek());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiElementIterator() {
|
||||||
|
final StringReader reader = new StringReader("tell @a :3");
|
||||||
|
final Iterator<String> elements = StringReaderAsQueue.from(reader).iterator();
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals("tell", elements.next());
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals("@a", elements.next());
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals(":3", elements.next());
|
||||||
|
assertFalse(elements.hasNext());
|
||||||
|
|
||||||
|
assertThrows(NoSuchElementException.class, elements::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDoubleElementIterator() {
|
||||||
|
final Iterator<String> elements = StringReaderAsQueue.from(new StringReader("cloud good")).iterator();
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals("cloud", elements.next());
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals("good", elements.next());
|
||||||
|
assertFalse(elements.hasNext());
|
||||||
|
|
||||||
|
assertThrows(NoSuchElementException.class, elements::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSingleElementIterator() {
|
||||||
|
final Iterator<String> elements = StringReaderAsQueue.from(new StringReader("word")).iterator();
|
||||||
|
assertTrue(elements.hasNext());
|
||||||
|
assertEquals("word", elements.next());
|
||||||
|
assertFalse(elements.hasNext());
|
||||||
|
|
||||||
|
assertThrows(NoSuchElementException.class, elements::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyIterator() {
|
||||||
|
final Iterator<String> empty = StringReaderAsQueue.from(new StringReader("")).iterator();
|
||||||
|
|
||||||
|
assertFalse(empty.hasNext());
|
||||||
|
assertThrows(NoSuchElementException.class, empty::next);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPartlyStartedIteration() {
|
||||||
|
final Queue<String> queue = StringReaderAsQueue.from(new StringReader("let's go for a walk"));
|
||||||
|
assertEquals("let's", queue.poll());
|
||||||
|
|
||||||
|
final Iterator<String> it = queue.iterator();
|
||||||
|
assertEquals("go", it.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToArrayMultiple() {
|
||||||
|
final Queue<String> elements = StringReaderAsQueue.from(new StringReader("one two three four"));
|
||||||
|
assertArrayEquals(new String[] { "one", "two", "three", "four" }, elements.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSizes() {
|
||||||
|
Stream.of(
|
||||||
|
Pair.of("", 0),
|
||||||
|
Pair.of("hi", 1),
|
||||||
|
Pair.of("the second!", 2),
|
||||||
|
Pair.of("a third entry", 3),
|
||||||
|
Pair.of("one two three, four", 4)
|
||||||
|
).forEach(pair -> {
|
||||||
|
assertEquals(pair.getSecond(), StringReaderAsQueue.from(new StringReader(pair.getFirst())).size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -175,4 +175,8 @@ public class VelocityCommandManager<C> extends CommandManager<C> implements Brig
|
||||||
return this.commandSenderMapper;
|
return this.commandSenderMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final @NonNull Function<@NonNull C, @NonNull CommandSource> getBackwardsCommandSenderMapper() {
|
||||||
|
return this.backwardsCommandSenderMapper;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ final class VelocityPluginRegistrationHandler<C> implements CommandRegistrationH
|
||||||
this.brigadierManager.brigadierSenderMapper(
|
this.brigadierManager.brigadierSenderMapper(
|
||||||
sender -> this.manager.getCommandSenderMapper().apply(sender)
|
sender -> this.manager.getCommandSenderMapper().apply(sender)
|
||||||
);
|
);
|
||||||
|
this.brigadierManager.backwardsBrigadierSenderMapper(this.manager.getBackwardsCommandSenderMapper());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue