Implemented -? and changed handling of InvalidUsageException.

Fixes WORLDEDIT-2947.
This commit is contained in:
sk89q 2014-06-30 22:48:04 -07:00
parent c29ca03e35
commit 11d37bce2b
8 changed files with 138 additions and 63 deletions

View File

@ -145,7 +145,7 @@ public class CommandContext {
suggestionContext = SuggestionContext.hangingValue(); suggestionContext = SuggestionContext.hangingValue();
// Not a flag? // Not a flag?
if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) { if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z\\?]+$")) {
if (!isHanging) { if (!isHanging) {
suggestionContext = SuggestionContext.lastValue(); suggestionContext = SuggestionContext.lastValue();
} }

View File

@ -66,7 +66,7 @@ public class CommandException extends Exception {
} }
builder.append(spacedSuffix); builder.append(spacedSuffix);
} }
return builder.toString(); return builder.toString().trim();
} }
} }

View File

@ -643,8 +643,7 @@ public class UtilityCommands {
actor.printRaw(ColorCodeBuilder.asColorCodes(box)); actor.printRaw(ColorCodeBuilder.asColorCodes(box));
} else { } else {
String title = String.format("Help: %s", Joiner.on(" ").join(visited)); CommandUsageBox box = new CommandUsageBox(callable, Joiner.on(" ").join(visited));
CommandUsageBox box = new CommandUsageBox(callable.getDescription(), title);
actor.printRaw(ColorCodeBuilder.asColorCodes(box)); actor.printRaw(ColorCodeBuilder.asColorCodes(box));
} }
} }

View File

@ -42,6 +42,8 @@ import com.sk89q.worldedit.util.command.fluent.CommandGraph;
import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler; import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
import com.sk89q.worldedit.util.formatting.components.CommandUsageBox;
import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
import com.sk89q.worldedit.util.logging.LogFormat; import com.sk89q.worldedit.util.logging.LogFormat;
@ -220,7 +222,17 @@ public final class CommandManager {
} catch (CommandPermissionsException e) { } catch (CommandPermissionsException e) {
actor.printError("You don't have permission to do this."); actor.printError("You don't have permission to do this.");
} catch (InvalidUsageException e) { } catch (InvalidUsageException e) {
actor.printError(e.getMessage() + "\nUsage: " + e.getUsage("/")); if (e.isFullUsageSuggested()) {
actor.printRaw(ColorCodeBuilder.asColorCodes(new CommandUsageBox(e.getCommand(), e.toStackString("/", ""), locals)));
String message = e.getMessage();
if (message != null) {
actor.printError(message);
}
} else {
String message = e.getMessage();
actor.printError(message != null ? message : "The command was not used properly (no more help available).");
actor.printError(e.getUsage("/"));
}
} catch (WrappedCommandException e) { } catch (WrappedCommandException e) {
Throwable t = e.getCause(); Throwable t = e.getCause();
actor.printError("Please report this error: [See console]"); actor.printError("Please report this error: [See console]");

View File

@ -21,29 +21,46 @@ package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandException;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Thrown when a command is not used properly. * Thrown when a command is not used properly.
*/ */
public class InvalidUsageException extends CommandException { public class InvalidUsageException extends CommandException {
private static final long serialVersionUID = -3222004168669490390L; private static final long serialVersionUID = -3222004168669490390L;
private final Description description; private final CommandCallable command;
private final boolean fullUsageSuggested;
public InvalidUsageException(Description description) { public InvalidUsageException(CommandCallable command) {
this.description = description; this(null, command);
} }
public InvalidUsageException(String message, Description description) { public InvalidUsageException(String message, CommandCallable command) {
this(message, command, false);
}
public InvalidUsageException(String message, CommandCallable command, boolean fullUsageSuggested) {
super(message); super(message);
this.description = description; checkNotNull(command);
this.command = command;
this.fullUsageSuggested = fullUsageSuggested;
} }
public Description getDescription() { public CommandCallable getCommand() {
return description; return command;
} }
public String getUsage(String prefix) { public String getUsage(String prefix) {
return toStackString(prefix, getDescription().getUsage()); return toStackString(prefix, command.getDescription().getUsage());
} }
/**
* Return whether the full usage of the command should be shown.
*
* @return show full usage
*/
public boolean isFullUsageSuggested() {
return fullUsageSuggested;
}
} }

View File

@ -20,12 +20,20 @@
package com.sk89q.worldedit.util.command; package com.sk89q.worldedit.util.command;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.sk89q.minecraft.util.commands.*; import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder; import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.worldedit.util.formatting.components.CommandListBox; import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.util.formatting.StyledFragment; import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* A simple implementation of {@link Dispatcher}. * A simple implementation of {@link Dispatcher}.
@ -109,7 +117,7 @@ public class SimpleDispatcher implements Dispatcher {
Set<String> aliases = getPrimaryAliases(); Set<String> aliases = getPrimaryAliases();
if (aliases.isEmpty()) { if (aliases.isEmpty()) {
throw new InvalidUsageException("This command has no sub-commands.", getDescription()); throw new InvalidUsageException("This command has no sub-commands.", this);
} else if (split.length > 0) { } else if (split.length > 0) {
String subCommand = split[0]; String subCommand = split[0];
String subArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length)); String subArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length));
@ -132,7 +140,7 @@ public class SimpleDispatcher implements Dispatcher {
} }
throw new InvalidUsageException(ColorCodeBuilder.asColorCodes(getSubcommandList(locals, parentCommands)), getDescription()); throw new InvalidUsageException("Please choose a sub-command.", this, true);
} }
@Override @Override
@ -185,16 +193,4 @@ public class SimpleDispatcher implements Dispatcher {
return false; return false;
} }
private StyledFragment getSubcommandList(CommandLocals locals, String[] parentCommands) {
CommandListBox box = new CommandListBox("Subcommands");
String prefix = parentCommands.length > 0 ? "/" + Joiner.on(" ").join(parentCommands) + " " : "";
for (CommandMapping mapping : getCommands()) {
if (mapping.getCallable().testPermission(locals)) {
box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription());
}
}
return box;
}
} }

View File

@ -19,15 +19,32 @@
package com.sk89q.worldedit.util.command.parametric; package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.*; import com.google.common.base.Joiner;
import com.sk89q.worldedit.util.command.*; import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.InvalidUsageException;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.worldedit.util.command.Parameter;
import com.sk89q.worldedit.util.command.SimpleDescription;
import com.sk89q.worldedit.util.command.UnconsumedParameterException;
import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.binding.Switch;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
* The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}. * The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}.
@ -169,6 +186,12 @@ class ParametricCallable implements CommandCallable {
String[] split = CommandContext.split(calledCommand + " " + stringArguments); String[] split = CommandContext.split(calledCommand + " " + stringArguments);
CommandContext context = new CommandContext(split, getValueFlags(), false, locals); CommandContext context = new CommandContext(split, getValueFlags(), false, locals);
// Provide help if -? is specified
if (context.hasFlag('?')) {
String title = Joiner.on(" ").join(parentCommands);
throw new InvalidUsageException(null, this, true);
}
Object[] args = new Object[parameters.length]; Object[] args = new Object[parameters.length];
ContextArgumentStack arguments = new ContextArgumentStack(context); ContextArgumentStack arguments = new ContextArgumentStack(context);
ParameterData parameter = null; ParameterData parameter = null;
@ -221,14 +244,14 @@ class ParametricCallable implements CommandCallable {
handler.postInvoke(handler, method, parameters, args, context); handler.postInvoke(handler, method, parameters, args, context);
} }
} catch (MissingParameterException e) { } catch (MissingParameterException e) {
throw new InvalidUsageException("Too few parameters!", getDescription()); throw new InvalidUsageException("Too few parameters!", this);
} catch (UnconsumedParameterException e) { } catch (UnconsumedParameterException e) {
throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), getDescription()); throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), this);
} catch (ParameterException e) { } catch (ParameterException e) {
assert parameter != null; assert parameter != null;
String name = parameter.getName(); String name = parameter.getName();
throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), getDescription()); throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
for (ExceptionConverter converter : builder.getExceptionConverters()) { for (ExceptionConverter converter : builder.getExceptionConverters()) {
converter.convert(e.getCause()); converter.convert(e.getCause());

View File

@ -19,56 +19,84 @@
package com.sk89q.worldedit.util.formatting.components; package com.sk89q.worldedit.util.formatting.components;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.CommandMapping;
import com.sk89q.worldedit.util.command.Description; import com.sk89q.worldedit.util.command.Description;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.formatting.Style; import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* A box to describe usage of a command. * A box to describe usage of a command.
*/ */
public class CommandUsageBox extends MessageBox { public class CommandUsageBox extends StyledFragment {
/** /**
* Create a new box. * Create a new usage box.
* *
* @param description the command to describe * @param command the command to describe
* @param title the title * @param commandString the command that was used, such as "/we" or "/brush sphere"
*/ */
public CommandUsageBox(Description description, String title) { public CommandUsageBox(CommandCallable command, String commandString) {
super(title); this(command, commandString, null);
checkNotNull(description);
attachCommandUsage(description);
} }
/** /**
* Create a new box. * Create a new usage box.
* *
* @param description the command to describe * @param command the command to describe
* @param commandString the command that was used, such as "/we" or "/brush sphere"
* @param locals list of locals to use
*/ */
public CommandUsageBox(Description description) { public CommandUsageBox(CommandCallable command, String commandString, @Nullable CommandLocals locals) {
super("Usage Help"); checkNotNull(command);
checkNotNull(description); checkNotNull(commandString);
attachCommandUsage(description); if (command instanceof Dispatcher) {
} attachDispatcherUsage((Dispatcher) command, commandString, locals);
private void attachCommandUsage(Description description) {
if (description.getUsage() != null) {
getContents().append(new Label().append("Usage: "));
getContents().append(description.getUsage());
} else { } else {
getContents().append(new Subtle().append("Usage information is not available.")); attachCommandUsage(command.getDescription(), commandString);
}
} }
getContents().newLine(); private void attachDispatcherUsage(Dispatcher dispatcher, String commandString, @Nullable CommandLocals locals) {
CommandListBox box = new CommandListBox("Subcommands");
String prefix = !commandString.isEmpty() ? commandString + " " : "";
for (CommandMapping mapping : dispatcher.getCommands()) {
if (locals == null || mapping.getCallable().testPermission(locals)) {
box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription());
}
}
append(box);
}
private void attachCommandUsage(Description description, String commandString) {
MessageBox box = new MessageBox("Help for " + commandString);
StyledFragment contents = box.getContents();
if (description.getUsage() != null) {
contents.append(new Label().append("Usage: "));
contents.append(description.getUsage());
} else {
contents.append(new Subtle().append("Usage information is not available."));
}
contents.newLine();
if (description.getHelp() != null) { if (description.getHelp() != null) {
getContents().createFragment(Style.YELLOW_DARK).append(description.getHelp()); contents.createFragment(Style.YELLOW_DARK).append(description.getHelp());
} else if (description.getShortDescription() != null) { } else if (description.getShortDescription() != null) {
getContents().createFragment(Style.YELLOW_DARK).append(description.getShortDescription()); contents.append(description.getShortDescription());
} else { } else {
getContents().append(new Subtle().append("No further help is available.")); contents.append(new Subtle().append("No further help is available."));
} }
append(box);
} }
} }