Directive - A light weight, annotation based command system

Update: Directive now supports Sponge’s argument system!

Hey all,

I’ve created a lightweight command system for easy registration of multiple commands in a single class via annotations. This allows for rapid plugin development without bothering with Sponge’s robust but heavyweight command system. It works by first adding each command into a tree and then registering all commands recursively through Sponge’s command API. Feel free to refractor the package and include this library in your own plugins!

Directive

A lightweight, annotation-based command framework for Sponge

Usage

Creating commands

Let’s begin by creating a new command called example

@Directive(name = {"example"}, description = "An example command", permission = "my.example.command")
public static CommandResult exampleCommand(CommandSource src, CommandContext args) {
  src.sendMessage(Texts.of("Hello Sender!"));
  return CommandResult.success();
}

To mark a method as a directive command, add the @Directive annotation to it.
This annotation has the following attributes: name, description and permission.
Name is an array of aliases, description is a short description of the command
and permission is the permission of the command. These are equivalent to Sponge’s
CommandSpec arguments.

Directive methods must satisfy the following:

  • Method must be static
  • Method must return CommandResult
  • Method must have arguments of CommandSource and CommandContext

Sub commands can be registered by adding periods into the name of the command.
For example, ‘example.test’ would register test as a subcommand of example and
could be executed by ‘/example test’.

Registering commands

To begin, create a new DirectiveHandler. This is the object you will use to
register directives.

@Subscribe
public void onPreInit(PreInitializationEvent event) {
  // Create a new DirectiveHandler with the plugin object (this) and the game
  DirectiveHandler handler = new DirectiveHandler(this, event.getGame());
}

From here, utilize the method handler.addDirectives(Class cls). For example, if
you had a class called MyCommands that contained all of your commands, you would
call handler.addDirectives(MyCommands.class).

Once you have added all of your directives, the method handler.registerDirectives()
must be called to add all of the commands through Sponge’s API.

Argument parsing

Directive now has an argument parsing system. More enums may be added to the
ArgumentType class.

@Directive(names = {"exargs"}, argumentLabels = {"msg"}, arguments = {ArgumentType.OPTIONAL_STRING})
public static CommandResult exampleArgumentCommand(CommandSource src, CommandContext args) {
	src.sendMessage(Texts.of(args.<String>getOne("msg").get()));
	return CommandResult.success();
}

Arrays of argumentLabels and arguments can be created in the @Directive annotation.
They must be the same length and the order they are in is the order that they will
be added to the command spec. You can then access the arguments just as you would
if it was a normal Sponge command through CommandContext.

A full example plugin can be found at

3 Likes

Bring back CommandContext and you’ll have a pretty cool utility here.

I can’t figure out a good way to access the arguments via CommandContext without specifying it in the CommandSpec (which I can’t figure out a good way through annotations)

You could always bite the bullet and add:

CommandElement... args default GenericArgument.optional(GenericArgument.remainingJoinedStrings(Texts.of("args")));

What is the benefit of your system compared to @sk89q’s Intake? From your example it looks like you library works similar to WorldEdit’s old (before version 6) command framework; Intake is also based on it but allows parsing command arguments into business objects automatically, reducing the overhead when writing individual commands.

Can’t do that in an annotation though as only raw types are allowed

Mine is very lightweight and simple compared to intake (although Intake is great!) making it easier to implement into a new project.

Ah, yeah. True enough. :smiley: You can, however, use enums. You could take a series of enums and construct the context that way.

I was thinking about that. I’ll take a look at it but I’d need an array of enums and an array of strings as labels to make it work. I may just make it default to something and have a DirectiveArgument annotation which can be used in conjunction with Directive.

Problem is that it is hard to dynamically configure the string keys through an enum while still satisfying arguments such as Game

Sponge command arguments are currently typed to help tab completion and provide other utilities, if you reduce that to an array of strings I can’t really see how you can have any of the nice features provided by Sponge? Is there a way you can incorporate those into Directive?

Im working on an enum type to implement. It essentially allow you to add arguments in the annotation with DirectiveArgument.BOOLEAN and “yes/no” as the key. However, it will not allow for custom options such as choices with maps as annotations only allow for generic types (though you could add your own enum option to the arguments enum).

Edit: Thing I got a working prototype of the argument system. I’ll commit to the git repo soon-ish

i dont know my system seams alot more simple :slight_smile:

Yours doesn’t support Sponge’s powerful argument system so it doesn’t need to be as complicated

ill make a sponge version too … i mad this more for runnable console jars oregionaly … as im working with java daemons for a minecraft panel :slight_smile:

the hole idea is make stuff easier for rapid development think of it as what you see is what you get :slight_smile:

this hole project also shows how to make a multi environment pom with travis CI this will save people the research time. im sure you could tweek it to your liking though :slight_smile:

PS. this is also my first opensource project :slight_smile:

The project sounds really cool! Good luck with it :slight_smile:

If you want some areas that you can improve on in regards to Sponge investigate custom extendable, composable, functional CommandElement’s at the moment they seem a little verbose to use for plugins as all the useful super classes are being hidden by interfaces and factories.