Skip to content
This repository has been archived by the owner on May 21, 2020. It is now read-only.

Creating Commands

Xemiru / Marc Tellerva edited this page Nov 2, 2017 · 13 revisions

Commands are the main flesh of any command system, and General makes it relatively simple to make them. A builder for commands is provided in a static method of the Command class. Commands can have various properties to them each of which can be set through the builder. A quick description of each is listed below. Bolded properties are required.

Property Description
name The name of the command. Extra names passed to this property are considered aliases, or alternate names that the command can be called with.
syntax The static syntax of the command. If this is set, the system will use it instead of syntax generated from the Arguments object.
shortDescription A short description of the command. Used by the help command when it needs to list all registered commands. If this isn't present, the help command will notify the user that it does not have short help text.
description A full description of the command. Used by the help command when it is asked about this specific command. If this isn't present, the help command will use shortDescription instead.
executor The executor holding the command's execution logic.

Tip!

An alternate command builder can be received by instead calling the parent() method. The builder returned houses all the same properties with an additional addCommand method. The resulting command holds all other commands provided to it (including even more parent commands) and comes with a help subcommand.

The parent command's executor property becomes the fallback executor called when no parameters are passed during the command's execution.

Example

Command helloWorld = Command.builder()
    .name("hello")
    .shortDescription("Hello, world!")
    .description("Responds with \"Hello, world!\"")
    .executor((context, args, dry) -> {
        if(dry) return;
        context.sendMessage("Hello, world!");
    })
    .build();

Custom Properties

See Custom Properties for information on assigning custom properties for Commands.

Executors

CommandExecutor objects hold the main execution logic for a command. FullCommandExecutor should be implemented for executors intending to exist as their own class, though one can use CommandExecutor for a quick lambda implementation.

The Command Context

The context of the command lets you know information about how the command was executed. This doesn't really contain too much; it's mostly meta information like what command was executed, the alias used to execute the command, etc. The context is also a command's main way to communicate with the person who sent the command, using its sendMessage method.

@Override
public void execute(CommandContext ctx, Arguments args) {
    ctx.sendMessage("Hello, user!");
}

Contexts may also have custom properties, retrievable from the <T>getCustom(String) method. These custom properties may be set by a context factory in the call to the CommandManager. These properties should allow you to introduce more meta information like a user object, a permissions value, etc.

The Arguments Object

The most special part of General is the Arguments object, as it is the core worker behind all of its special features -- namely, syntax generation and argument parsing.

You might wanna read about argument parsers here, first.

Arguments are parsed using the write methods (alternatively the named methods if you want to change the typename). The write methods dictate the syntax of your command using the parsers you pass to it, and processes user input into the tokens you can use. You can then retrieve the parsed parameters in the same order you declared them in using the next method.

This process of declaring syntax is performed in the FullCommandExecutor's initialize method.

import static com.github.xemiru.general.ArgumentParsers.*;

// ...

@Override
public void initialize(CommandContext ctx, Arguments args) {
    args.write(NUMBER)
        .write(INTEGER)
        .write(STRING);
}

@Override
public void execute(CommandContext ctx, Arguments args) {
    double num = args.next();
    int intt = args.next();
    String str = args.next();
    // do stuff with these
}

Tip!

The write/named methods have useful overloads for verifying that a value is actually usable! You can pass a Predicate to these methods along with an error message should you need some extra checks on a parameter.

args.named("amount", NUMBER, it -> it > 0, "amount must be at least one")

The next method is what causes any user errors to come to light, though calling it does not prevent one from adding more parameters. Should the user provide bad input, the write methods will hold the error until the next method is called. A SyntaxException will then be thrown -- though in most cases, you won't need to care about it as the command manager who ran the command will catch it and notify the user of their error.

Because user error isn't shown until the first call to next(), you can perform state checks prior to calling it for the first time to ensure the command runs in its preferred environment. It is recommended to do so to make sure the user finds out that the command can't be ran first before being notified of their errors in input.

The dry Parameter

If you've chosen to implement a CommandExecutor instead of a FullCommandExecutor, the dry parameter determines whether or not we're calling only initialize, or both initialize and execute. The FullCommandExecutor follows this rule when it implements CommandExecutor's execute itself:

@Override
default void execute(CommandContext context, Arguments args, boolean dry) {
    this.initialize(context, args);
    if(!dry) this.execute(context, args);
}

Error handling

Should you feel something bad may happen during your command, it is up to you to throw a CommandException. The command system will recognize this as a foreseen failure, and pass the message within the exception onto the user.

@Override
public void initialize(CommandContext ctx, Arguments args) {
    args.write(STRING);
}

@Override
public void execute(CommandContext ctx, Arguments args) {
    int i;

    try {
        i = Integer.parseInt(args.next());
    } catch(NumberFormatException e) {
        throw new CommandException("Not a number!");
    }

    // side note: the INTEGER parser exists and you could do this with args.write(INTEGER) instead
}

Any other exceptions that occur during the execution runtime of a command is considered a crash; the system will report it to the user accordingly.