Construct more reasonable syntax messages

This commit is contained in:
Alexander Söderberg 2020-09-18 22:42:18 +02:00
parent 3f852d068e
commit c208204fa3
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
6 changed files with 135 additions and 43 deletions

View file

@ -39,6 +39,7 @@ import com.intellectualsites.commands.meta.CommandMeta;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@ -101,7 +102,10 @@ public final class CommandTree<C, M extends CommandMeta> {
public Optional<Command<C, M>> parse(@Nonnull final CommandContext<C> commandContext, public Optional<Command<C, M>> parse(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> args) throws @Nonnull final Queue<String> args) throws
NoSuchCommandException, NoPermissionException, InvalidSyntaxException { NoSuchCommandException, NoPermissionException, InvalidSyntaxException {
final Optional<Command<C, M>> commandOptional = parseCommand(commandContext, args, this.internalTree); final Optional<Command<C, M>> commandOptional = parseCommand(new ArrayList<>(),
commandContext,
args,
this.internalTree);
commandOptional.flatMap(Command::getSenderType).ifPresent(requiredType -> { commandOptional.flatMap(Command::getSenderType).ifPresent(requiredType -> {
if (!requiredType.isAssignableFrom(commandContext.getSender().getClass())) { if (!requiredType.isAssignableFrom(commandContext.getSender().getClass())) {
throw new InvalidCommandSenderException(commandContext.getSender(), requiredType, Collections.emptyList()); throw new InvalidCommandSenderException(commandContext.getSender(), requiredType, Collections.emptyList());
@ -110,7 +114,8 @@ public final class CommandTree<C, M extends CommandMeta> {
return commandOptional; return commandOptional;
} }
private Optional<Command<C, M>> parseCommand(@Nonnull final CommandContext<C> commandContext, private Optional<Command<C, M>> parseCommand(@Nonnull final List<CommandArgument<C, ?>> parsedArguments,
@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> commandQueue, @Nonnull final Queue<String> commandQueue,
@Nonnull final Node<CommandArgument<C, ?>> root) { @Nonnull final Node<CommandArgument<C, ?>> root) {
String permission = this.isPermitted(commandContext.getSender(), root); String permission = this.isPermitted(commandContext.getSender(), root);
@ -121,7 +126,10 @@ public final class CommandTree<C, M extends CommandMeta> {
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
final Optional<Command<C, M>> parsedChild = this.attemptParseUnambiguousChild(commandContext, root, commandQueue); final Optional<Command<C, M>> parsedChild = this.attemptParseUnambiguousChild(parsedArguments,
commandContext,
root,
commandQueue);
// noinspection all // noinspection all
if (parsedChild != null) { if (parsedChild != null) {
return parsedChild; return parsedChild;
@ -136,9 +144,7 @@ public final class CommandTree<C, M extends CommandMeta> {
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(root.getValue() .apply(parsedArguments, root),
.getOwningCommand()
.getArguments()),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
@ -147,10 +153,7 @@ public final class CommandTree<C, M extends CommandMeta> {
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull( .apply(parsedArguments, root),
Objects.requireNonNull(root.getValue())
.getOwningCommand())
.getArguments()),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
@ -164,21 +167,32 @@ public final class CommandTree<C, M extends CommandMeta> {
if (child.getValue() != null) { if (child.getValue() != null) {
final ArgumentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue); final ArgumentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
if (result.getParsedValue().isPresent()) { if (result.getParsedValue().isPresent()) {
return this.parseCommand(commandContext, commandQueue, child); parsedArguments.add(child.getValue());
return this.parseCommand(parsedArguments, commandContext, commandQueue, child);
} /*else if (result.getFailure().isPresent() && root.children.size() == 1) { } /*else if (result.getFailure().isPresent() && root.children.size() == 1) {
}*/ }*/
} }
} }
} }
/* We could not find a match */ /* We could not find a match */
throw new NoSuchCommandException(commandContext.getSender(), if (root.equals(this.internalTree)) {
getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), throw new NoSuchCommandException(commandContext.getSender(),
stringOrEmpty(commandQueue.peek())); getChain(root).stream().map(Node::getValue).collect(Collectors.toList()),
stringOrEmpty(commandQueue.peek()));
}
/* We have already traversed the tree */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList()));
} }
} }
@Nullable @Nullable
private Optional<Command<C, M>> attemptParseUnambiguousChild(@Nonnull final CommandContext<C> commandContext, private Optional<Command<C, M>> attemptParseUnambiguousChild(@Nonnull final List<CommandArgument<C, ?>> parsedArguments,
@Nonnull final CommandContext<C> commandContext,
@Nonnull final Node<CommandArgument<C, ?>> root, @Nonnull final Node<CommandArgument<C, ?>> root,
@Nonnull final Queue<String> commandQueue) { @Nonnull final Queue<String> commandQueue) {
String permission; String permission;
@ -204,18 +218,25 @@ public final class CommandTree<C, M extends CommandMeta> {
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull( .apply(Objects.requireNonNull(
child.getValue().getOwningCommand()) child.getValue().getOwningCommand())
.getArguments()), .getArguments(), child),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList())); .collect(Collectors.toList()));
} else { } else {
/*
throw new NoSuchCommandException(commandContext.getSender(), throw new NoSuchCommandException(commandContext.getSender(),
this.getChain(root) this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
.collect(Collectors.toList()), .collect(Collectors.toList()),
""); "");*/
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(parsedArguments, root),
commandContext.getSender(), this.getChain(root)
.stream()
.map(Node::getValue)
.collect(Collectors.toList()));
} }
} }
final ArgumentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue); final ArgumentParseResult<?> result = child.getValue().getParser().parse(commandContext, commandQueue);
@ -227,9 +248,7 @@ public final class CommandTree<C, M extends CommandMeta> {
} else { } else {
/* Too many arguments. We have a unique path, so we can send the entire context */ /* Too many arguments. We have a unique path, so we can send the entire context */
throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter() throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter()
.apply(Objects.requireNonNull(child.getValue() .apply(parsedArguments, child),
.getOwningCommand())
.getArguments()),
commandContext.getSender(), this.getChain(root) commandContext.getSender(), this.getChain(root)
.stream() .stream()
.map(Node::getValue) .map(Node::getValue)
@ -237,7 +256,8 @@ public final class CommandTree<C, M extends CommandMeta> {
Collectors.toList())); Collectors.toList()));
} }
} else { } else {
return this.parseCommand(commandContext, commandQueue, child); parsedArguments.add(child.getValue());
return this.parseCommand(parsedArguments, commandContext, commandQueue, child);
} }
} else if (result.getFailure().isPresent()) { } else if (result.getFailure().isPresent()) {
throw new ArgumentParseException(result.getFailure().get(), commandContext.getSender(), throw new ArgumentParseException(result.getFailure().get(), commandContext.getSender(),

View file

@ -23,9 +23,10 @@
// //
package com.intellectualsites.commands.arguments; package com.intellectualsites.commands.arguments;
import com.intellectualsites.commands.CommandTree;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.function.Function;
/** /**
* Utility that formats chains of {@link CommandArgument command arguments} into syntax strings * Utility that formats chains of {@link CommandArgument command arguments} into syntax strings
@ -33,10 +34,17 @@ import java.util.function.Function;
* @param <C> Command sender type * @param <C> Command sender type
*/ */
@FunctionalInterface @FunctionalInterface
public interface CommandSyntaxFormatter<C> extends Function<List<CommandArgument<C, ?>>, String> { public interface CommandSyntaxFormatter<C> {
@Override /**
* Format the command arguments into a syntax string
*
* @param commandArguments Command arguments
* @param node Trailing node
* @return Syntax string
*/
@Nonnull @Nonnull
String apply(@Nonnull List<CommandArgument<C, ?>> commandArguments); String apply(@Nonnull List<CommandArgument<C, ?>> commandArguments,
@Nonnull CommandTree.Node<CommandArgument<C, ?>> node);
} }

View file

@ -23,6 +23,8 @@
// //
package com.intellectualsites.commands.arguments; package com.intellectualsites.commands.arguments;
import com.intellectualsites.commands.CommandTree;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -41,7 +43,8 @@ public class StandardCommandSyntaxFormatter<C> implements CommandSyntaxFormatter
@Nonnull @Nonnull
@Override @Override
public final String apply(@Nonnull final List<CommandArgument<C, ?>> commandArguments) { public final String apply(@Nonnull final List<CommandArgument<C, ?>> commandArguments,
@Nonnull final CommandTree.Node<CommandArgument<C, ?>> node) {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
final Iterator<CommandArgument<C, ?>> iterator = commandArguments.iterator(); final Iterator<CommandArgument<C, ?>> iterator = commandArguments.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -59,6 +62,27 @@ public class StandardCommandSyntaxFormatter<C> implements CommandSyntaxFormatter
stringBuilder.append(" "); stringBuilder.append(" ");
} }
} }
CommandTree.Node<CommandArgument<C, ?>> tail = node;
while (tail != null && !tail.isLeaf()) {
if (tail.getChildren().size() > 1) {
stringBuilder.append(" ");
final Iterator<CommandTree.Node<CommandArgument<C, ?>>> childIterator = tail.getChildren().iterator();
while (childIterator.hasNext()) {
final CommandTree.Node<CommandArgument<C, ?>> child = childIterator.next();
stringBuilder.append(child.getValue().getName());
if (childIterator.hasNext()) {
stringBuilder.append("|");
}
}
break;
}
final CommandArgument<C, ?> argument = tail.getChildren().get(0).getValue();
stringBuilder.append(" ")
.append(argument.isRequired() ? '<' : '[')
.append(argument.getName())
.append(argument.isRequired() ? '>' : ']');
tail = tail.getChildren().get(0);
}
return stringBuilder.toString(); return stringBuilder.toString();
} }

View file

@ -66,14 +66,4 @@ public class InvalidSyntaxException extends CommandParseException {
return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax); return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax);
} }
@Override
public final synchronized Throwable fillInStackTrace() {
return this;
}
@Override
public final synchronized Throwable initCause(final Throwable cause) {
return this;
}
} }

View file

@ -114,6 +114,12 @@ class CommandTreeTest {
manager.executeCommand(new TestCommandSender(), "default 5").join(); manager.executeCommand(new TestCommandSender(), "default 5").join();
} }
@Test
void invalidCommand() {
Assertions.assertThrows(CompletionException.class, () -> manager
.executeCommand(new TestCommandSender(), "invalid test").join());
}
public static final class SpecificCommandSender extends TestCommandSender { public static final class SpecificCommandSender extends TestCommandSender {
} }

View file

@ -25,16 +25,28 @@ package com.intellectualsites.commands;
import com.intellectualsites.commands.arguments.CommandArgument; import com.intellectualsites.commands.arguments.CommandArgument;
import com.intellectualsites.commands.arguments.StaticArgument; import com.intellectualsites.commands.arguments.StaticArgument;
import com.intellectualsites.commands.exceptions.ArgumentParseException;
import com.intellectualsites.commands.exceptions.InvalidCommandSenderException;
import com.intellectualsites.commands.exceptions.InvalidSyntaxException;
import com.intellectualsites.commands.exceptions.NoPermissionException;
import com.intellectualsites.commands.exceptions.NoSuchCommandException;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
final class BukkitCommand<C> extends org.bukkit.command.Command implements PluginIdentifiableCommand { final class BukkitCommand<C> extends org.bukkit.command.Command implements PluginIdentifiableCommand {
private static final String MESSAGE_NO_PERMS = ChatColor.RED
+ "I'm sorry, but you do not have permission to perform this command. "
+ "Please contact the server administrators if you believe that this is in error.";
private static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command. Type \"/help\" for help.";
private final CommandArgument<C, ?> command; private final CommandArgument<C, ?> command;
private final BukkitCommandManager<C> bukkitCommandManager; private final BukkitCommandManager<C> bukkitCommandManager;
private final com.intellectualsites.commands.Command<C, BukkitCommandMeta> cloudCommand; private final com.intellectualsites.commands.Command<C, BukkitCommandMeta> cloudCommand;
@ -63,13 +75,24 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
builder.toString()) builder.toString())
.whenComplete(((commandResult, throwable) -> { .whenComplete(((commandResult, throwable) -> {
if (throwable != null) { if (throwable != null) {
commandSender.sendMessage(ChatColor.RED + throwable.getMessage()); if (throwable instanceof InvalidSyntaxException) {
commandSender.sendMessage(ChatColor.RED + throwable.getCause().getMessage()); commandSender.sendMessage(ChatColor.RED + "Invalid Command Syntax. "
throwable.printStackTrace(); + "Correct command syntax is: "
throwable.getCause().printStackTrace(); + ChatColor.GRAY + "/"
} else { + ((InvalidSyntaxException) throwable).getCorrectSyntax());
// Do something... } else if (throwable instanceof InvalidCommandSenderException) {
commandSender.sendMessage("All good!"); commandSender.sendMessage(ChatColor.RED + throwable.getMessage());
} else if (throwable instanceof NoPermissionException) {
commandSender.sendMessage(MESSAGE_NO_PERMS);
} else if (throwable instanceof NoSuchCommandException) {
commandSender.sendMessage(MESSAGE_UNKNOWN_COMMAND);
} else if (throwable instanceof ArgumentParseException) {
commandSender.sendMessage(ChatColor.RED + "Invalid Command Argument: "
+ ChatColor.GRAY + throwable.getCause().getMessage());
} else {
commandSender.sendMessage(throwable.getMessage());
throwable.printStackTrace();
}
} }
})); }));
return true; return true;
@ -101,4 +124,25 @@ final class BukkitCommand<C> extends org.bukkit.command.Command implements Plugi
return this.cloudCommand.getCommandPermission(); return this.cloudCommand.getCommandPermission();
} }
@Nullable
private <E extends Throwable> E captureException(@Nonnull final Class<E> clazz, @Nullable final Throwable throwable) {
if (throwable == null) {
return null;
}
if (clazz.equals(throwable.getClass())) {
//noinspection unchecked
return (E) throwable;
}
return captureException(clazz, throwable.getCause());
}
private <E extends Throwable> boolean handleException(@Nullable final E throwable,
@Nonnull final Consumer<E> consumer) {
if (throwable == null) {
return false;
}
consumer.accept(throwable);
return true;
}
} }