OptParse an idea

So I had this idea before the new Command stuff was released. I’ve since updated it to work with the latest version of sponge. I was planning on just not using it again, and using it only as private reference material. However I thought I should ask here first if anybody wants it; if they do I can put up the code.

Basically OptParse is a way of parsing command options, and arguments. It basically removes a lot of code that usually is extremely repetitive.

Let’s start with a basic TPPos command to show some of it’s uses:

public class BasicTPPosCommand extends OptParseCommand {

  private static final Text usage = Texts.of("tppos <x> [y] [z]");
  private static final Text help = Texts.of("Teleports you to a specified location. Based on 3 longs you enter.");
  private static final Text desc = Texts.of("Teleports the player to a specific location");

  public BasicTPPosCommand() {
    super(usage, Optional.of(help), Optional.of(desc));
  }

  @Override
  @ArgumentSpecifications(playerOnly = true,
      validArgs = {1, 2, 3}, argTypes = {@Argument(type = "Long", hasRange = true,
      range = @Range(max = 30000000L, min = -30000000L)), @Argument(type = "Integer", hasClamp = true,
      clamp = @Clamp(max = 255)), @Argument(type = "Long", hasRange = true,
      range = @Range(max = 30000000L, min = -30000000L))}) 
  public CommandResult onCommand(CommandSource source, List<CommandValue> args) {
    assert(args.size() == 1 || args.size() == 2 || args.size() == 3);

    Player p = (Player) source;

    CommandValue<Long> x;
    CommandValue<Integer> y;
    CommandValue<Long> z;

    if(args.size() == 1) {
      x = args.get(0);
      y = new CommandValue<Integer>((int) p.getLocation().getY());
      z = new CommandValue<Long>((long) p.getLocation().getZ());
    }else if(args.size() == 2) {
      x = args.get(0);
      y = args.get(1);
      z = new CommandValue<Long>((long) p.getLocation().getZ());
    }else {
      x = args.get(0);
      y = args.get(1);
      z = args.get(2);
    }
    p.setLocation(new Location(p.getLocation().getExtent(), new Vector3d(
        (double) x.getValue(), (double) y.getValue(), (double) z.getValue()
    )));
    return CommandResult.success();
  }
}

As you can see you simply extend optparse command. All of the other sponge functions are done in the backend for you (though you can override them if you want too). Because of how the OptParseCommand class is registered you can use it in the SpongeAPI just like a CommandCallable they work perfectly together. (The only method you can’t override is the: process(CommandSource source, String arguments) method due to thats where OptParse command does it’s parsing to pass to the onCommand() method.

Anyway here is the argument specifications broken down:

@ArgumentSpecifications(playerOnly = true,
      validArgs = {1, 2, 3}, argTypes = {@Argument(type = "Long", hasRange = true,
      range = @Range(max = 30000000L, min = -30000000L)), @Argument(type = "Integer", hasClamp = true,
      clamp = @Clamp(max = 255)), @Argument(type = "Long", hasRange = true,
      range = @Range(max = 30000000L, min = -30000000L))}) 

This command does a few things. It makes sure the command can only be executed by a player, says the only valid argument lengths are 1, 2, and 3. Then it says the first argument is a long, that has to be in the range of (-30000000 to 30000000 (max of an MC world)), the second argument is an integer, which should always be below 255 if not make it 255, and the last which is another long with the some range.
As you saw Range & Clamp are some special types of things allowing you to greater specify your argument.
  • Clamp - Sets a maximum for a Number Value. If an argument is greater than the clamp it will make the value the clamp maximum.
  • Range - Sets a range for a number value. If an argument is not in the range it will send an error message to the player.
  • StringEquals - Makes sure a string is equal to one string in an array of String.
  • StringFilter - Filters out certain characters in strings.

You also noticed the argument type. There is 4 default types. Parsing Floating Point numbers, non-floating point numbers, strings, and booleans. Anybody can create a parser.


You can also specify different types of parsers for different types of argument lengths. I.E. :
@ArgumentSpecifications(validArgs = {1, 3, 4, 5, 6}, hasSpecialArgs = true,
    specialArgTypes = {
      @SpecialArgType(useWhenArgCountIs = 3, argTypes = {
          @Argument(type = "String"), @Argument(type = "Long"), @Argument(type = "Long")
      })
    }, argTypes = {
      @Argument(type = "String", required = true), @Argument(type = "String"),
      @Argument(type = "Long"), @Argument(type = "Long"), @Argument(type = "Long"),
      @Argument(type = "Boolean")
    }
  )

Here if the argument count is 3, then it will parse the arguments, as String, Long, and then finally another long. However if there isn’t three arguments passed they will be parsed as a String, String, and then a long, long, boolean. In one case the second argument is parsed as a long, and in the other the argument is parsed as a string.

7 Likes

Yes, I would like to see something like that, but completely based on methods and annotations (like the event system).

It was discussed to add such a command API to the SpongeAPI.

MethodCommand is a very nice library for Bukkit: Home · Ranzdo/MethodCommand Wiki · GitHub

Intake is another library: https://github.com/sk89q/intake

I really want something like this. Wanna team up?

There is a command api built into sponge. Already that is built in, this is meant to be an extension of the CommandAPI to make it easier to split up options/arguments passed by users fitting in with Sponge’s already existing CommandAPI.

Very useful. Good job!

You could add a few custom CommandElements (with range etc.) to support the new CommandSpec API. That would be really useful.

Look at the GenericArguments class of SpongeAPI to see what I mean.

Very nice, the less redundant code I have to write the better. :stuck_out_tongue: