Generate command syntax and store context values

This commit is contained in:
Alexander Söderberg 2020-09-05 22:50:20 +02:00
parent d4143246b7
commit 10aba61110
No known key found for this signature in database
GPG key ID: C0207FF7EA146678
7 changed files with 267 additions and 11 deletions

View file

@ -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<JLineCommandSender> 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;
}
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.");
}

View file

@ -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<C extends CommandSender> {
private final CommandExecutionCoordinator<C> commandExecutionCoordinator;
private final CommandTree<C> commandTree;
private CommandSyntaxFormatter commandSyntaxFormatter = CommandSyntaxFormatter.STANDARD_COMMAND_SYNTAX_FORMATTER;
protected CommandManager(@Nonnull final Function<CommandTree<C>, CommandExecutionCoordinator<C>> 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<C extends CommandSender> {
* Register a new command
*
* @param command Command to register
* @return The command manager instance
*/
public void registerCommand(@Nonnull final Command command) {
public CommandManager<C> 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;
}
}

View file

@ -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<C extends CommandSender> {
private final Node<CommandComponent<C, ?>> internalTree = new Node<>(null);
private final CommandManager<C> commandManager;
private final CommandRegistrationHandler commandRegistrationHandler;
private CommandTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
private CommandTree(@Nonnull final CommandManager<C> 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 <C> Command sender type
* @return New command tree
*/
@Nonnull
public static <C extends CommandSender> CommandTree<C> newTree(@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
return new CommandTree<>(commandRegistrationHandler);
public static <C extends CommandSender> CommandTree<C> newTree(@Nonnull final CommandManager<C> commandManager,
@Nonnull final CommandRegistrationHandler commandRegistrationHandler) {
return new CommandTree<>(commandManager, commandRegistrationHandler);
}
public Optional<Command<C>> parse(@Nonnull final CommandContext<C> commandContext, @Nonnull final Queue<String> args) throws NoSuchCommandException {
@ -76,11 +81,28 @@ public class CommandTree<C extends CommandSender> {
// The value has to be a variable
final Node<CommandComponent<C, ?>> 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()) {
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<C extends CommandSender> {
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) {
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<CommandComponent<C, ?>> components = this.getChain(root).stream().map(Node::getValue).collect(Collectors.toList());

View file

@ -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<List<CommandComponent<?, ?>>, String> {
CommandSyntaxFormatter STANDARD_COMMAND_SYNTAX_FORMATTER = new StandardCommandSyntaxFormatter();
@Override @Nonnull String apply(@Nonnull List<CommandComponent<?, ?>> commandComponents);
}

View file

@ -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<CommandComponent<?, ?>> commandComponents) {
final StringBuilder stringBuilder = new StringBuilder();
final Iterator<CommandComponent<?, ?>> 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();
}
}

View file

@ -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<C extends CommandSender> {
private final Map<String, Object> internalStorage = new HashMap<>();
private final C commandSender;
public CommandContext(@Nonnull final C commandSender) {
@ -40,8 +44,38 @@ public class CommandContext<C extends CommandSender> {
*
* @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 <T> Value type
*/
public <T> 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 <T> Value type
* @return Value
*/
public <T> Optional<T> 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();
}
}
}

View file

@ -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<CommandComponent<?, ?>> 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);
}
}