Merge branch 'master' into feature/mapping

This commit is contained in:
sk89q 2014-07-01 12:59:55 -07:00
commit 7a5ea73c57
28 changed files with 775 additions and 185 deletions

36
pom.xml
View File

@ -355,24 +355,6 @@
</configuration> </configuration>
</plugin> </plugin>
<!-- Assembly builds .zip, etc. -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-2</version>
<configuration>
<descriptor>${basedir}/src/main/assembly/default.xml</descriptor>
</configuration>
<executions>
<execution>
<id>release</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Shades --> <!-- Shades -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -396,6 +378,24 @@
</executions> </executions>
</plugin> </plugin>
<!-- Assembly builds .zip, etc. -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-2</version>
<configuration>
<descriptor>${basedir}/src/main/assembly/default.xml</descriptor>
</configuration>
<executions>
<execution>
<id>release</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Release plugin --> <!-- Release plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -30,12 +30,12 @@ import java.io.File;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
class BukkitCommandSender implements Actor { public class BukkitCommandSender implements Actor {
private CommandSender sender; private CommandSender sender;
private WorldEditPlugin plugin; private WorldEditPlugin plugin;
BukkitCommandSender(WorldEditPlugin plugin, CommandSender sender) { public BukkitCommandSender(WorldEditPlugin plugin, CommandSender sender) {
checkNotNull(plugin); checkNotNull(plugin);
checkNotNull(sender); checkNotNull(sender);
checkArgument(!(sender instanceof Player), "Cannot wrap a player"); checkArgument(!(sender instanceof Player), "Cannot wrap a player");

View File

@ -156,10 +156,12 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter {
*/ */
@Override @Override
public void onDisable() { public void onDisable() {
controller.clearSessions(); if (controller != null) {
controller.getPlatformManager().unregister(server); controller.clearSessions();
config.unload(); controller.getPlatformManager().unregister(server);
server.unregisterCommands(); config.unload();
server.unregisterCommands();
}
this.getServer().getScheduler().cancelTasks(this); this.getServer().getScheduler().cancelTasks(this);
} }

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

@ -19,10 +19,13 @@
package com.sk89q.minecraft.util.commands; package com.sk89q.minecraft.util.commands;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import static com.google.common.base.Preconditions.checkNotNull;
public class CommandException extends Exception { public class CommandException extends Exception {
private static final long serialVersionUID = 870638193072101739L; private static final long serialVersionUID = 870638193072101739L;
@ -48,7 +51,16 @@ public class CommandException extends Exception {
commandStack.add(name); commandStack.add(name);
} }
public String toStackString(String prefix, String spacedSuffix) { /**
* Gets the command that was called, which will include the sub-command
* (i.e. "/br sphere").
*
* @param prefix the command shebang character (such as "/") -- may be empty
* @param spacedSuffix a suffix to put at the end (optional) -- may be null
* @return the command that was used
*/
public String getCommandUsed(String prefix, @Nullable String spacedSuffix) {
checkNotNull(prefix);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
if (prefix != null) { if (prefix != null) {
builder.append(prefix); builder.append(prefix);
@ -66,7 +78,7 @@ public class CommandException extends Exception {
} }
builder.append(spacedSuffix); builder.append(spacedSuffix);
} }
return builder.toString(); return builder.toString().trim();
} }
} }

View File

@ -131,7 +131,7 @@ public class RegionCommands {
desc = "Draws a spline through selected points", desc = "Draws a spline through selected points",
help = help =
"Draws a spline through selected points.\n" + "Draws a spline through selected points.\n" +
"Can only be uesd with convex polyhedral selections.\n" + "Can only be used with convex polyhedral selections.\n" +
"Flags:\n" + "Flags:\n" +
" -h generates only a shell", " -h generates only a shell",
flags = "h", flags = "h",

View File

@ -60,8 +60,8 @@ public class SchematicCommands {
@Command( @Command(
aliases = { "load", "l" }, aliases = { "load", "l" },
usage = "[format] <filename>", usage = "[format] <filename>",
desc = "Load a schematic into your clipboard", desc = "Load a file into your clipboard",
help = "Load a schematic into your clipboard\n" + help = "Load a schematic file into your clipboard\n" +
"Format is a format from \"//schematic formats\"\n" + "Format is a format from \"//schematic formats\"\n" +
"If the format is not provided, WorldEdit will\n" + "If the format is not provided, WorldEdit will\n" +
"attempt to automatically detect the format of the schematic", "attempt to automatically detect the format of the schematic",
@ -78,8 +78,8 @@ public class SchematicCommands {
@Command( @Command(
aliases = { "save", "s" }, aliases = { "save", "s" },
usage = "[format] <filename>", usage = "[format] <filename>",
desc = "Save a schematic into your clipboard", desc = "Save your clipboard to file",
help = "Save a schematic into your clipboard\n" + help = "Save your clipboard to file\n" +
"Format is a format from \"//schematic formats\"\n", "Format is a format from \"//schematic formats\"\n",
min = 1, min = 1,
max = 2 max = 2
@ -93,7 +93,7 @@ public class SchematicCommands {
@Command( @Command(
aliases = { "delete", "d" }, aliases = { "delete", "d" },
usage = "<filename>", usage = "<filename>",
desc = "Delete a schematic from the schematic list", desc = "Delete a saved schematic",
help = "Delete a schematic from the schematic list", help = "Delete a schematic from the schematic list",
min = 1, min = 1,
max = 1 max = 1
@ -122,7 +122,7 @@ public class SchematicCommands {
@Command( @Command(
aliases = {"formats", "listformats", "f"}, aliases = {"formats", "listformats", "f"},
desc = "List available schematic formats", desc = "List available formats",
max = 0 max = 0
) )
@CommandPermissions("worldedit.schematic.formats") @CommandPermissions("worldedit.schematic.formats")
@ -147,7 +147,7 @@ public class SchematicCommands {
@Command( @Command(
aliases = {"list", "all", "ls"}, aliases = {"list", "all", "ls"},
desc = "List available schematics", desc = "List saved schematics",
max = 0, max = 0,
flags = "dn", flags = "dn",
help = "List all schematics in the schematics directory\n" + help = "List all schematics in the schematics directory\n" +

View File

@ -19,26 +19,44 @@
package com.sk89q.worldedit.command; package com.sk89q.worldedit.command;
import com.sk89q.minecraft.util.commands.*; import com.google.common.base.Joiner;
import com.sk89q.worldedit.*; import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.Logging;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EntityType;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.LocalWorld.KillFlags; import com.sk89q.worldedit.LocalWorld.KillFlags;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.patterns.Pattern; import com.sk89q.worldedit.patterns.Pattern;
import com.sk89q.worldedit.patterns.SingleBlockPattern; import com.sk89q.worldedit.patterns.SingleBlockPattern;
import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.CommandMapping; import com.sk89q.worldedit.util.command.CommandMapping;
import com.sk89q.worldedit.util.command.Description;
import com.sk89q.worldedit.util.command.Dispatcher; import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.util.command.parametric.Optional;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import com.sk89q.worldedit.util.formatting.component.Code;
import com.sk89q.worldedit.util.formatting.component.CommandListBox;
import com.sk89q.worldedit.util.formatting.component.CommandUsageBox;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import java.util.Comparator; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT; import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT;
@ -497,59 +515,137 @@ public class UtilityCommands {
help(args, we, actor); help(args, we, actor);
} }
public static void help(CommandContext args, WorldEdit we, Actor actor) { private static CommandMapping detectCommand(Dispatcher dispatcher, String command, boolean isRootLevel) {
final Dispatcher dispatcher = we.getPlatformManager().getCommandManager().getDispatcher(); CommandMapping mapping;
if (args.argsLength() == 0) { // First try the command as entered
SortedSet<String> commands = new TreeSet<String>(new Comparator<String>() { mapping = dispatcher.get(command);
@Override if (mapping != null) {
public int compare(String o1, String o2) { return mapping;
final int ret = o1.replaceAll("/", "").compareToIgnoreCase(o2.replaceAll("/", "")); }
if (ret == 0) {
return o1.compareToIgnoreCase(o2);
}
return ret;
}
});
commands.addAll(dispatcher.getPrimaryAliases());
StringBuilder sb = new StringBuilder(); // Then if we're looking at root commands and the user didn't use
boolean first = true; // any slashes, let's try double slashes and then single slashes.
for (String command : commands) { // However, be aware that there exists different single slash
if (!first) { // and double slash commands in WorldEdit
sb.append(", "); if (isRootLevel && !command.contains("/")) {
} mapping = dispatcher.get("//" + command);
if (mapping != null) {
sb.append('/'); return mapping;
sb.append(command);
first = false;
} }
actor.print(sb.toString()); mapping = dispatcher.get("/" + command);
if (mapping != null) {
return; return mapping;
}
} }
String command = args.getJoinedStrings(0).toLowerCase().replaceAll("^/", ""); return null;
CommandMapping mapping = dispatcher.get(command); }
if (mapping == null) { public static void help(CommandContext args, WorldEdit we, Actor actor) {
actor.printError("Unknown command '" + command + "'."); CommandCallable callable = we.getPlatformManager().getCommandManager().getDispatcher();
return;
int page = 0;
final int perPage = actor instanceof Player ? 8 : 20; // More pages for console
int effectiveLength = args.argsLength();
// Detect page from args
try {
if (args.argsLength() > 0) {
page = args.getInteger(args.argsLength() - 1);
if (page <= 0) {
page = 1;
} else {
page--;
}
effectiveLength--;
}
} catch (NumberFormatException ignored) {
} }
Description description = mapping.getDescription(); boolean isRootLevel = true;
List<String> visited = new ArrayList<String>();
if (description.getUsage() != null) { // Drill down to the command
actor.printDebug("Usage: " + description.getUsage()); for (int i = 0; i < effectiveLength; i++) {
String command = args.getString(i);
if (callable instanceof Dispatcher) {
// Chop off the beginning / if we're are the root level
if (isRootLevel && command.length() > 1 && command.charAt(0) == '/') {
command = command.substring(1);
}
CommandMapping mapping = detectCommand((Dispatcher) callable, command, isRootLevel);
if (mapping != null) {
callable = mapping.getCallable();
} else {
if (isRootLevel) {
actor.printError(String.format("The command '%s' could not be found.", args.getString(i)));
return;
} else {
actor.printError(String.format("The sub-command '%s' under '%s' could not be found.",
command, Joiner.on(" ").join(visited)));
return;
}
}
visited.add(args.getString(i));
isRootLevel = false;
} else {
actor.printError(String.format("'%s' has no sub-commands. (Maybe '%s' is for a parameter?)",
Joiner.on(" ").join(visited), command));
return;
}
} }
if (description.getHelp() != null) { // Create the message
actor.print(description.getHelp()); if (callable instanceof Dispatcher) {
} else if (description.getShortDescription() != null) { Dispatcher dispatcher = (Dispatcher) callable;
actor.print(description.getShortDescription());
// Get a list of aliases
List<CommandMapping> aliases = new ArrayList<CommandMapping>(dispatcher.getCommands());
Collections.sort(aliases, new PrimaryAliasComparator(CommandManager.COMMAND_CLEAN_PATTERN));
// Calculate pagination
int offset = perPage * page;
int pageTotal = (int) Math.ceil(aliases.size() / (double) perPage);
// Box
CommandListBox box = new CommandListBox(String.format("Help: page %d/%d ", page + 1, pageTotal));
StyledFragment contents = box.getContents();
StyledFragment tip = contents.createFragment(Style.GRAY);
if (offset >= aliases.size()) {
tip.createFragment(Style.RED).append(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal)).newLine();
} else {
List<CommandMapping> list = aliases.subList(offset, Math.min(offset + perPage, aliases.size()));
tip.append("Type ");
tip.append(new Code().append("//help ").append("<command> [<page>]"));
tip.append(" for more information.").newLine();
// Add each command
for (CommandMapping mapping : list) {
StringBuilder builder = new StringBuilder();
if (isRootLevel) {
builder.append("/");
}
if (!visited.isEmpty()) {
builder.append(Joiner.on(" ").join(visited));
builder.append(" ");
}
builder.append(mapping.getPrimaryAlias());
box.appendCommand(builder.toString(), mapping.getDescription().getShortDescription());
}
}
actor.printRaw(ColorCodeBuilder.asColorCodes(box));
} else { } else {
actor.print("No further help is available."); CommandUsageBox box = new CommandUsageBox(callable, Joiner.on(" ").join(visited));
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.component.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;
@ -50,6 +52,7 @@ import java.io.IOException;
import java.util.logging.FileHandler; import java.util.logging.FileHandler;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -60,6 +63,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public final class CommandManager { public final class CommandManager {
public static final Pattern COMMAND_CLEAN_PATTERN = Pattern.compile("^[/]+");
private static final Logger logger = Logger.getLogger(CommandManager.class.getCanonicalName()); private static final Logger logger = Logger.getLogger(CommandManager.class.getCanonicalName());
private static final java.util.regex.Pattern numberFormatExceptionPattern = java.util.regex.Pattern.compile("^For input string: \"(.*)\"$"); private static final java.util.regex.Pattern numberFormatExceptionPattern = java.util.regex.Pattern.compile("^For input string: \"(.*)\"$");
@ -220,7 +224,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.isFullHelpSuggested()) {
actor.printRaw(ColorCodeBuilder.asColorCodes(new CommandUsageBox(e.getCommand(), e.getCommandUsed("/", ""), 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("Usage: " + e.getSimpleUsageString("/"));
}
} 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,8 +21,10 @@ package com.sk89q.worldedit.math.transform;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -60,6 +62,15 @@ public class CombinedTransform implements Transform {
return vector; return vector;
} }
@Override
public Transform inverse() {
List<Transform> list = new ArrayList<Transform>();
for (int i = transforms.length - 1; i >= 0; i--) {
list.add(transforms[i].inverse());
}
return new CombinedTransform(list);
}
@Override @Override
public Transform combine(Transform other) { public Transform combine(Transform other) {
checkNotNull(other); checkNotNull(other);

View File

@ -31,12 +31,17 @@ public class Identity implements Transform {
return vector; return vector;
} }
@Override
public Transform inverse() {
return this;
}
@Override @Override
public Transform combine(Transform other) { public Transform combine(Transform other) {
if (other instanceof Identity) { if (other instanceof Identity) {
return this; return this;
} else { } else {
return new CombinedTransform(this, other); return other;
} }
} }
} }

View File

@ -27,6 +27,13 @@ import com.sk89q.worldedit.Vector;
*/ */
public interface Transform extends Function<Vector, Vector> { public interface Transform extends Function<Vector, Vector> {
/**
* Create a new inverse transform.
*
* @return a new inverse transform
*/
Transform inverse();
/** /**
* Create a new {@link Transform} that combines this transform with another. * Create a new {@link Transform} that combines this transform with another.
* *

View File

@ -0,0 +1,49 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.math.transform;
import com.sk89q.worldedit.util.Location;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Various utility methods related to {@link Transform}s.
*/
public final class Transforms {
private Transforms() {
}
/**
* Transform a location's position with a given transform.
* </p>
* Direction is unaffected.
*
* @param location the location
* @param transform the transform
* @return the transformed location
*/
public static Location transform(Location location, Transform transform) {
checkNotNull(location);
checkNotNull(transform);
return new Location(location.getWorld(), transform.apply(location.toVector()), location.getDirection());
}
}

View File

@ -23,20 +23,12 @@ import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals; import com.sk89q.minecraft.util.commands.CommandLocals;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* A command that can be executed. * A command that can be executed.
*/ */
public interface CommandCallable { public interface CommandCallable {
/**
* Get a list of value flags used by this command.
*
* @return a list of value flags
*/
Set<Character> getValueFlags();
/** /**
* Execute the correct command based on the input. * Execute the correct command based on the input.
* </p> * </p>

View File

@ -19,70 +19,37 @@
package com.sk89q.worldedit.util.command; package com.sk89q.worldedit.util.command;
import java.util.Arrays;
/** /**
* Tracks a command registration. * Provides information about a mapping between a command and its aliases.
*/ */
public class CommandMapping { public interface CommandMapping {
private final String[] aliases;
private final CommandCallable callable;
/**
* Create a new instance.
*
* @param callable the command callable
* @param alias a list of all aliases, where the first one is the primary one
*/
public CommandMapping(CommandCallable callable, String... alias) {
super();
this.aliases = alias;
this.callable = callable;
}
/** /**
* Get the primary alias. * Get the primary alias.
* *
* @return the primary alias * @return the primary alias
*/ */
public String getPrimaryAlias() { String getPrimaryAlias();
return aliases[0];
}
/** /**
* Get a list of all aliases. * Get a list of all aliases.
* *
* @return aliases * @return aliases
*/ */
public String[] getAllAliases() { String[] getAllAliases();
return aliases;
}
/** /**
* Get the callable * Get the callable
* *
* @return the callable * @return the callable
*/ */
public CommandCallable getCallable() { CommandCallable getCallable();
return callable;
}
/** /**
* Get the {@link Description} form the callable. * Get the {@link Description} form the callable.
* *
* @return the description * @return the description
*/ */
public Description getDescription() { Description getDescription();
return getCallable().getDescription();
}
@Override
public String toString() {
return "CommandMapping{" +
"aliases=" + Arrays.toString(aliases) +
", callable=" + callable +
'}';
}
} }

View File

@ -21,29 +21,87 @@ package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandException;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Thrown when a command is not used properly. * Thrown when a command is not used properly.
* </p>
* When handling this exception, print the error message if it is not null.
* Print a one line help instruction unless {@link #isFullHelpSuggested()}
* is true, which, in that case, the full help of the command should be shown.
* </p>
* If no error message is set and full help is not to be shown, then a generic
* "you used this command incorrectly" message should be shown.
*/ */
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 fullHelpSuggested;
public InvalidUsageException(Description description) { /**
this.description = description; * Create a new instance with no error message and with no suggestion
* that full and complete help for the command should be shown. This will
* result in a generic error message.
*
* @param command the command
*/
public InvalidUsageException(CommandCallable command) {
this(null, command);
} }
public InvalidUsageException(String message, Description description) { /**
* Create a new instance with a message and with no suggestion
* that full and complete help for the command should be shown.
*
* @param message the message
* @param command the command
*/
public InvalidUsageException(@Nullable String message, CommandCallable command) {
this(message, command, false);
}
/**
* Create a new instance with a message.
*
* @param message the message
* @param command the command
* @param fullHelpSuggested true if the full help for the command should be shown
*/
public InvalidUsageException(@Nullable String message, CommandCallable command, boolean fullHelpSuggested) {
super(message); super(message);
this.description = description; checkNotNull(command);
this.command = command;
this.fullHelpSuggested = fullHelpSuggested;
} }
public Description getDescription() { /**
return description; * Get the command.
*
* @return the command
*/
public CommandCallable getCommand() {
return command;
} }
public String getUsage(String prefix) { /**
return toStackString(prefix, getDescription().getUsage()); * Get a simple usage string.
*
* @param prefix the command shebang (such as "/") -- may be blank
* @return a usage string
*/
public String getSimpleUsageString(String prefix) {
return getCommandUsed(prefix, command.getDescription().getUsage());
} }
/**
* Return whether the full usage of the command should be shown.
*
* @return show full usage
*/
public boolean isFullHelpSuggested() {
return fullHelpSuggested;
}
} }

View File

@ -0,0 +1,59 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import javax.annotation.Nullable;
import java.util.Comparator;
import java.util.regex.Pattern;
/**
* Compares the primary aliases of two {@link CommandMapping} using
* {@link String#compareTo(String)}.
*/
public final class PrimaryAliasComparator implements Comparator<CommandMapping> {
/**
* An instance of this class.
*/
public static final PrimaryAliasComparator INSTANCE = new PrimaryAliasComparator(null);
private final @Nullable Pattern removalPattern;
/**
* Create a new instance.
*
* @param removalPattern a regex to remove unwanted characters from the compared aliases
*/
public PrimaryAliasComparator(@Nullable Pattern removalPattern) {
this.removalPattern = removalPattern;
}
private String clean(String alias) {
if (removalPattern != null) {
return removalPattern.matcher(alias).replaceAll("");
}
return alias;
}
@Override
public int compare(CommandMapping o1, CommandMapping o2) {
return clean(o1.getPrimaryAlias()).compareTo(clean(o2.getPrimaryAlias()));
}
}

View File

@ -0,0 +1,72 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import java.util.Arrays;
/**
* Tracks a command registration.
*/
public class SimpleCommandMapping implements CommandMapping {
private final String[] aliases;
private final CommandCallable callable;
/**
* Create a new instance.
*
* @param callable the command callable
* @param alias a list of all aliases, where the first one is the primary one
*/
public SimpleCommandMapping(CommandCallable callable, String... alias) {
super();
this.aliases = alias;
this.callable = callable;
}
@Override
public String getPrimaryAlias() {
return aliases[0];
}
@Override
public String[] getAllAliases() {
return aliases;
}
@Override
public CommandCallable getCallable() {
return callable;
}
@Override
public Description getDescription() {
return getCallable().getDescription();
}
@Override
public String toString() {
return "CommandMapping{" +
"aliases=" + Arrays.toString(aliases) +
", callable=" + callable +
'}';
}
}

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.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}.
@ -47,7 +55,7 @@ public class SimpleDispatcher implements Dispatcher {
@Override @Override
public void registerCommand(CommandCallable callable, String... alias) { public void registerCommand(CommandCallable callable, String... alias) {
CommandMapping mapping = new CommandMapping(callable, alias); CommandMapping mapping = new SimpleCommandMapping(callable, alias);
// Check for replacements // Check for replacements
for (String a : alias) { for (String a : alias) {
@ -93,11 +101,6 @@ public class SimpleDispatcher implements Dispatcher {
return commands.get(alias.toLowerCase()); return commands.get(alias.toLowerCase());
} }
@Override
public Set<Character> getValueFlags() {
return Collections.emptySet();
}
@Override @Override
public boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException { public boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException {
// We have permission for this command if we have permissions for subcommands // We have permission for this command if we have permissions for subcommands
@ -109,7 +112,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 +135,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 +188,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

@ -23,6 +23,7 @@ import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.worldedit.util.command.binding.PrimitiveBindings; import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
import com.sk89q.worldedit.util.command.binding.StandardBindings; import com.sk89q.worldedit.util.command.binding.StandardBindings;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List; import java.util.List;
@ -78,7 +79,7 @@ public interface Binding {
* @throws CommandException on a command exception * @throws CommandException on a command exception
*/ */
Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume) Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume)
throws ParameterException, CommandException; throws ParameterException, CommandException, InvocationTargetException;
/** /**
* Get a list of suggestions for the given parameter and user arguments. * Get a list of suggestions for the given parameter and user arguments.

View File

@ -143,7 +143,7 @@ public class BindingHelper implements Binding {
@Override @Override
public Object bind(ParameterData parameter, ArgumentStack scoped, public Object bind(ParameterData parameter, ArgumentStack scoped,
boolean onlyConsume) throws ParameterException, CommandException { boolean onlyConsume) throws ParameterException, CommandException, InvocationTargetException {
BoundMethod binding = match(parameter); BoundMethod binding = match(parameter);
List<Object> args = new ArrayList<Object>(); List<Object> args = new ArrayList<Object>();
args.add(scoped); args.add(scoped);
@ -178,7 +178,7 @@ public class BindingHelper implements Binding {
} else if (e.getCause() instanceof CommandException) { } else if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause(); throw (CommandException) e.getCause();
} }
throw new RuntimeException(e.getCause()); throw e;
} }
} }

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());
@ -250,7 +273,11 @@ class ParametricCallable implements CommandCallable {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override /**
* Get a list of value flags used by this command.
*
* @return a list of value flags
*/
public Set<Character> getValueFlags() { public Set<Character> getValueFlags() {
return valueFlags; return valueFlags;
} }
@ -344,7 +371,7 @@ class ParametricCallable implements CommandCallable {
* @throws CommandException on an error * @throws CommandException on an error
*/ */
private Object getDefaultValue(int i, ContextArgumentStack scoped) private Object getDefaultValue(int i, ContextArgumentStack scoped)
throws ParameterException, CommandException { throws ParameterException, CommandException, InvocationTargetException {
CommandContext context = scoped.getContext(); CommandContext context = scoped.getContext();
ParameterData parameter = parameters[i]; ParameterData parameter = parameters[i];

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a fragment representing a command that is to be typed.
*/
public class Code extends StyledFragment {
/**
* Create a new instance.
*/
public Code() {
super(Style.CYAN);
}
}

View File

@ -17,7 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.util.formatting; package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
public class CommandListBox extends MessageBox { public class CommandListBox extends MessageBox {

View File

@ -0,0 +1,110 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.extension.platform.CommandManager;
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.Dispatcher;
import com.sk89q.worldedit.util.command.PrimaryAliasComparator;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A box to describe usage of a command.
*/
public class CommandUsageBox extends StyledFragment {
/**
* Create a new usage box.
*
* @param command the command to describe
* @param commandString the command that was used, such as "/we" or "/brush sphere"
*/
public CommandUsageBox(CommandCallable command, String commandString) {
this(command, commandString, null);
}
/**
* Create a new usage box.
*
* @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(CommandCallable command, String commandString, @Nullable CommandLocals locals) {
checkNotNull(command);
checkNotNull(commandString);
if (command instanceof Dispatcher) {
attachDispatcherUsage((Dispatcher) command, commandString, locals);
} else {
attachCommandUsage(command.getDescription(), commandString);
}
}
private void attachDispatcherUsage(Dispatcher dispatcher, String commandString, @Nullable CommandLocals locals) {
CommandListBox box = new CommandListBox("Subcommands");
String prefix = !commandString.isEmpty() ? commandString + " " : "";
List<CommandMapping> list = new ArrayList<CommandMapping>(dispatcher.getCommands());
Collections.sort(list, new PrimaryAliasComparator(CommandManager.COMMAND_CLEAN_PATTERN));
for (CommandMapping mapping : list) {
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) {
contents.append(description.getHelp());
} else if (description.getShortDescription() != null) {
contents.append(description.getShortDescription());
} else {
contents.append(new Subtle().append("No further help is available."));
}
append(box);
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a fragment representing a label.
*/
public class Label extends StyledFragment {
/**
* Create a new instance.
*/
public Label() {
super(Style.YELLOW);
}
}

View File

@ -17,7 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.util.formatting; package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting.component;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
/**
* Represents a subtle part of the message.
*/
public class Subtle extends StyledFragment {
/**
* Create a new instance.
*/
public Subtle() {
super(Style.GRAY);
}
}