diff --git a/src/main/java/cpw/mods/fml/common/event/FMLServerStartingEvent.java b/src/main/java/cpw/mods/fml/common/event/FMLServerStartingEvent.java index 037c44d..ecdd612 100644 --- a/src/main/java/cpw/mods/fml/common/event/FMLServerStartingEvent.java +++ b/src/main/java/cpw/mods/fml/common/event/FMLServerStartingEvent.java @@ -17,6 +17,10 @@ import net.minecraft.server.MinecraftServer; import cpw.mods.fml.common.LoaderState.ModState; import org.ultramine.commands.CommandRegistry; +import org.ultramine.commands.IExtendedCommand; +import org.ultramine.commands.syntax.ArgumentsPatternParser; +import org.ultramine.commands.syntax.IArgumentCompletionHandler; +import org.ultramine.commands.syntax.IArgumentValidationHandler; public class FMLServerStartingEvent extends FMLStateEvent { @@ -45,8 +49,40 @@ ch.registerCommand(command); } - public CommandRegistry getCommandRegistry() + /* ========================================= ULTRAMINE START ======================================*/ + + public void registerCommand(IExtendedCommand command) + { + getCommandRegistry().registerCommand(command); + } + + public void registerCommands(Class holder) + { + getCommandRegistry().registerCommands(holder); + } + + public void registerArgumentHandler(String argumentType, IArgumentCompletionHandler handler) + { + getArgumentsParser().registerHandler(argumentType, handler); + } + + public void registerArgumentHandler(String argumentType, IArgumentValidationHandler handler) + { + getArgumentsParser().registerHandler(argumentType, handler); + } + + public void registerArgumentHandlers(Class holder) + { + getArgumentsParser().registerHandlers(holder); + } + + private CommandRegistry getCommandRegistry() { return ((CommandHandler) getServer().getCommandManager()).getRegistry(); } + + private ArgumentsPatternParser getArgumentsParser() + { + return getCommandRegistry().getArgumentsParser(); + } } \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/CommandContext.java b/src/main/java/org/ultramine/commands/CommandContext.java index bf04cde..8e0f8cd 100644 --- a/src/main/java/org/ultramine/commands/CommandContext.java +++ b/src/main/java/org/ultramine/commands/CommandContext.java @@ -259,7 +259,7 @@ context = new CommandContext(command, sender, args); } - public Builder setArgumentsNames(List names) + public Builder resolveArguments(List names) { context.lastArgumentNum = names.size(); Map nameCount = new HashMap(); @@ -267,11 +267,8 @@ { String name = names.get(i); - if (name.equals("action")) - { - context.actionName = context.actionName.isEmpty() ? context.args[i] : context.actionName + "." + context.args[i]; + if (name == null || name.isEmpty()) continue; - } if (context.argumentMap.containsKey(name)) { @@ -285,13 +282,9 @@ return this; } - public String getActionName() + public Builder setAction(String actionName, ICommandHandler actionHandler) { - return context.getAction(); - } - - public Builder setActionHandler(ICommandHandler actionHandler) - { + context.actionName = actionName; context.actionHandler = actionHandler; return this; } diff --git a/src/main/java/org/ultramine/commands/CommandRegistry.java b/src/main/java/org/ultramine/commands/CommandRegistry.java index e475bce..1a2af02 100644 --- a/src/main/java/org/ultramine/commands/CommandRegistry.java +++ b/src/main/java/org/ultramine/commands/CommandRegistry.java @@ -3,9 +3,7 @@ import net.minecraft.command.CommandBase; import net.minecraft.command.ICommand; import net.minecraft.command.ICommandSender; -import net.minecraftforge.common.MinecraftForge; -import org.ultramine.commands.completion.CompletionStringParser; -import org.ultramine.commands.completion.RegisterCompletersEvent; +import org.ultramine.commands.syntax.ArgumentsPatternParser; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -13,17 +11,15 @@ public class CommandRegistry { - private Map commandMap; - private SortedSet registeredCommands; - private CompletionStringParser completionStringParser; + private final Map commandMap; + private final SortedSet registeredCommands; + private final ArgumentsPatternParser argumentsPatternParser; public CommandRegistry() { commandMap = new HashMap(); registeredCommands = new TreeSet(); - completionStringParser = new CompletionStringParser(); - - MinecraftForge.EVENT_BUS.post(new RegisterCompletersEvent(completionStringParser)); + argumentsPatternParser = new ArgumentsPatternParser(); } public IExtendedCommand registerCommand(IExtendedCommand command) @@ -66,7 +62,7 @@ .setUsableFromServer(data.isUsableFromServer()); for (String completion : data.completions()) - builder.addCompletionHandlers(completionStringParser.parse(completion)); + builder.addArgumentsPattern(argumentsPatternParser.parse(completion)); builders.add(builder); } @@ -130,8 +126,8 @@ return result; } - public CompletionStringParser getCompletionStringParser() + public ArgumentsPatternParser getArgumentsParser() { - return completionStringParser; + return argumentsPatternParser; } } diff --git a/src/main/java/org/ultramine/commands/HandlerBasedCommand.java b/src/main/java/org/ultramine/commands/HandlerBasedCommand.java index c54f51d..7dba9fb 100644 --- a/src/main/java/org/ultramine/commands/HandlerBasedCommand.java +++ b/src/main/java/org/ultramine/commands/HandlerBasedCommand.java @@ -3,28 +3,26 @@ import net.minecraft.command.ICommandSender; import net.minecraft.command.WrongUsageException; import org.apache.commons.lang3.ArrayUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.ultramine.commands.completion.CommandCompletionHandler; +import org.ultramine.commands.syntax.ArgumentsPattern; import org.ultramine.server.PermissionHandler; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; public class HandlerBasedCommand implements IExtendedCommand { - private static final Logger logger = LogManager.getLogger(); - private String name; private String usage; private String group; private String description; private ICommandHandler handler; - private List completionHandlers; + private List argumentsPatterns; private Map actionHandlers; private List aliases; @@ -38,7 +36,7 @@ this.handler = handler; this.usage = "command." + name + ".usage"; this.description = "command." + name + ".description"; - this.completionHandlers = new ArrayList(); + this.argumentsPatterns = new ArrayList(); this.actionHandlers = new HashMap(); } @@ -77,15 +75,17 @@ { CommandContext.Builder builder = new CommandContext.Builder(this, var1, var2); - if (completionHandlers.size() > 0) + if (argumentsPatterns.size() > 0) { - CommandCompletionHandler completionHandler = findCompletionHandler(var2); - if (completionHandler == null) + ArgumentsPattern pattern = findArgumentsPattern(var2); + if (pattern == null) throw new WrongUsageException(usage); - builder.setArgumentsNames(completionHandler.getNames()); - if (!builder.getActionName().isEmpty()) - builder.setActionHandler(actionHandlers.get(builder.getActionName())); + builder.resolveArguments(pattern.getArgumentsNames()); + + String actionName = pattern.resolveActionName(var2); + if (!actionName.isEmpty()) + builder.setAction(actionName, actionHandlers.get(actionName)); } handler.processCommand(builder.build()); @@ -100,19 +100,52 @@ @Override public List addTabCompletionOptions(ICommandSender var1, String[] var2) { - if (completionHandlers.size() == 0) + if (argumentsPatterns.size() == 0) return null; List result = null; + Set dupChecker = null; + ArgumentsPattern.MatchResult minimumMatch = ArgumentsPattern.MatchResult.POSSIBLY; String[] tail = ArrayUtils.remove(var2, var2.length - 1); - for (CommandCompletionHandler completionHandler : completionHandlers) + for (ArgumentsPattern argumentsPattern : argumentsPatterns) { - if (completionHandler.match(false, tail)) + ArgumentsPattern.MatchResult currentMatch = argumentsPattern.partialMatch(tail); + + if (currentMatch.isGreaterThan(minimumMatch)) + { + minimumMatch = currentMatch; + result = null; + dupChecker = null; + } + + if (!currentMatch.isLessThan(minimumMatch)) + { + List options = argumentsPattern.getCompletionOptions(var2); + if (options == null || options.size() == 0) + continue; + if (result == null) - result = completionHandler.getCompletionOptions(var2); + { + result = options; + } else - result.addAll(completionHandler.getCompletionOptions(var2)); + { + if (dupChecker == null) + { + dupChecker = new HashSet(result); + } + + for (String option: options) + { + if (!dupChecker.contains(option)) + { + result.add(option); + dupChecker.add(option); + } + } + } + } } return result; } @@ -120,19 +153,19 @@ @Override public boolean isUsernameIndex(String[] var1, int var2) { - CommandCompletionHandler completionHandler = findCompletionHandler(var1); - return completionHandler != null && completionHandler.isUsernameIndex(var2); + ArgumentsPattern argumentsPattern = findArgumentsPattern(var1); + return argumentsPattern != null && argumentsPattern.isUsernameIndex(var2); } - private CommandCompletionHandler findCompletionHandler(String[] args) + private ArgumentsPattern findArgumentsPattern(String[] args) { - if (completionHandlers.size() == 0) + if (argumentsPatterns.size() == 0) return null; - for (CommandCompletionHandler completionHandler : completionHandlers) + for (ArgumentsPattern argumentsPattern : argumentsPatterns) { - if (completionHandler.match(true, args)) - return completionHandler; + if (argumentsPattern.match(args)) + return argumentsPattern; } return null; } @@ -171,9 +204,9 @@ return this; } - public Builder addCompletionHandlers(CommandCompletionHandler completionHandler) + public Builder addArgumentsPattern(ArgumentsPattern argumentsPattern) { - command.completionHandlers.add(completionHandler); + command.argumentsPatterns.add(argumentsPattern); return this; } diff --git a/src/main/java/org/ultramine/commands/completion/ArgumentCompleter.java b/src/main/java/org/ultramine/commands/completion/ArgumentCompleter.java deleted file mode 100644 index 290a1ce..0000000 --- a/src/main/java/org/ultramine/commands/completion/ArgumentCompleter.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.ultramine.commands.completion; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface ArgumentCompleter -{ - String value(); - boolean isUsername() default false; -} \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java b/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java deleted file mode 100644 index 8139bd6..0000000 --- a/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.ultramine.commands.completion; - -import net.minecraft.command.CommandBase; -import java.util.ArrayList; -import java.util.List; - -public class CommandCompletionHandler -{ - private static ICompleter IGNORED = new ICompleter.ArgumentCompleter(null, null); - - private List completers = new ArrayList(); - private List names = new ArrayList(); - private int usernameArgIndex = -1; - private boolean isInfinite = false; - - public List getCompletionOptions(String[] args) - { - if (completers.size() == 0) - return null; - - ICompleter completer; - if (completers.size() < args.length) - completer = isInfinite ? completers.get(completers.size() - 1) : IGNORED; - else - completer = completers.get(args.length - 1); - - return completer.getCompletionOptions(args); - } - - public boolean isUsernameIndex(int checkArgNum) - { - return checkArgNum == usernameArgIndex; - } - - public boolean match(boolean strict, String[] args) - { - for (int i = 0; i < args.length; i++) - { - if (i == completers.size()) - return isInfinite; - - if (!completers.get(i).match(args[i])) - return false; - } - return !strict || args.length == completers.size(); - } - - public List getNames() - { - return names; - } - - public void addNextArgument(String name, IArgumentCompletionHandler handler, String[] params) - { - if (usernameArgIndex == -1 && handler.isUsername()) - usernameArgIndex = completers.size(); - - completers.add(new ICompleter.ArgumentCompleter(handler, params)); - names.add(name); - } - - public void addNextActionArgument(String... actions) - { - completers.add(new ICompleter.ActionCompleter(actions)); - names.add("action"); - } - - public void ignoreNextArgument(String name) - { - completers.add(IGNORED); - names.add(name); - } - - public void makeInfinite() - { - isInfinite = true; - } - - private static interface ICompleter - { - List getCompletionOptions(String[] args); - boolean match(String val); - - static class ArgumentCompleter implements ICompleter - { - private IArgumentCompletionHandler handler; - private String[] params; - - private ArgumentCompleter(IArgumentCompletionHandler handler, String[] params) - { - this.handler = handler; - this.params = params; - } - - @Override - public List getCompletionOptions(String[] args) - { - if (handler == null || params == null) - return null; - - String[] params = new String[this.params.length]; - for (int i = 0; i < this.params.length; i++) - { - String param = this.params[i]; - if (param.startsWith("&")) - { - try - { - int argNum = Integer.valueOf(param.substring(1)); - params[i] = args[argNum]; - } - catch (Exception ignored) - { - params[i] = param; - } - } - else - params[i] = param; - } - - return handler.handleCompletion(args[args.length - 1], params); - } - - @Override - public boolean match(String val) - { - return true; - } - } - - static class ActionCompleter implements ICompleter - { - private String[] actions; - - private ActionCompleter(String[] actions) - { - this.actions = actions; - } - - @Override - public List getCompletionOptions(String[] args) - { - List result = new ArrayList(); - String val = args[args.length - 1]; - for (String action : actions) - { - if (CommandBase.doesStringStartWith(val, action)) - result.add(action); - } - return result; - } - - @Override - public boolean match(String val) - { - for (String action : actions) - { - if (action.equalsIgnoreCase(val)) - return true; - } - return false; - } - } - } -} diff --git a/src/main/java/org/ultramine/commands/completion/CompletionStringParser.java b/src/main/java/org/ultramine/commands/completion/CompletionStringParser.java deleted file mode 100644 index d43dae1..0000000 --- a/src/main/java/org/ultramine/commands/completion/CompletionStringParser.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.ultramine.commands.completion; - -import org.apache.commons.lang3.StringUtils; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class CompletionStringParser -{ - private static final Pattern argumentPattern = Pattern.compile("([\\[<])\\s*(([^<>%\\[\\]\\s]*)\\s*([^<>%\\[\\]]*))(|%\\s*([^<>%\\[\\]\\s]*)\\s*)[\\]>]"); - private Map handlers = new HashMap(); - - public CommandCompletionHandler parse(String completionString) - { - CommandCompletionHandler result = new CommandCompletionHandler(); - Matcher matcher = argumentPattern.matcher(completionString); - - while (matcher.find()) - { - if (matcher.group(1).equals("[")) - { - String[] params = StringUtils.split(matcher.group(2)); - result.addNextActionArgument(params); - } - else - { - String handlerName = matcher.group(3); - String argumentName = matcher.group(6); - if (argumentName == null || argumentName.isEmpty()) - argumentName = handlerName; - - if (handlerName.isEmpty() || !handlers.containsKey(handlerName)) - { - result.ignoreNextArgument(argumentName); - } - else - { - String[] params = StringUtils.split(matcher.group(4)); - result.addNextArgument(argumentName, handlers.get(handlerName), params); - } - } - } - - if (completionString.endsWith("...")) - result.makeInfinite(); - - return result; - } - - public void registerHandler(String name, IArgumentCompletionHandler handler) - { - handlers.put(name, handler); - } - - public void registerHandlers(Class cls) - { - for (Method handler : cls.getMethods()) - { - if (handler.isAnnotationPresent(ArgumentCompleter.class) && Modifier.isStatic(handler.getModifiers())) - { - ArgumentCompleter data = handler.getAnnotation(ArgumentCompleter.class); - registerHandler(data.value(), new WrappedHandler(handler, data.isUsername())); - } - } - } - - private static class WrappedHandler implements IArgumentCompletionHandler - { - private Method method; - private boolean isUsername; - - private WrappedHandler(Method method, boolean isUsername) - { - this.method = method; - this.isUsername = isUsername; - } - - @Override - public List handleCompletion(String val, String[] args) - { - try - { - return (List) method.invoke(null, val, args); - } - catch (IllegalAccessException ignored) - { - } - catch (InvocationTargetException ignored) - { - } - - return null; - } - - @Override - public boolean isUsername() - { - return isUsername; - } - } -} diff --git a/src/main/java/org/ultramine/commands/completion/DefaultCompleters.java b/src/main/java/org/ultramine/commands/completion/DefaultCompleters.java deleted file mode 100644 index 2ee6827..0000000 --- a/src/main/java/org/ultramine/commands/completion/DefaultCompleters.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.ultramine.commands.completion; - -import net.minecraft.block.Block; -import net.minecraft.command.CommandBase; -import net.minecraft.entity.EntityList; -import net.minecraft.item.Item; -import net.minecraft.server.MinecraftServer; - -import java.util.ArrayList; -import java.util.List; - -public class DefaultCompleters -{ - @ArgumentCompleter(value = "player", isUsername = true) - public static List player(String val, String[] args) - { - return filterArray(val, MinecraftServer.getServer().getAllUsernames()); - } - - @ArgumentCompleter("item") - public static List item(String val, String[] args) - { - return filterCollection(val, Item.itemRegistry.getKeys()); - } - - @ArgumentCompleter("block") - public static List block(String val, String[] args) - { - return filterCollection(val, Block.blockRegistry.getKeys()); - } - - @ArgumentCompleter("entity") - public static List entity(String val, String[] args) - { - return filterCollection(val, EntityList.func_151515_b()); - } - - @ArgumentCompleter("list") - public static List list(String val, String[] args) - { - return filterArray(val, args); - } - - public static List filterArray(String filter, String[] strings) - { - List result = new ArrayList(); - - for (String str : strings) - { - if (CommandBase.doesStringStartWith(filter, str)) - result.add(str); - } - - return result; - } - - public static List filterCollection(String filter, Iterable iterable) - { - List result = new ArrayList(); - - for (String str : iterable) - { - if (CommandBase.doesStringStartWith(filter, str)) - result.add(str); - } - - return result; - } -} diff --git a/src/main/java/org/ultramine/commands/completion/IArgumentCompletionHandler.java b/src/main/java/org/ultramine/commands/completion/IArgumentCompletionHandler.java deleted file mode 100644 index 6e601df..0000000 --- a/src/main/java/org/ultramine/commands/completion/IArgumentCompletionHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.ultramine.commands.completion; - -import java.util.List; - -public interface IArgumentCompletionHandler -{ - List handleCompletion(String val, String[] args); - boolean isUsername(); -} \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/completion/RegisterCompletersEvent.java b/src/main/java/org/ultramine/commands/completion/RegisterCompletersEvent.java deleted file mode 100644 index ff10119..0000000 --- a/src/main/java/org/ultramine/commands/completion/RegisterCompletersEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.ultramine.commands.completion; - -import cpw.mods.fml.common.eventhandler.Event; -import org.ultramine.commands.completion.CompletionStringParser; - -public class RegisterCompletersEvent extends Event -{ - private final CompletionStringParser completionStringParser; - - public RegisterCompletersEvent(CompletionStringParser completionStringParser) - { - this.completionStringParser = completionStringParser; - } - - public CompletionStringParser getCompletionStringParser() - { - return completionStringParser; - } -} diff --git a/src/main/java/org/ultramine/commands/syntax/ArgumentCompleter.java b/src/main/java/org/ultramine/commands/syntax/ArgumentCompleter.java new file mode 100644 index 0000000..8b39205 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/ArgumentCompleter.java @@ -0,0 +1,14 @@ +package org.ultramine.commands.syntax; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ArgumentCompleter +{ + String value(); + boolean isUsername() default false; +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/syntax/ArgumentValidator.java b/src/main/java/org/ultramine/commands/syntax/ArgumentValidator.java new file mode 100644 index 0000000..2618467 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/ArgumentValidator.java @@ -0,0 +1,13 @@ +package org.ultramine.commands.syntax; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ArgumentValidator +{ + String value(); +} diff --git a/src/main/java/org/ultramine/commands/syntax/ArgumentsPattern.java b/src/main/java/org/ultramine/commands/syntax/ArgumentsPattern.java new file mode 100644 index 0000000..1afbef0 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/ArgumentsPattern.java @@ -0,0 +1,223 @@ +package org.ultramine.commands.syntax; + +import net.minecraft.command.CommandBase; + +import java.util.ArrayList; +import java.util.List; + +public class ArgumentsPattern +{ + private final List arguments; + private final List names; + private final List actionPositions; + private boolean isInfinite; + + private ArgumentsPattern() + { + arguments = new ArrayList(); + names = new ArrayList(); + actionPositions = new ArrayList(); + isInfinite = false; + } + + private IArgument getSafe(int num) + { + if (num >= arguments.size()) + return isInfinite ? arguments.get(arguments.size() - 1) : IGNORED; + else + return arguments.get(num); + } + + public List getCompletionOptions(String[] args) + { + if (arguments.size() == 0) + return null; + + return getSafe(args.length - 1).getCompletionOptions(args); + } + + public boolean isUsernameIndex(int checkArgNum) + { + return getSafe(checkArgNum).isUsername(); + } + + public boolean match(String[] args) + { + for (int i = 0; i < args.length; i++) + { + if (i == arguments.size()) + return isInfinite; + + IArgument argument = arguments.get(i); + if (argument.hasValidation() && !argument.validate(args[i])) + return false; + } + return args.length == arguments.size(); + } + + public MatchResult partialMatch(String[] args) + { + for (int i = 0; i < arguments.size(); i++) + { + IArgument argument = arguments.get(i); + + if (i < args.length) + { + if (argument.hasValidation() && !argument.validate(args[i])) + return MatchResult.NOT; + } + else + { + if (argument.hasValidation()) + return MatchResult.POSSIBLY; + } + } + if (isInfinite || args.length <= arguments.size()) + return MatchResult.FULLY; + else + return MatchResult.NOT; + } + + public List getArgumentsNames() + { + return names; + } + + public String resolveActionName(String[] args) + { + if (actionPositions.size() == 0) + return ""; + + StringBuilder builder = new StringBuilder(args[actionPositions.get(0)]); + for (int i = 1; i < actionPositions.size(); i++) + builder.append(' ').append(args[actionPositions.get(i)]); + + return builder.toString(); + } + + public static class Builder + { + private ArgumentsPattern pattern; + + public Builder() + { + pattern = new ArgumentsPattern(); + } + + public void addArgument(String name, IArgument argument) + { + pattern.arguments.add(argument); + pattern.names.add(name); + } + + public void addAction(String[] options) + { + pattern.actionPositions.add(pattern.arguments.size()); + addArgument(null, new ActionArgument(options)); + } + + public void makeInfinite() + { + pattern.isInfinite = true; + } + + public ArgumentsPattern build() + { + return pattern; + } + } + + private static class ActionArgument implements IArgument + { + private final String[] options; + + private ActionArgument(String[] options) + { + this.options = options; + } + + @Override + public boolean isUsername() + { + return false; + } + + @Override + public List getCompletionOptions(String[] args) + { + List result = new ArrayList(); + String val = args[args.length - 1]; + for (String option : options) + { + if (CommandBase.doesStringStartWith(val, option)) + result.add(option); + } + return result; + } + + @Override + public boolean hasValidation() + { + return true; + } + + @Override + public boolean validate(String val) + { + for (String action : options) + { + if (action.equalsIgnoreCase(val)) + return true; + } + return false; + } + } + + public static final IArgument IGNORED = new IArgument() + { + @Override + public boolean isUsername() + { + return false; + } + + @Override + public List getCompletionOptions(String[] args) + { + return null; + } + + @Override + public boolean hasValidation() + { + return false; + } + + @Override + public boolean validate(String val) + { + return true; + } + }; + + public static enum MatchResult { + FULLY(2), POSSIBLY(1), NOT(0); + + private int value; + + private MatchResult(int value) + { + this.value = value; + } + + public boolean isLessThan(MatchResult anotherResult) + { + return value < anotherResult.value; + } + + public boolean isGreaterThan(MatchResult anotherResult) + { + return value > anotherResult.value; + } + } +} diff --git a/src/main/java/org/ultramine/commands/syntax/ArgumentsPatternParser.java b/src/main/java/org/ultramine/commands/syntax/ArgumentsPatternParser.java new file mode 100644 index 0000000..9b43423 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/ArgumentsPatternParser.java @@ -0,0 +1,181 @@ +package org.ultramine.commands.syntax; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ArgumentsPatternParser +{ + private static final Logger logger = LogManager.getLogger(); + + private static final Pattern argumentPattern = Pattern.compile("([\\[<])\\s*(([^<>%\\[\\]\\s]*)\\s*([^<>%\\[\\]]*))(|%\\s*([^<>%\\[\\]\\s]*)\\s*)[\\]>]"); + + private final Map completionHandlers = new HashMap(); + private final Map validationHandlers = new HashMap(); + private final List arguments = new ArrayList(); + + public ArgumentsPattern parse(String completionString) + { + ArgumentsPattern.Builder builder = new ArgumentsPattern.Builder(); + Matcher matcher = argumentPattern.matcher(completionString); + + while (matcher.find()) + { + if (matcher.group(1).equals("[")) + { + String[] params = StringUtils.split(matcher.group(2)); + builder.addAction(params); + } + else + { + String handlerName = matcher.group(3); + String argumentName = matcher.group(6); + if (argumentName == null || argumentName.isEmpty()) + argumentName = handlerName; + + String[] params = StringUtils.split(matcher.group(4)); + HandlerBasedArgument argument = new HandlerBasedArgument(argumentName, params); + argument.setValidationHandler(validationHandlers.get(argumentName)); + argument.setCompletionHandler(completionHandlers.get(argumentName)); + arguments.add(argument); + builder.addArgument(argumentName, argument); + } + } + + if (completionString.endsWith("...")) + builder.makeInfinite(); + + return builder.build(); + } + + public void registerHandler(String name, IArgumentCompletionHandler handler) + { + registerHandlerDelayed(name, handler); + updateArguments(); + } + + public void registerHandler(String name, IArgumentValidationHandler handler) + { + registerHandlerDelayed(name, handler); + updateArguments(); + } + + public void registerHandlers(Class cls) + { + for (Method handler : cls.getMethods()) + { + if (!Modifier.isStatic(handler.getModifiers())) + continue; + + if (handler.isAnnotationPresent(ArgumentCompleter.class)) + { + ArgumentCompleter data = handler.getAnnotation(ArgumentCompleter.class); + registerHandlerDelayed(data.value(), new WrappedCompletionHandler(handler, data.isUsername())); + } + + if (handler.isAnnotationPresent(ArgumentValidator.class)) + { + ArgumentValidator data = handler.getAnnotation(ArgumentValidator.class); + registerHandlerDelayed(data.value(), new WrappedValidationHandler(handler)); + } + } + updateArguments(); + } + + private void registerHandlerDelayed(String name, IArgumentCompletionHandler handler) + { + if (!completionHandlers.containsKey(name)) + completionHandlers.put(name, handler); + else + logger.warn("Completion handler name is already registered: " + name); + } + + private void registerHandlerDelayed(String name, IArgumentValidationHandler handler) + { + if (!validationHandlers.containsKey(name)) + validationHandlers.put(name, handler); + else + logger.warn("Validation handler name is already registered: " + name); + } + + private void updateArguments() + { + for (HandlerBasedArgument argument : arguments) + { + argument.setCompletionHandler(completionHandlers.get(argument.getName())); + argument.setValidationHandler(validationHandlers.get(argument.getName())); + } + } + + private static class WrappedCompletionHandler implements IArgumentCompletionHandler + { + private Method method; + private boolean isUsername; + + private WrappedCompletionHandler(Method method, boolean isUsername) + { + this.method = method; + this.isUsername = isUsername; + } + + @Override + public List handleCompletion(String val, String[] params) + { + try + { + return (List) method.invoke(null, val, params); + } + catch (IllegalAccessException ignored) + { + } + catch (InvocationTargetException ignored) + { + } + + return null; + } + + @Override + public boolean isUsername() + { + return isUsername; + } + } + + private static class WrappedValidationHandler implements IArgumentValidationHandler + { + private Method method; + + private WrappedValidationHandler(Method method) + { + this.method = method; + } + + @Override + public boolean handleValidation(String val, String[] params) + { + try + { + return (Boolean) method.invoke(null, val, params); + } + catch (IllegalAccessException ignored) + { + } + catch (InvocationTargetException ignored) + { + } + + return true; + } + } +} diff --git a/src/main/java/org/ultramine/commands/syntax/DefaultCompleters.java b/src/main/java/org/ultramine/commands/syntax/DefaultCompleters.java new file mode 100644 index 0000000..0fe36d4 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/DefaultCompleters.java @@ -0,0 +1,69 @@ +package org.ultramine.commands.syntax; + +import net.minecraft.block.Block; +import net.minecraft.command.CommandBase; +import net.minecraft.entity.EntityList; +import net.minecraft.item.Item; +import net.minecraft.server.MinecraftServer; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultCompleters +{ + @ArgumentCompleter(value = "player", isUsername = true) + public static List player(String val, String[] args) + { + return filterArray(val, MinecraftServer.getServer().getAllUsernames()); + } + + @ArgumentCompleter("item") + public static List item(String val, String[] args) + { + return filterCollection(val, Item.itemRegistry.getKeys()); + } + + @ArgumentCompleter("block") + public static List block(String val, String[] args) + { + return filterCollection(val, Block.blockRegistry.getKeys()); + } + + @ArgumentCompleter("entity") + public static List entity(String val, String[] args) + { + return filterCollection(val, EntityList.func_151515_b()); + } + + @ArgumentCompleter("list") + public static List list(String val, String[] args) + { + return filterArray(val, args); + } + + public static List filterArray(String filter, String[] strings) + { + List result = new ArrayList(); + + for (String str : strings) + { + if (CommandBase.doesStringStartWith(filter, str)) + result.add(str); + } + + return result; + } + + public static List filterCollection(String filter, Iterable iterable) + { + List result = new ArrayList(); + + for (String str : iterable) + { + if (CommandBase.doesStringStartWith(filter, str)) + result.add(str); + } + + return result; + } +} diff --git a/src/main/java/org/ultramine/commands/syntax/HandlerBasedArgument.java b/src/main/java/org/ultramine/commands/syntax/HandlerBasedArgument.java new file mode 100644 index 0000000..6c5faaf --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/HandlerBasedArgument.java @@ -0,0 +1,95 @@ +package org.ultramine.commands.syntax; + +import java.util.List; + +public class HandlerBasedArgument implements IArgument +{ + private IArgumentCompletionHandler completionHandler; + private IArgumentValidationHandler validationHandler; + private final String name; + private final String[] params; + private int[] links; + + public HandlerBasedArgument(String name, String[] params) { + this.name = name; + this.params = new String[params.length]; + this.links = new int[params.length]; + boolean hasLinks = false; + + for (int i = 0; i < params.length; i++) + { + String param = params[i]; + if (param.startsWith("&")) + { + try + { + int argNum = Integer.valueOf(param.substring(1)); + this.links[i] = argNum; + this.params[i] = ""; + hasLinks = true; + continue; + } + catch (Exception ignored) {} + } + this.links[i] = -1; + this.params[i] = param; + } + + if (!hasLinks) links = null; + } + + public void setCompletionHandler(IArgumentCompletionHandler completionHandler) + { + this.completionHandler = completionHandler; + } + + public void setValidationHandler(IArgumentValidationHandler validationHandler) + { + this.validationHandler = validationHandler; + } + + public String getName() + { + return name; + } + + @Override + public boolean isUsername() + { + return completionHandler != null && completionHandler.isUsername(); + } + + @Override + public List getCompletionOptions(String[] args) + { + if (completionHandler == null) + return null; + + String[] params; + if (links != null) + { + params = new String[this.params.length]; + for (int i = 0; i < this.params.length; i++) + { + int link = links[i]; + params[i] = link >= 0 && link < args.length ? args[link] : this.params[i]; + } + } + else + params = this.params; + + return completionHandler.handleCompletion(args[args.length - 1], params); + } + + @Override + public boolean hasValidation() + { + return validationHandler != null; + } + + @Override + public boolean validate(String val) + { + return validationHandler == null || validationHandler.handleValidation(val, params); + } +} diff --git a/src/main/java/org/ultramine/commands/syntax/IArgument.java b/src/main/java/org/ultramine/commands/syntax/IArgument.java new file mode 100644 index 0000000..23b5727 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/IArgument.java @@ -0,0 +1,12 @@ +package org.ultramine.commands.syntax; + +import java.util.List; + +interface IArgument +{ + boolean isUsername(); + List getCompletionOptions(String[] args); + + boolean hasValidation(); + boolean validate(String val); +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/syntax/IArgumentCompletionHandler.java b/src/main/java/org/ultramine/commands/syntax/IArgumentCompletionHandler.java new file mode 100644 index 0000000..af65bc4 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/IArgumentCompletionHandler.java @@ -0,0 +1,9 @@ +package org.ultramine.commands.syntax; + +import java.util.List; + +public interface IArgumentCompletionHandler +{ + List handleCompletion(String val, String[] params); + boolean isUsername(); +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/commands/syntax/IArgumentValidationHandler.java b/src/main/java/org/ultramine/commands/syntax/IArgumentValidationHandler.java new file mode 100644 index 0000000..95daec5 --- /dev/null +++ b/src/main/java/org/ultramine/commands/syntax/IArgumentValidationHandler.java @@ -0,0 +1,6 @@ +package org.ultramine.commands.syntax; + +public interface IArgumentValidationHandler +{ + boolean handleValidation(String val, String[] params); +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/server/UltramineServerModContainer.java b/src/main/java/org/ultramine/server/UltramineServerModContainer.java index 454010c..e814f5c 100644 --- a/src/main/java/org/ultramine/server/UltramineServerModContainer.java +++ b/src/main/java/org/ultramine/server/UltramineServerModContainer.java @@ -19,8 +19,7 @@ import cpw.mods.fml.common.network.NetworkCheckHandler; import cpw.mods.fml.common.network.NetworkRegistry; import cpw.mods.fml.relauncher.Side; -import org.ultramine.commands.completion.DefaultCompleters; -import org.ultramine.commands.completion.RegisterCompletersEvent; +import org.ultramine.commands.syntax.DefaultCompleters; import org.ultramine.permission.commands.BasicPermissionCommands; public class UltramineServerModContainer extends DummyModContainer @@ -81,12 +80,6 @@ } @Subscribe - public void registerCommandCompleters(RegisterCompletersEvent event) - { - event.getCompletionStringParser().registerHandlers(DefaultCompleters.class); - } - - @Subscribe public void serverStarting(FMLServerStartingEvent e) { switch (e.getSide()) @@ -98,7 +91,8 @@ PermissionHandler.initServer(); break; } - e.getCommandRegistry().registerCommands(BasicPermissionCommands.class); + e.registerArgumentHandlers(DefaultCompleters.class); + e.registerCommands(BasicPermissionCommands.class); } @Subscribe diff --git a/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy b/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy deleted file mode 100644 index ba86991..0000000 --- a/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy +++ /dev/null @@ -1,199 +0,0 @@ -package org.ultramine.commands.completion - -import spock.lang.Specification - -class CommandCompletionHandlerTest extends Specification { - - def commandHandler = new CommandCompletionHandler() - - def "Test completion"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - - when: "Add argument handler to command handler" - commandHandler.addNextArgument("name", argumentHandler, "p1", "p2") - - and: "Get completion for first argument" - commandHandler.getCompletionOptions("first") - - then: "Argument handler is called" - 1 * argumentHandler.handleCompletion("first", "p1", "p2") - } - - def "Test action completion"() { - setup: - commandHandler.addNextActionArgument("add", "adopt", "remove") - - expect: - commandHandler.getCompletionOptions("ad") == ["add", "adopt"] - } - - def "Test ignored argument"() { - when: "Add ignored argument" - commandHandler.ignoreNextArgument("name") - - and: "Get completion for first argument" - def result = commandHandler.getCompletionOptions("first") - - then: "Result is null" - result == null - } - - def "Test argument replacement"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - - when: "Add argument handler with linked params to command handler" - commandHandler.ignoreNextArgument("name") - commandHandler.addNextArgument("name", argumentHandler, "p1", "&0", "p3") - - and: "Get completion for second argument" - commandHandler.getCompletionOptions("first", "second") - - then: "Argument handler is called with right params" - 1 * argumentHandler.handleCompletion("second", "p1", "first", "p3") - } - - def "Test argument replacement: out if bound index"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - - when: "Add argument handler with linked params to command handler" - commandHandler.ignoreNextArgument("name") - commandHandler.addNextArgument("name", argumentHandler, "p1", "&5", "p3") - - and: "Get completion for second argument" - commandHandler.getCompletionOptions("first", "second") - - then: "Argument handler is called with right params" - 1 * argumentHandler.handleCompletion("second", "p1", "&5", "p3") - } - - def "Test infinite handler"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - commandHandler.addNextArgument("name", argumentHandler) - - when: "Get completion for second argument" - def result = commandHandler.getCompletionOptions("first", "second") - - then: "Argument handler is not called" - 0 * argumentHandler._ - - and: "Result is null" - result == null - - when: "Make command handler infinite" - commandHandler.makeInfinite() - - and: "Get completion for second and third argument" - commandHandler.getCompletionOptions("first", "second") - commandHandler.getCompletionOptions("first", "second", "third") - - then: "Argument handler is called" - 1 * argumentHandler.handleCompletion("second") - 1 * argumentHandler.handleCompletion("third") - } - - def "Test username argument"() { - setup: "Command completion with username argument" - commandHandler.ignoreNextArgument("name") - commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler) { - isUsername() >> true - }) - commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler)) - - expect: "Username argument is detected correctly" - !commandHandler.isUsernameIndex(0) - commandHandler.isUsernameIndex(1) - !commandHandler.isUsernameIndex(2) - - when: "Add another username argument" - commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler) { - isUsername() >> true - }) - - then: "Only first argument is detected" - !commandHandler.isUsernameIndex(0) - commandHandler.isUsernameIndex(1) - !commandHandler.isUsernameIndex(2) - !commandHandler.isUsernameIndex(3) - } - - def "Test blank handler"() { - expect: "Always return null" - commandHandler.getCompletionOptions("first") == null - commandHandler.getCompletionOptions("first", "second") == null - } - - def "Test matching"() { - setup: - commandHandler.ignoreNextArgument("ignored") - commandHandler.addNextActionArgument("add") - commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler)) - - expect: "Strict matching is correct" - !commandHandler.match(true, "arg1") - !commandHandler.match(true, "arg1", "add") - commandHandler.match(true, "arg1", "add", "arg2") - !commandHandler.match(true, "arg1", "add2", "arg2") - !commandHandler.match(true, "arg1", "add", "arg2", "arg3") - - and: "Not strict matching is correct" - commandHandler.match(false, "arg1") - commandHandler.match(false, "arg1", "add") - commandHandler.match(false, "arg1", "add", "arg2") - !commandHandler.match(false, "arg1", "add2", "arg2") - !commandHandler.match(false, "arg1", "add", "arg2", "arg3") - - when: "Make handler infinite" - commandHandler.makeInfinite() - - then: "Strict matching is correct" - !commandHandler.match(true, "arg1") - !commandHandler.match(true, "arg1", "add") - commandHandler.match(true, "arg1", "add", "arg2") - !commandHandler.match(true, "arg1", "ad", "arg2") - commandHandler.match(true, "arg1", "add", "arg2", "arg3") - - and: "Not strict matching is correct" - commandHandler.match(false, "arg1") - commandHandler.match(false, "arg1", "add") - commandHandler.match(false, "arg1", "add", "arg2") - !commandHandler.match(false, "arg1", "ad", "arg2") - commandHandler.match(false, "arg1", "add", "arg2", "arg3") - } - - def "Test names"() { - setup: - commandHandler.ignoreNextArgument("ignored") - commandHandler.addNextActionArgument("add") - commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler)) - - expect: "Names are correct" - commandHandler.getNames() == ["ignored", "action", "name"] - } - - def "Test integration"() { - setup: - commandHandler.ignoreNextArgument("name") - commandHandler.addNextArgument("name", - new IArgumentCompletionHandler() { - @Override - List handleCompletion(String val, String[] args) { - return args.findAll { it.startsWith(val) } - } - - @Override - boolean isUsername() { - return false - } - }, "boom", "bod", "&0" - ) - - expect: "Completions are correct" - commandHandler.getCompletionOptions("ara", "bo") == ["boom", "bod"] - commandHandler.getCompletionOptions("zava", "za") == ["zava"] - commandHandler.getCompletionOptions("b", "va", "vaka") == null - } -} diff --git a/src/test/groovy/org/ultramine/commands/completion/CompletionStringParserTest.groovy b/src/test/groovy/org/ultramine/commands/completion/CompletionStringParserTest.groovy deleted file mode 100644 index 5ad49cc..0000000 --- a/src/test/groovy/org/ultramine/commands/completion/CompletionStringParserTest.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package org.ultramine.commands.completion - -import spock.lang.Specification - -class CompletionStringParserTest extends Specification { - - def parser = new CompletionStringParser() - - def "Test single handler"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - parser.registerHandler("test", argumentHandler) - def commandHandler = parser.parse("") - - when: "Get completion" - commandHandler.getCompletionOptions("first") - - then: "Argument handler is called" - 1 * argumentHandler.handleCompletion("first", "par1", "par2") - } - - def "Test not registered handler"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - parser.registerHandler("test", argumentHandler) - def commandHandler = parser.parse("") - - when: "Get completion" - commandHandler.getCompletionOptions("first") - - then: "Argument handler is not called" - 0 * argumentHandler._ - } - - def "Test action argument"() { - setup: - def commandHandler = parser.parse("[ add remove ]") - - expect: "Action is parsed" - commandHandler.getCompletionOptions("a") == ["add"] - commandHandler.getNames() == ["action"] - } - - def "Test several arguments"() { - setup: - def playerHandler = Mock(IArgumentCompletionHandler) - def itemHandler = Mock(IArgumentCompletionHandler) - parser.registerHandler("player", playerHandler) - parser.registerHandler("item", itemHandler) - def commadHandler = parser.parse(string) - - when: "Get completions" - commadHandler.getCompletionOptions("first") - commadHandler.getCompletionOptions("first", "second") - commadHandler.getCompletionOptions("first", "second", "third") - commadHandler.getCompletionOptions("first", "second", "third", "fourth") - - then: "Argument handles called correctly" - 1 * playerHandler.handleCompletion("first") - 1 * itemHandler.handleCompletion("third", "first") - 0 * playerHandler._ - 0 * itemHandler._ - - where: - string << [" <> ", " < player> < > < item &0 >"] - } - - def "Test infinite"() { - setup: - def argumentHandler = Mock(IArgumentCompletionHandler) - parser.registerHandler("test", argumentHandler) - def commandHandler = parser.parse("<> ...") - - when: "Get completion" - commandHandler.getCompletionOptions("first", "second", "third") - - then: "Argument handler is called" - 1 * argumentHandler.handleCompletion("third", "par1", "par2") - } - - def "Test naming"() { - setup: - parser.registerHandler("test", Mock(IArgumentCompletionHandler)) - def commandHandler = parser.parse(" [add remove] <%p3>") - - expect: "Names are correct" - commandHandler.getNames() == ["p1", "test", "action", "p2", "p3"] - } - - def "Test integration"() { - setup: - parser.registerHandlers(TestHandlers) - def commnadHander = parser.parse(" ") - - expect: - commnadHander.getCompletionOptions("B") == ["Bob", "Barny"] - commnadHander.getCompletionOptions("Bob", "ki") == ["kick", "kill"] - commnadHander.isUsernameIndex(0) - } - - public static class TestHandlers { - @ArgumentCompleter(value = "player", isUsername = true) - public static List player(String val, String[] args) - { - return ["Bob", "Jenifer", "Barny"].findAll { it.startsWith(val) } - } - - @ArgumentCompleter("list") - public static List list(String val, String[] args) - { - return args.findAll { it.startsWith(val) } - } - } -} diff --git a/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternParserTest.groovy b/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternParserTest.groovy new file mode 100644 index 0000000..31f43c4 --- /dev/null +++ b/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternParserTest.groovy @@ -0,0 +1,121 @@ +package org.ultramine.commands.syntax + +import spock.lang.Specification + +class ArgumentsPatternParserTest extends Specification { + + def parser = new ArgumentsPatternParser() + + def "Test single handler"() { + setup: + def argumentHandler = Mock(IArgumentCompletionHandler) + parser.registerHandler("test", argumentHandler) + def commandHandler = parser.parse("") + + when: "Get completion" + commandHandler.getCompletionOptions("first") + + then: "Argument handler is called" + 1 * argumentHandler.handleCompletion("first", "par1", "par2") + } + + def "Test not registered handler"() { + setup: + def argumentHandler = Mock(IArgumentCompletionHandler) + parser.registerHandler("test", argumentHandler) + def commandHandler = parser.parse("") + + when: "Get completion" + commandHandler.getCompletionOptions("first") + + then: "Argument handler is not called" + 0 * argumentHandler._ + } + + def "Test action argument"() { + setup: + def commandHandler = parser.parse("[ add remove ]") + + expect: "Action is parsed" + commandHandler.getCompletionOptions("a") == ["add"] + commandHandler.resolveActionName("add") == "add" + } + + def "Test several arguments"() { + setup: + def playerHandler = Mock(IArgumentCompletionHandler) + def itemHandler = Mock(IArgumentCompletionHandler) + parser.registerHandler("player", playerHandler) + parser.registerHandler("item", itemHandler) + def commadHandler = parser.parse(string) + + when: "Get completions" + commadHandler.getCompletionOptions("first") + commadHandler.getCompletionOptions("first", "second") + commadHandler.getCompletionOptions("first", "second", "third") + commadHandler.getCompletionOptions("first", "second", "third", "fourth") + + then: "Argument handles called correctly" + 1 * playerHandler.handleCompletion("first") + 1 * itemHandler.handleCompletion("third", "first") + 0 * playerHandler._ + 0 * itemHandler._ + + where: + string << [" <> ", " < player> < > < item &0 >"] + } + + def "Test infinite"() { + setup: + def argumentHandler = Mock(IArgumentCompletionHandler) + parser.registerHandler("test", argumentHandler) + def commandHandler = parser.parse("<> ...") + + when: "Get completion" + commandHandler.getCompletionOptions("first", "second", "third") + + then: "Argument handler is called" + 1 * argumentHandler.handleCompletion("third", "par1", "par2") + } + + def "Test naming"() { + setup: + parser.registerHandler("test", Mock(IArgumentCompletionHandler)) + def commandHandler = parser.parse(" [add remove] <%p3>") + + expect: "Names are correct" + commandHandler.getArgumentsNames() == ["p1", "test", null, "p2", "p3"] + } + + def "Test integration"() { + setup: + def commnadHander = parser.parse(" ") + parser.registerHandlers(TestHandlers) + + expect: + commnadHander.getCompletionOptions("B") == ["Bob", "Barny"] + commnadHander.getCompletionOptions("Bob", "ki") == ["kick", "kill"] + commnadHander.match("Jenifer", "aza") + commnadHander.isUsernameIndex(0) + } + + public static class TestHandlers { + @ArgumentCompleter(value = "player", isUsername = true) + public static List player(String val, String[] args) + { + return ["Bob", "Jenifer", "Barny"].findAll { it.startsWith(val) } + } + + @ArgumentCompleter("list") + public static List list(String val, String[] args) + { + return args.findAll { it.startsWith(val) } + } + + @ArgumentValidator("player") + public static boolean player_v(String val, String[] args) + { + return ["Bob", "Jenifer", "Barny"].contains(val) + } + } +} diff --git a/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternTest.groovy b/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternTest.groovy new file mode 100644 index 0000000..1b29a06 --- /dev/null +++ b/src/test/groovy/org/ultramine/commands/syntax/ArgumentsPatternTest.groovy @@ -0,0 +1,190 @@ +package org.ultramine.commands.syntax + +import spock.lang.Specification + +import static org.ultramine.commands.syntax.ArgumentsPattern.MatchResult.* + +class ArgumentsPatternTest extends Specification { + + def builder = new ArgumentsPattern.Builder() + def pattern = builder.build(); + + def "Test completion"() { + setup: "Pattern with argument" + def argument = Mock(IArgument) + builder.addArgument("name", argument); + + when: "Get completion for first argument" + pattern.getCompletionOptions("first") + + then: "Argument handler is called" + 1 * argument.getCompletionOptions("first") + } + + def "Test action completion"() { + setup: "Pattern with action" + builder.addAction("add", "adopt", "remove") + + expect: + pattern.getCompletionOptions("ad") == ["add", "adopt"] + } + + def "Test action name"() { + setup: "Pattern without action" + builder.addArgument("name", Mock(IArgument)) + + expect: "Action name is blank" + pattern.resolveActionName("name") == "" + + when: "With one action" + builder.addAction("add", "get") + + then: "Action name is correct" + pattern.resolveActionName("name", "add") == "add" + + when: "With two actions" + builder.addAction("make") + + then: "Space is separator between actions" + pattern.resolveActionName("name", "get", "make") == "get make" + } + + def "Test infinite handler"() { + setup: + def argument = Mock(IArgument) + builder.addArgument("name", argument) + + when: "Get completion for second argument" + def result = pattern.getCompletionOptions("first", "second") + + then: "Argument handler is not called" + 0 * argument._ + + and: "Result is null" + result == null + + when: "Make command handler infinite" + builder.makeInfinite() + + and: "Get completion for second and third argument" + pattern.getCompletionOptions("first", "second") + pattern.getCompletionOptions("first", "second", "third") + + then: "Argument handler is called" + 1 * argument.getCompletionOptions("first", "second") + 1 * argument.getCompletionOptions("first", "second", "third") + } + + def "Test username argument"() { + setup: "Command completion with username argument" + builder.addArgument("name", Mock(IArgument)) + builder.addArgument("name", Mock(IArgument) { + isUsername() >> true + }) + builder.addArgument("name", Mock(IArgument)) + + expect: "Username argument is detected correctly" + !pattern.isUsernameIndex(0) + pattern.isUsernameIndex(1) + !pattern.isUsernameIndex(2) + + when: "Add another username argument" + builder.addArgument("name", Mock(IArgument) { + isUsername() >> true + }) + + then: "Both arguments are detected" + !pattern.isUsernameIndex(0) + pattern.isUsernameIndex(1) + !pattern.isUsernameIndex(2) + pattern.isUsernameIndex(3) + } + + def "Test blank handler"() { + expect: "Always return null" + pattern.getCompletionOptions("first") == null + pattern.getCompletionOptions("first", "second") == null + } + + def "Test matching"() { + setup: + builder.addArgument("name", Mock(IArgument)) + builder.addAction("add") + builder.addArgument("name", Mock(IArgument)) + + expect: "Matching is correct" + !pattern.match("arg1") + !pattern.match("arg1", "add") + pattern.match("arg1", "add", "arg2") + !pattern.match("arg1", "add2", "arg2") + !pattern.match("arg1", "add", "arg2", "arg3") + + when: "Make handler infinite" + builder.makeInfinite() + + then: "Matching is correct" + !pattern.match("arg1") + !pattern.match("arg1", "add") + pattern.match("arg1", "add", "arg2") + !pattern.match("arg1", "ad", "arg2") + pattern.match("arg1", "add", "arg2", "arg3") + } + + def "Test partial matching"() + { + setup: + builder.addArgument("name", Mock(IArgument)) + builder.addAction("add") + builder.addArgument("name", Mock(IArgument)) + + expect: "Partial matching is correct" + pattern.partialMatch("arg1") == POSSIBLY + pattern.partialMatch("arg1", "add") == FULLY + pattern.partialMatch("arg1", "add", "arg2") == FULLY + pattern.partialMatch("arg1", "add2", "arg2") == NOT + pattern.partialMatch("arg1", "add", "arg2", "arg3") == NOT + + when: "Make handler infinite" + builder.makeInfinite() + + then: "Partial matching is correct" + pattern.partialMatch("arg1") == POSSIBLY + pattern.partialMatch("arg1", "add") == FULLY + pattern.partialMatch("arg1", "add", "arg2") == FULLY + pattern.partialMatch("arg1", "ad", "arg2") == NOT + pattern.partialMatch("arg1", "add", "arg2", "arg3") == FULLY + } + + def "Test names"() { + setup: + builder.addArgument("ignored", Mock(IArgument)) + builder.addAction("add") + builder.addArgument("name", Mock(IArgument)) + + expect: "Names are correct" + pattern.getArgumentsNames() == ["ignored", null, "name"] + } + + def "Test integration"() { + setup: + builder.addArgument("name", Mock(IArgument)) + IArgument argument = new HandlerBasedArgument("name", "boom", "bod", "&0"); + argument.setCompletionHandler(new IArgumentCompletionHandler() { + @Override + List handleCompletion(String val, String[] params) { + return params.findAll { it.startsWith(val) } + } + + @Override + boolean isUsername() { + return false + } + }) + builder.addArgument("name", argument) + + expect: "Completions are correct" + pattern.getCompletionOptions("ara", "bo") == ["boom", "bod"] + pattern.getCompletionOptions("zava", "za") == ["zava"] + pattern.getCompletionOptions("b", "va", "vaka") == null + } +} diff --git a/src/test/groovy/org/ultramine/commands/syntax/HandlerBasedArgumentTest.groovy b/src/test/groovy/org/ultramine/commands/syntax/HandlerBasedArgumentTest.groovy new file mode 100644 index 0000000..f66183e --- /dev/null +++ b/src/test/groovy/org/ultramine/commands/syntax/HandlerBasedArgumentTest.groovy @@ -0,0 +1,46 @@ +package org.ultramine.commands.syntax + +import spock.lang.Specification + +class HandlerBasedArgumentTest extends Specification { + + + def "Test argument replacement"() { + setup: + def argument = new HandlerBasedArgument("name", "p1", "&0", "p3") + def handler = Mock(IArgumentCompletionHandler) + argument.setCompletionHandler(handler) + + when: "Get completion for argument" + argument.getCompletionOptions("first", "second") + + then: "Argument handler is called with right params" + 1 * handler.handleCompletion("second", "p1", "first", "p3") + } + + def "Test argument replacement: out if bound index"() { + setup: + def argument = new HandlerBasedArgument("name", "p1", "&5", "p3") + def handler = Mock(IArgumentCompletionHandler) + argument.setCompletionHandler(handler) + + when: "Get completion for argument" + argument.getCompletionOptions("first", "second") + + then: "Argument handler is called with right params" + 1 * handler.handleCompletion("second", "p1", "", "p3") + } + + def "Test argument replacement: not number"() { + setup: + def argument = new HandlerBasedArgument("name", "p1", "&aza", "p3") + def handler = Mock(IArgumentCompletionHandler) + argument.setCompletionHandler(handler) + + when: "Get completion for argument" + argument.getCompletionOptions("first", "second") + + then: "Argument handler is called with right params" + 1 * handler.handleCompletion("second", "p1", "&aza", "p3") + } +}