diff --git a/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java b/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java index 7a9d4ff..8139bd6 100644 --- a/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java +++ b/src/main/java/org/ultramine/commands/completion/CommandCompletionHandler.java @@ -1,11 +1,15 @@ package org.ultramine.commands.completion; +import net.minecraft.command.CommandBase; import java.util.ArrayList; import java.util.List; public class CommandCompletionHandler { - private List completers = new ArrayList(); + 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; @@ -14,16 +18,13 @@ if (completers.size() == 0) return null; - ArgumentCompleter completer; + ICompleter completer; if (completers.size() < args.length) completer = isInfinite ? completers.get(completers.size() - 1) : IGNORED; else completer = completers.get(args.length - 1); - if (completer != IGNORED) - return completer.getCompletionOptions(args); - else - return null; + return completer.getCompletionOptions(args); } public boolean isUsernameIndex(int checkArgNum) @@ -31,17 +32,43 @@ return checkArgNum == usernameArgIndex; } - public void addNextArgument(IArgumentCompletionHandler handler, String[] params) + 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 ArgumentCompleter(handler, params)); + completers.add(new ICompleter.ArgumentCompleter(handler, params)); + names.add(name); } - public void ignoreNextArgument() + 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() @@ -49,41 +76,90 @@ isInfinite = true; } - private static ArgumentCompleter IGNORED = new ArgumentCompleter(null, null); - private static class ArgumentCompleter + private static interface ICompleter { - private IArgumentCompletionHandler handler; - private String[] params; + List getCompletionOptions(String[] args); + boolean match(String val); - private ArgumentCompleter(IArgumentCompletionHandler handler, String[] params) + static class ArgumentCompleter implements ICompleter { - this.handler = handler; - this.params = params; - } + private IArgumentCompletionHandler handler; + private String[] params; - List getCompletionOptions(String[] args) - { - String[] params = new String[this.params.length]; - for (int i = 0; i < this.params.length; i++) + private ArgumentCompleter(IArgumentCompletionHandler handler, String[] params) { - 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; + this.handler = handler; + this.params = params; } - return handler.handleCompletion(args[args.length - 1], 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 index 7bc1573..d43dae1 100644 --- a/src/main/java/org/ultramine/commands/completion/CompletionStringParser.java +++ b/src/main/java/org/ultramine/commands/completion/CompletionStringParser.java @@ -13,7 +13,7 @@ public class CompletionStringParser { - private static final Pattern argumentPattern = Pattern.compile("<\\s*([^<>\\s]*)\\s*([^<>]*)>"); + private static final Pattern argumentPattern = Pattern.compile("([\\[<])\\s*(([^<>%\\[\\]\\s]*)\\s*([^<>%\\[\\]]*))(|%\\s*([^<>%\\[\\]\\s]*)\\s*)[\\]>]"); private Map handlers = new HashMap(); public CommandCompletionHandler parse(String completionString) @@ -23,16 +23,27 @@ while (matcher.find()) { - String handlerName = matcher.group(1); - - if (handlerName.isEmpty() || !handlers.containsKey(handlerName)) + if (matcher.group(1).equals("[")) { - result.ignoreNextArgument(); + String[] params = StringUtils.split(matcher.group(2)); + result.addNextActionArgument(params); } else { - String[] params = StringUtils.split(matcher.group(2)); - result.addNextArgument(handlers.get(handlerName), params); + 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); + } } } diff --git a/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy b/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy index ed5dc2e..ba86991 100644 --- a/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy +++ b/src/test/groovy/org/ultramine/commands/completion/CommandCompletionHandlerTest.groovy @@ -11,7 +11,7 @@ def argumentHandler = Mock(IArgumentCompletionHandler) when: "Add argument handler to command handler" - commandHandler.addNextArgument(argumentHandler, "p1", "p2") + commandHandler.addNextArgument("name", argumentHandler, "p1", "p2") and: "Get completion for first argument" commandHandler.getCompletionOptions("first") @@ -20,9 +20,17 @@ 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() + commandHandler.ignoreNextArgument("name") and: "Get completion for first argument" def result = commandHandler.getCompletionOptions("first") @@ -36,8 +44,8 @@ def argumentHandler = Mock(IArgumentCompletionHandler) when: "Add argument handler with linked params to command handler" - commandHandler.ignoreNextArgument() - commandHandler.addNextArgument(argumentHandler, "p1", "&0", "p3") + commandHandler.ignoreNextArgument("name") + commandHandler.addNextArgument("name", argumentHandler, "p1", "&0", "p3") and: "Get completion for second argument" commandHandler.getCompletionOptions("first", "second") @@ -51,8 +59,8 @@ def argumentHandler = Mock(IArgumentCompletionHandler) when: "Add argument handler with linked params to command handler" - commandHandler.ignoreNextArgument() - commandHandler.addNextArgument(argumentHandler, "p1", "&5", "p3") + commandHandler.ignoreNextArgument("name") + commandHandler.addNextArgument("name", argumentHandler, "p1", "&5", "p3") and: "Get completion for second argument" commandHandler.getCompletionOptions("first", "second") @@ -64,7 +72,7 @@ def "Test infinite handler"() { setup: def argumentHandler = Mock(IArgumentCompletionHandler) - commandHandler.addNextArgument(argumentHandler) + commandHandler.addNextArgument("name", argumentHandler) when: "Get completion for second argument" def result = commandHandler.getCompletionOptions("first", "second") @@ -89,9 +97,11 @@ def "Test username argument"() { setup: "Command completion with username argument" - commandHandler.ignoreNextArgument() - commandHandler.addNextArgument(Mock(IArgumentCompletionHandler) { isUsername() >> true }) - commandHandler.addNextArgument(Mock(IArgumentCompletionHandler)) + commandHandler.ignoreNextArgument("name") + commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler) { + isUsername() >> true + }) + commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler)) expect: "Username argument is detected correctly" !commandHandler.isUsernameIndex(0) @@ -99,7 +109,9 @@ !commandHandler.isUsernameIndex(2) when: "Add another username argument" - commandHandler.addNextArgument(Mock(IArgumentCompletionHandler) { isUsername() >> true }) + commandHandler.addNextArgument("name", Mock(IArgumentCompletionHandler) { + isUsername() >> true + }) then: "Only first argument is detected" !commandHandler.isUsernameIndex(0) @@ -108,16 +120,64 @@ !commandHandler.isUsernameIndex(3) } - def "Test black handler"() { + 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() - commandHandler.addNextArgument( + commandHandler.ignoreNextArgument("name") + commandHandler.addNextArgument("name", new IArgumentCompletionHandler() { @Override List handleCompletion(String val, String[] args) { @@ -131,15 +191,9 @@ }, "boom", "bod", "&0" ) - when: "Get completion" - def r1 = commandHandler.getCompletionOptions("ara", "bo") - def r2 = commandHandler.getCompletionOptions("zava", "za") - def r3 = commandHandler.getCompletionOptions("b", "va", "vaka") - - then: "Result is correct" - r1 == ["boom", "bod"] - r2 == ["zava"] - r3 == null - + 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 index 504923a..5ad49cc 100644 --- a/src/test/groovy/org/ultramine/commands/completion/CompletionStringParserTest.groovy +++ b/src/test/groovy/org/ultramine/commands/completion/CompletionStringParserTest.groovy @@ -32,6 +32,15 @@ 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) @@ -69,6 +78,15 @@ 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)