Skip to content

Building commands

Revxrsal edited this page Mar 1, 2022 · 6 revisions

This page outlines the core feature of Lamp, how to build and compose commands. Other features, such as auto-completions, conditions and resolvers have their own dedicated wiki pages, and will not be mentioned here.

Command examples:

Background

Command actors (senders)

  • Command actors represent the individual responsible for executing a command. This can be a CLI, a player, a platform-supported one, such as org.bukkit.Player, or a custom sender type.
  • In cases of non-subclasses of CommandActor, the parameter that represents the sender must come first in the command! Otherwise, it will be interpreted as a non-sender, and will look for another resolver
  • When the first parameter in the command should not be inferred as the sender, it can be annotated with @NotSender.

Registering commands

Commands are registered by class instances. A class may contain one or more command, and each is represented by a method.

  • The command method has to be annotated with @Command, @Subcommand or @Default to be considered a command method, otherwise it is dismissed.
  • Commands must have a known parent at compile-time, through the @Command annotation, whether on the class or the method. In cases where the parent is only known at runtime, use orphan commands.
  • To register commands, use the generic commandHandler.register(Object...) method.
  • In cases of inner or nested classes, the parent classes are respected and checked for annotations.

Command paths

Commands in Lamp are identified by paths, which are very similar to the concept of file paths on your computer.

  • Subcommands must have a parent, either through @Command annotation on the containing class or method, or the orphan commands API which allows supplying a parent at runtime.
  • @Command annotations can be placed on classes and methods. In cases of inner or nested classes, the parent classes are respected and checked for annotations as well.
  • @Command and @Subcommand accept spaces, which represent nested paths. For example, @Command("foo bar") will be a foo command, which has a bar subcommand. It is effectively the same as @Command("foo") @Subcommand("bar")
  • Every command (and category) has a unique path, and can be retrieved from the CommandHandler using commandHandler.getCommand(CommandPath) and commandHandler.getCategory(CommandPath).
  • CommandPath is the official representation of command paths. The path of foo bar is equivalent to CommandPath.get("foo", "bar").

Examples

The following will contain examples on building simple commands.

Any pre-registering is included in each command, where the command handler is a ConsoleCommandHandler and is represented by the variable handler. Although, all command handler implementations should work similarly.

Echo command

A command that returns the text inputted to it.

@Command({"ping", "echo"})  
public void echo(CommandActor actor, @Default("") String message) {  
    actor.reply(message);  
}

A simpler implementation

handler.registerResponseHandler(String.class, (response, actor, command) -> actor.reply(response));

Any String returned from methods will automatically be sent to the actor.

@Command({"ping", "echo"})  
public String echo(@Default("") String message) {  
    return message;  
}
> echo Hello world!
Hello world!
> ping
(empty message)

Read file command

A command that reads the content of a file.

We will use java.nio.Path to read files.

handler.registerValueResolver(Path.class, context -> Paths.get(context.popForParameter()));

@Command("read")  
public void readFile(CommandActor actor, Path file) throws IOException {  
    if (!Files.exists(file))  
        throw new CommandErrorException("No such file: " + file);  
  Files.readAllLines(file).forEach(actor::reply);  
}

Executes:

> read
You must specify a value for the file!
> read not-exists.txt
No such file: not-exists.txt
> read example.yml
Hello, Lamp!

Zip file commands

  1. A command for zipping the current directory
handler.registerValueResolver(File.class, context -> new File(context.popForParameter()));
public enum Compression {  
    GZIP,  
    ZLIB  
}

@Command("zip pack")
public void zipFile(
        CommandActor actor,
        @Flag("output") @Optional File output,
        @Flag("compression") @Default("GZIP") Compression compression
) throws IOException {
    if (output == null) { // output was not specified
        File runningDirectory = new File("dummy").getParentFile();
        output = new File(runningDirectory, runningDirectory.getName() + ".zip");
    }
    if (output.exists())
        throw new CommandErrorException("A file with name '" + output.getName() + "' already exists!");
    output.createNewFile();
    File directory = output.getParentFile();
    // create ZIP here...
}

Execution:

> zip pack
(creates a zip with the dir name and GZIP)
> zip pack -output foo.zip
(zips with GZIP)
> zip pack -output foo.zip -compression zlib
(zips with zlib)
  1. A command for unzipping files
@Command("zip unpack")  
public void unzipFile(  
  CommandActor actor,  
  File zipFile,  
  @Flag("compression") @Default("GZIP") Compression compression  
) {  
    if (!zipFile.exists())  
        throw new CommandErrorException("No such file: " + zipFile.getName());  
  // unpack ZIP here...  
}

Executes:

> zip unpack file.zip
(unzips with GZIP)
> zip unpack file.zip -compression zlib
(unzips with zlib)

Format JSON command

handler.setSwitchPrefix("--");
handler.registerResponseHandler(String.class, (response, actor, command) -> actor.reply(response));

@Command("formatjson")  
public String formatJSON(  
        String json,  
  @Switch("pretty-printing") boolean prettyPrinting,  
  @Switch("disable-html-escaping") boolean disableHtmlEscaping  
) {  
    GsonBuilder builder = new GsonBuilder();  
  
 if (prettyPrinting)  
        builder.setPrettyPrinting();  
 if (disableHtmlEscaping)  
        builder.disableHtmlEscaping();  
  
  Gson gson = builder.create();  
  JsonElement element = JsonParser.parseString(json);  
 return gson.toJson(element);  
}

Execution:

> formatjson [1,2,3]
[1,2,3]
> formatjson [1,2,3] --pretty-printing
[
  1,
  2,
  3
]
Clone this wiki locally