From 10aba61110f2923947ad58ceadc64d69da3ee0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Sat, 5 Sep 2020 22:50:20 +0200 Subject: [PATCH] Generate command syntax and store context values --- .../commands/jline/JLineCommandManager.java | 31 +++++++++- .../commands/CommandManager.java | 27 ++++++++- .../commands/CommandTree.java | 40 +++++++++++-- .../components/CommandSyntaxFormatter.java | 36 ++++++++++++ .../StandardCommandSyntaxFormatter.java | 58 +++++++++++++++++++ .../commands/context/CommandContext.java | 36 +++++++++++- .../exceptions/InvalidSyntaxException.java | 50 ++++++++++++++++ 7 files changed, 267 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/intellectualsites/commands/components/CommandSyntaxFormatter.java create mode 100644 src/main/java/com/intellectualsites/commands/components/StandardCommandSyntaxFormatter.java create mode 100644 src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java diff --git a/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java index 006aaa7f..99c6f43b 100644 --- a/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java +++ b/commands-jline/src/main/java/com/intellectualsites/commands/jline/JLineCommandManager.java @@ -26,8 +26,12 @@ package com.intellectualsites.commands.jline; import com.intellectualsites.commands.Command; import com.intellectualsites.commands.CommandManager; import com.intellectualsites.commands.CommandTree; +import com.intellectualsites.commands.components.CommandComponent; +import com.intellectualsites.commands.exceptions.InvalidSyntaxException; +import com.intellectualsites.commands.exceptions.NoSuchCommandException; import com.intellectualsites.commands.execution.CommandExecutionCoordinator; import com.intellectualsites.commands.internal.CommandRegistrationHandler; +import com.intellectualsites.commands.parser.ComponentParseResult; import org.jline.reader.*; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; @@ -49,13 +53,36 @@ public class JLineCommandManager extends CommandManager impl .completer(jLineCommandManager).terminal(terminal).appName("Test").build(); boolean[] shouldStop = new boolean[] { false }; jLineCommandManager.registerCommand(Command.newBuilder("stop").withHandler(commandContext -> - shouldStop[0] = true).build()); + shouldStop[0] = true).build()).registerCommand(Command.newBuilder("echo") + .withComponent(CommandComponent.ofType(String.class).named("string").asRequired() + .withParser(((commandContext, inputQueue) -> { + final StringBuilder stringBuilder = new StringBuilder(); + while (!inputQueue.isEmpty()) { + stringBuilder.append(inputQueue.remove()); + if (!inputQueue.isEmpty()) { + stringBuilder.append(" "); + } + } + return ComponentParseResult.success(stringBuilder.toString()); + })).build()) + .withHandler(commandContext -> commandContext.get("string").ifPresent(System.out::println)).build()); while (!shouldStop[0]) { final String line = lineReader.readLine(); if (line == null || line.isEmpty() || !line.startsWith("/")) { continue; } - jLineCommandManager.executeCommand(new JLineCommandSender(), line.substring(1)).join(); + try { + jLineCommandManager.executeCommand(new JLineCommandSender(), line.substring(1)).join(); + } catch (RuntimeException runtimeException) { + if (runtimeException.getCause() instanceof NoSuchCommandException) { + System.out.println("No such command"); + } else if (runtimeException.getCause() instanceof InvalidSyntaxException) { + System.out.println(runtimeException.getCause().getMessage()); + } else { + System.out.printf("Something went wrong: %s\n", runtimeException.getCause().getMessage()); + runtimeException.printStackTrace(); + } + } if (shouldStop[0]) { System.out.println("Stopping."); } diff --git a/src/main/java/com/intellectualsites/commands/CommandManager.java b/src/main/java/com/intellectualsites/commands/CommandManager.java index b008be1a..510b42af 100644 --- a/src/main/java/com/intellectualsites/commands/CommandManager.java +++ b/src/main/java/com/intellectualsites/commands/CommandManager.java @@ -23,6 +23,7 @@ // package com.intellectualsites.commands; +import com.intellectualsites.commands.components.CommandSyntaxFormatter; import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.execution.CommandExecutionCoordinator; import com.intellectualsites.commands.execution.CommandResult; @@ -46,9 +47,11 @@ public abstract class CommandManager { private final CommandExecutionCoordinator commandExecutionCoordinator; private final CommandTree commandTree; + private CommandSyntaxFormatter commandSyntaxFormatter = CommandSyntaxFormatter.STANDARD_COMMAND_SYNTAX_FORMATTER; + protected CommandManager(@Nonnull final Function, CommandExecutionCoordinator> commandExecutionCoordinator, @Nonnull final CommandRegistrationHandler commandRegistrationHandler) { - this.commandTree = CommandTree.newTree(commandRegistrationHandler); + this.commandTree = CommandTree.newTree(this, commandRegistrationHandler); this.commandExecutionCoordinator = commandExecutionCoordinator.apply(commandTree); } @@ -66,9 +69,29 @@ public abstract class CommandManager { * Register a new command * * @param command Command to register + * @return The command manager instance */ - public void registerCommand(@Nonnull final Command command) { + public CommandManager registerCommand(@Nonnull final Command command) { this.commandTree.insertCommand(command); + return this; + } + + /** + * Get the command syntax formatter + * + * @return Command syntax formatter + */ + @Nonnull public CommandSyntaxFormatter getCommandSyntaxFormatter() { + return this.commandSyntaxFormatter; + } + + /** + * Set the command syntax formatter + * + * @param commandSyntaxFormatter New formatter + */ + public void setCommandSyntaxFormatter(@Nonnull final CommandSyntaxFormatter commandSyntaxFormatter) { + this.commandSyntaxFormatter = commandSyntaxFormatter; } } diff --git a/src/main/java/com/intellectualsites/commands/CommandTree.java b/src/main/java/com/intellectualsites/commands/CommandTree.java index 36e43158..5909e6a0 100644 --- a/src/main/java/com/intellectualsites/commands/CommandTree.java +++ b/src/main/java/com/intellectualsites/commands/CommandTree.java @@ -28,6 +28,7 @@ import com.google.common.collect.Lists; import com.intellectualsites.commands.components.CommandComponent; import com.intellectualsites.commands.components.StaticComponent; import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.commands.exceptions.InvalidSyntaxException; import com.intellectualsites.commands.exceptions.NoSuchCommandException; import com.intellectualsites.commands.internal.CommandRegistrationHandler; import com.intellectualsites.commands.parser.ComponentParseResult; @@ -46,22 +47,26 @@ import java.util.stream.Collectors; public class CommandTree { private final Node> internalTree = new Node<>(null); + private final CommandManager commandManager; private final CommandRegistrationHandler commandRegistrationHandler; - private CommandTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { + private CommandTree(@Nonnull final CommandManager commandManager, @Nonnull final CommandRegistrationHandler commandRegistrationHandler) { + this.commandManager = commandManager; this.commandRegistrationHandler = commandRegistrationHandler; } /** * Create a new command tree instance * + * @param commandManager Command manager * @param commandRegistrationHandler Command registration handler * @param Command sender type * @return New command tree */ @Nonnull - public static CommandTree newTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) { - return new CommandTree<>(commandRegistrationHandler); + public static CommandTree newTree(@Nonnull final CommandManager commandManager, + @Nonnull final CommandRegistrationHandler commandRegistrationHandler) { + return new CommandTree<>(commandManager, commandRegistrationHandler); } public Optional> parse(@Nonnull final CommandContext commandContext, @Nonnull final Queue args) throws NoSuchCommandException { @@ -76,11 +81,28 @@ public class CommandTree { // The value has to be a variable final Node> child = children.get(0); if (child.getValue() != null) { + if (commandQueue.isEmpty()) { + if (child.isLeaf()) { + /* Not enough arguments */ + throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter().apply(Arrays.asList(child.getValue().getOwningCommand().getComponents())), + commandContext.getCommandSender(), this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList())); + } else { + throw new NoSuchCommandException(commandContext.getCommandSender(), + this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList()), + ""); + } + } final ComponentParseResult result = child.getValue().getParser().parse(commandContext, commandQueue); if (result.getParsedValue().isPresent()) { - /* TODO: Add context */ + commandContext.store(child.getValue().getName(), result.getParsedValue().get()); if (child.isLeaf()) { - return Optional.ofNullable(child.getValue().getOwningCommand()); + if (commandQueue.isEmpty()) { + return Optional.ofNullable(child.getValue().getOwningCommand()); + } else { + /* Too many arguments. We have a unique path, so we can send the entire context */ + throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter().apply(Arrays.asList(child.getValue().getOwningCommand().getComponents())), + commandContext.getCommandSender(), this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList())); + } } else { return this.parseCommand(commandContext, commandQueue, child); } @@ -94,7 +116,13 @@ public class CommandTree { if (children.isEmpty()) { /* We are at the bottom. Check if there's a command attached, in which case we're done */ if (root.getValue() != null && root.getValue().getOwningCommand() != null) { - return Optional.of(root.getValue().getOwningCommand()); + if (commandQueue.isEmpty()) { + return Optional.of(root.getValue().getOwningCommand()); + } else { + /* Too many arguments. We have a unique path, so we can send the entire context */ + throw new InvalidSyntaxException(this.commandManager.getCommandSyntaxFormatter().apply(Arrays.asList(root.getValue().getOwningCommand().getComponents())), + commandContext.getCommandSender(), this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList())); + } } else { /* TODO: Indicate that we could not resolve the command here */ final List> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList()); diff --git a/src/main/java/com/intellectualsites/commands/components/CommandSyntaxFormatter.java b/src/main/java/com/intellectualsites/commands/components/CommandSyntaxFormatter.java new file mode 100644 index 00000000..39ff537a --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/components/CommandSyntaxFormatter.java @@ -0,0 +1,36 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 com.intellectualsites.commands.components; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.function.Function; + +@FunctionalInterface public interface CommandSyntaxFormatter extends Function>, String> { + + CommandSyntaxFormatter STANDARD_COMMAND_SYNTAX_FORMATTER = new StandardCommandSyntaxFormatter(); + + @Override @Nonnull String apply(@Nonnull List> commandComponents); + +} diff --git a/src/main/java/com/intellectualsites/commands/components/StandardCommandSyntaxFormatter.java b/src/main/java/com/intellectualsites/commands/components/StandardCommandSyntaxFormatter.java new file mode 100644 index 00000000..39c6ab22 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/components/StandardCommandSyntaxFormatter.java @@ -0,0 +1,58 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 com.intellectualsites.commands.components; + +import javax.annotation.Nonnull; +import java.util.Iterator; +import java.util.List; + +public class StandardCommandSyntaxFormatter implements CommandSyntaxFormatter { + + public StandardCommandSyntaxFormatter() { + } + + @Nonnull + @Override + public String apply(@Nonnull final List> commandComponents) { + final StringBuilder stringBuilder = new StringBuilder(); + final Iterator> iterator = commandComponents.iterator(); + while (iterator.hasNext()) { + final CommandComponent commandComponent = iterator.next(); + if (commandComponent instanceof StaticComponent) { + stringBuilder.append(commandComponent.getName()); + } else { + if (commandComponent.isRequired()) { + stringBuilder.append("<").append(commandComponent.getName()).append(">"); + } else { + stringBuilder.append("[").append(commandComponent.getName()).append("]"); + } + } + if (iterator.hasNext()) { + stringBuilder.append(" "); + } + } + return stringBuilder.toString(); + } + +} diff --git a/src/main/java/com/intellectualsites/commands/context/CommandContext.java b/src/main/java/com/intellectualsites/commands/context/CommandContext.java index 02c2ca00..ef07fed2 100644 --- a/src/main/java/com/intellectualsites/commands/context/CommandContext.java +++ b/src/main/java/com/intellectualsites/commands/context/CommandContext.java @@ -26,9 +26,13 @@ package com.intellectualsites.commands.context; import com.intellectualsites.commands.sender.CommandSender; import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; public class CommandContext { + private final Map internalStorage = new HashMap<>(); private final C commandSender; public CommandContext(@Nonnull final C commandSender) { @@ -40,8 +44,38 @@ public class CommandContext { * * @return Command sender */ - @Nonnull public C getCommandSender() { + @Nonnull + public C getCommandSender() { return this.commandSender; } + /** + * Store a value in the context map + * + * @param key Key + * @param value Value + * @param Value type + */ + public void store(@Nonnull final String key, @Nonnull final T value) { + this.internalStorage.put(key, value); + } + + /** + * Get a value from its key + * + * @param key Key + * @param Value type + * @return Value + */ + public Optional get(@Nonnull final String key) { + final Object value = this.internalStorage.get(key); + if (value != null) { + @SuppressWarnings("ALL") + final T castedValue = (T) value; + return Optional.of(castedValue); + } else { + return Optional.empty(); + } + } + } diff --git a/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java b/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java new file mode 100644 index 00000000..0a7eb8f2 --- /dev/null +++ b/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java @@ -0,0 +1,50 @@ +// +// MIT License +// +// Copyright (c) 2020 IntellectualSites +// +// 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 com.intellectualsites.commands.exceptions; + +import com.intellectualsites.commands.components.CommandComponent; +import com.intellectualsites.commands.sender.CommandSender; + +import javax.annotation.Nonnull; +import java.util.List; + +public class InvalidSyntaxException extends CommandParseException { + + private final String correctSyntax; + + public InvalidSyntaxException(@Nonnull final String correctSyntax, @Nonnull final CommandSender commandSender, @Nonnull final List> currentChain) { + super(commandSender, currentChain); + this.correctSyntax = correctSyntax; + } + + @Nonnull public String getCorrectSyntax() { + return this.correctSyntax; + } + + @Override + public String getMessage() { + return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax); + } + +}