diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/BooleanFlag.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/BooleanFlag.java index be96c0210..3e849a099 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/BooleanFlag.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/BooleanFlag.java @@ -19,37 +19,35 @@ package com.sk89q.worldedit.command.argument; -import com.google.common.collect.Lists; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandLocals; import com.sk89q.worldedit.util.command.argument.CommandArgs; import com.sk89q.worldedit.util.command.composition.CommandExecutor; +import java.util.Collections; import java.util.List; public class BooleanFlag implements CommandExecutor { - private final char flag; private final String description; - public BooleanFlag(char flag, String description) { - this.flag = flag; + public BooleanFlag(String description) { this.description = description; } @Override public Boolean call(CommandArgs args, CommandLocals locals) throws CommandException { - return args.containsFlag(flag); + return true; } @Override public List getSuggestions(CommandArgs args, CommandLocals locals) { - return Lists.newArrayList("-" + flag); + return Collections.emptyList(); } @Override public String getUsage() { - return "[-" + flag + "]"; + return ""; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DeformArg.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DeformArg.java index 6c684f908..3e7302703 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DeformArg.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/DeformArg.java @@ -30,19 +30,22 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.function.factory.Deform; import com.sk89q.worldedit.function.factory.Deform.Mode; import com.sk89q.worldedit.util.command.argument.CommandArgs; +import com.sk89q.worldedit.util.command.composition.FlagParser.Flag; +import com.sk89q.worldedit.util.command.composition.FlagParser.FlagData; import com.sk89q.worldedit.util.command.composition.SimpleCommand; public class DeformArg extends SimpleCommand { - private final BooleanFlag rawCoordsFlag = addParameter(new BooleanFlag('r', "Raw coords mode")); - private final BooleanFlag offsetFlag = addParameter(new BooleanFlag('o', "Offset mode")); + private final Flag rawCoordsFlag = addFlag('r', new BooleanFlag("Raw coords mode")); + private final Flag offsetFlag = addFlag('o', new BooleanFlag("Offset mode")); private final StringArg expressionParser = addParameter(new StringArg("expression", "Expression to apply")); @Override public Deform call(CommandArgs args, CommandLocals locals) throws CommandException { + FlagData flagData = getFlagParser().call(args, locals); String expression = expressionParser.call(args, locals); - boolean rawCoords = rawCoordsFlag.call(args, locals); - boolean offset = offsetFlag.call(args, locals); + boolean rawCoords = rawCoordsFlag.get(flagData, false); + boolean offset = offsetFlag.get(flagData, false); Deform deform = new Deform(expression); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/argument/CommandArgs.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/argument/CommandArgs.java index 55537ec3f..7c91149e8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/argument/CommandArgs.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/argument/CommandArgs.java @@ -20,26 +20,23 @@ package com.sk89q.worldedit.util.command.argument; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; public class CommandArgs { private final List arguments; - private final Map flags; private int position = 0; - private Set consumedFlags = Sets.newHashSet(); - public CommandArgs(List arguments, Map flags) { + public CommandArgs(List arguments) { this.arguments = arguments; - this.flags = Maps.newHashMap(flags); + } + + public CommandArgs(CommandArgs args) { + this(Lists.newArrayList(args.arguments)); } public boolean hasNext() { @@ -54,6 +51,14 @@ public class CommandArgs { } } + public String uncheckedNext() { + if (hasNext()) { + return arguments.get(position); + } else { + return null; + } + } + public String peek() throws MissingArgumentException { try { return arguments.get(position); @@ -62,6 +67,14 @@ public class CommandArgs { } } + public String uncheckedPeek() { + if (hasNext()) { + return arguments.get(position); + } else { + return null; + } + } + public String remaining() throws MissingArgumentException { if (hasNext()) { StringBuilder builder = new StringBuilder(); @@ -108,206 +121,20 @@ public class CommandArgs { position = arguments.size(); } - public CommandArgs split() { - return new CommandArgs(getUnusedArguments(), getUnusedFlags()); - } - - private List getUnusedArguments() { - List args = Lists.newArrayList(); - while (position < arguments.size()) { - args.add(arguments.get(position++)); - } - return args; - } - - private Map getUnusedFlags() { - Map flags = Maps.newHashMap(); - for (Entry entry : this.flags.entrySet()) { - if (!consumedFlags.contains(entry.getKey())) { - flags.put(entry.getKey(), entry.getValue()); - consumedFlags.add(entry.getKey()); - } - } - return flags; - } - public void requireAllConsumed() throws UnusedArgumentsException { - StringBuilder builder = new StringBuilder(); - boolean hasUnconsumed = false; - - if (flags.size() > consumedFlags.size()) { - hasUnconsumed = true; - for (Entry entry : flags.entrySet()) { - if (!consumedFlags.contains(entry.getKey())) { - builder.append("-").append(entry.getKey()).append(" "); - if (entry.getValue() != null) { - builder.append(entry.getValue()).append(" "); - } - } - } - } - if (hasNext()) { - hasUnconsumed = true; + StringBuilder builder = new StringBuilder(); try { builder.append(peekRemaining()); } catch (MissingArgumentException e) { throw new RuntimeException("This should not have happened", e); } - } - - if (hasUnconsumed) { throw new UnusedArgumentsException("There were unused arguments: " + builder); } } - public int nextInt() throws ArgumentParseException, MissingArgumentException { - String next = next(); - try { - return Integer.parseInt(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number, got '" + next + "'"); - } - } - - public short nextShort() throws ArgumentParseException, MissingArgumentException { - String next = next(); - try { - return Short.parseShort(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number, got '" + next + "'"); - } - } - - public byte nextByte() throws ArgumentParseException, MissingArgumentException { - String next = next(); - try { - return Byte.parseByte(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number, got '" + next + "'"); - } - } - - public double nextDouble() throws ArgumentParseException, MissingArgumentException { - String next = next(); - try { - return Double.parseDouble(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number, got '" + next + "'"); - } - } - - public float nextFloat() throws ArgumentParseException, MissingArgumentException { - String next = next(); - try { - return Float.parseFloat(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number, got '" + next + "'"); - } - } - - public boolean nextBoolean() throws ArgumentParseException, MissingArgumentException { - String next = next(); - if (next.equalsIgnoreCase("yes") || next.equalsIgnoreCase("true") || next.equalsIgnoreCase("y") || next.equalsIgnoreCase("1")) { - return true; - } else if (next.equalsIgnoreCase("no") || next.equalsIgnoreCase("false") || next.equalsIgnoreCase("n") || next.equalsIgnoreCase("0")) { - return false; - } else { - throw new ArgumentParseException("Expected a boolean (yes/no), got '" + next + "'"); - } - } - - public boolean containsFlag(char c) { - boolean contains = flags.containsKey(c); - if (contains) { - consumedFlags.add(c); - } - return contains; - } - - public String getFlag(char c, String fallback) { - boolean contains = flags.containsKey(c); - if (contains) { - consumedFlags.add(c); - String value = flags.get(c); - if (value == null) { - return fallback; - } else { - return value; - } - } - return fallback; - } - - public int getIntFlag(char c, int fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - try { - return Integer.parseInt(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number for flag '-" + c + "', got '" + next + "'"); - } - } - - public short getShortFlag(char c, short fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - try { - return Short.parseShort(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number for flag '-" + c + "', got '" + next + "'"); - } - } - - public byte getByteFlag(char c, byte fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - try { - return Byte.parseByte(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number for flag '-" + c + "', got '" + next + "'"); - } - } - - public double getDoubleFlag(char c, double fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - try { - return Double.parseDouble(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number for flag '-" + c + "', got '" + next + "'"); - } - } - - public float getFloatFlag(char c, float fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - try { - return Float.parseFloat(next); - } catch (NumberFormatException ignored) { - throw new ArgumentParseException("Expected a number for flag '-" + c + "', got '" + next + "'"); - } - } - - public boolean getBooleanFlag(char c, boolean fallback) throws ArgumentParseException { - String next = getFlag(c, String.valueOf(fallback)); - if (next.equalsIgnoreCase("yes") || next.equalsIgnoreCase("true") || next.equalsIgnoreCase("y") || next.equalsIgnoreCase("1")) { - return true; - } else if (next.equalsIgnoreCase("no") || next.equalsIgnoreCase("false") || next.equalsIgnoreCase("n") || next.equalsIgnoreCase("0")) { - return false; - } else { - throw new ArgumentParseException("Expected a boolean (yes/no), got '" + next + "'"); - } - } - public static class Parser { - private boolean parseFlags = true; private boolean usingHangingArguments = false; - private Set valueFlags = Sets.newHashSet(); - - public boolean isParseFlags() { - return parseFlags; - } - - public Parser setParseFlags(boolean parseFlags) { - this.parseFlags = parseFlags; - return this; - } public boolean isUsingHangingArguments() { return usingHangingArguments; @@ -318,17 +145,8 @@ public class CommandArgs { return this; } - public Set getValueFlags() { - return valueFlags; - } - - public Parser setValueFlags(Set valueFlags) { - this.valueFlags = valueFlags; - return this; - } - public CommandArgs parse(String arguments) throws CommandException { - CommandContext context = new CommandContext(CommandContext.split("_ " + arguments), valueFlags, false, null, parseFlags); + CommandContext context = new CommandContext(CommandContext.split("_ " + arguments), Collections.emptySet(), false, null, false); List args = Lists.newArrayList(); for (int i = 0; i < context.argsLength(); i++) { args.add(context.getString(i)); @@ -338,11 +156,7 @@ public class CommandArgs { args.add(""); } } - Map flags = Maps.newHashMap(context.getValueFlags()); - for (Character c : context.getFlags()) { - flags.put(c, null); - } - return new CommandArgs(args, flags); + return new CommandArgs(args); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/FlagParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/FlagParser.java new file mode 100644 index 000000000..2868a3ba8 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/FlagParser.java @@ -0,0 +1,196 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.command.composition; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandLocals; +import com.sk89q.worldedit.util.command.argument.CommandArgs; +import com.sk89q.worldedit.util.command.composition.FlagParser.FlagData; +import com.sk89q.worldedit.util.command.argument.MissingArgumentException; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class FlagParser implements CommandExecutor { + + private final Map> flags = Maps.newHashMap(); + + public Flag registerFlag(char flag, CommandExecutor executor) { + Flag ret = new Flag(flag); + flags.put(flag, executor); + return ret; + } + + @Override + public FlagData call(CommandArgs args, CommandLocals locals) throws CommandException { + Map values = Maps.newHashMap(); + try { + while (true) { + String next = args.peek(); + if (next.equals("--")) { + args.next(); + break; + } else if (next.length() > 0 && next.charAt(0) == '-') { + args.next(); + + if (next.length() == 1) { + throw new CommandException("- must be followed by a flag (like -a), otherwise use -- before the - (i.e. /cmd -- - is a dash)."); + } else { + for (int i = 1; i < next.length(); i++) { + char flag = next.charAt(i); + CommandExecutor executor = flags.get(flag); + if (executor != null) { + values.put(flag, executor.call(args, locals)); + } else { + throw new CommandException("Unknown flag: -" + flag + " (try one of -" + Joiner.on("").join(flags.keySet()) + " or put -- to skip flag parsing, i.e. /cmd -- -this begins with a dash)."); + } + } + } + } else { + break; + } + } + } catch (MissingArgumentException ignored) { + } + + return new FlagData(values); + } + + @Override + public List getSuggestions(CommandArgs args, CommandLocals locals) throws MissingArgumentException { + List suggestions = Collections.emptyList(); + + while (true) { + String next = args.peek(); + if (next.equals("--")) { + args.next(); + break; + } else if (next.length() > 0 && next.charAt(0) == '-') { + args.next(); + + if (!args.hasNext()) { // Completing -| or -???| + List flagSuggestions = Lists.newArrayList(); + for (Character flag : flags.keySet()) { + if (next.indexOf(flag) < 1) { // Don't add any flags that the user has entered + flagSuggestions.add(next + flag); + } + } + return flagSuggestions; + } else { // Completing -??? ???| + for (int i = 1; i < next.length(); i++) { + char flag = next.charAt(i); + CommandExecutor executor = flags.get(flag); + if (executor != null) { + suggestions = executor.getSuggestions(args, locals); + } else { + return suggestions; + } + } + } + } else { + return suggestions; + } + } + + return suggestions; + } + + @Override + public String getUsage() { + List options = Lists.newArrayList(); + for (Entry> entry : flags.entrySet()) { + String usage = entry.getValue().getUsage(); + options.add("[-" + entry.getKey() + (!usage.isEmpty() ? " " + usage : "") + "]"); + } + return Joiner.on(" ").join(options); + } + + @Override + public String getDescription() { + return "Read flags"; + } + + @Override + public boolean testPermission(CommandLocals locals) { + for (CommandExecutor executor : flags.values()) { + if (!executor.testPermission(locals)) { + return false; + } + } + + return true; + } + + public static class FlagData { + private final Map data; + + private FlagData(Map data) { + this.data = data; + } + + public int size() { + return data.size(); + } + + public boolean isEmpty() { + return data.isEmpty(); + } + + public Object get(char key) { + return data.get(key); + } + + public boolean containsKey(char key) { + return data.containsKey(key); + } + + } + + public static final class Flag { + private final char flag; + + private Flag(char flag) { + this.flag = flag; + } + + @SuppressWarnings("unchecked") + @Nullable + public T get(FlagData data) { + return (T) data.get(flag); + } + + @SuppressWarnings("unchecked") + public T get(FlagData data, T fallback) { + T value = get(data); + if (value == null) { + return fallback; + } else { + return value; + } + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/LegacyCommandAdapter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/LegacyCommandAdapter.java index e6c69fdeb..79c569a71 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/LegacyCommandAdapter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/LegacyCommandAdapter.java @@ -42,17 +42,20 @@ public class LegacyCommandAdapter implements CommandCallable { @Override public final Object call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException { CommandArgs args = new CommandArgs.Parser().parse(arguments); - if (args.containsFlag('?')) { - throw new CommandException(executor.getUsage()); - } else { - Object ret = executor.call(args, locals); - try { - args.requireAllConsumed(); - } catch (UnusedArgumentsException e) { - throw new CommandException(e.getMessage()); + + if (args.hasNext()) { + if (args.uncheckedPeek().equals("-?")) { + throw new CommandException(executor.getUsage()); } - return ret; } + + Object ret = executor.call(args, locals); + try { + args.requireAllConsumed(); + } catch (UnusedArgumentsException e) { + throw new CommandException(e.getMessage()); + } + return ret; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/ParameterCommand.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/ParameterCommand.java index 542514ce1..8580f4f6d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/ParameterCommand.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/composition/ParameterCommand.java @@ -22,28 +22,44 @@ package com.sk89q.worldedit.util.command.composition; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.sk89q.minecraft.util.commands.CommandLocals; -import com.sk89q.worldedit.util.command.composition.CommandExecutor; +import com.sk89q.worldedit.util.command.composition.FlagParser.Flag; import java.util.List; public abstract class ParameterCommand implements CommandExecutor { private final List> parameters = Lists.newArrayList(); + private final FlagParser flagParser = new FlagParser(); + + public ParameterCommand() { + addParameter(flagParser); + } protected List> getParameters() { return parameters; } - public > T addParameter(T executor) { + public > E addParameter(E executor) { parameters.add(executor); return executor; } + public Flag addFlag(char flag, CommandExecutor executor) { + return flagParser.registerFlag(flag, executor); + } + + protected FlagParser getFlagParser() { + return flagParser; + } + @Override public final String getUsage() { List parts = Lists.newArrayList(); for (CommandExecutor executor : parameters) { - parts.add(executor.getUsage()); + String usage = executor.getUsage(); + if (!usage.isEmpty()) { + parts.add(executor.getUsage()); + } } return Joiner.on(" ").join(parts); }