diff --git a/docs/annotated/annotated.md b/docs/annotated/annotated.md new file mode 100644 index 00000000..456ae131 --- /dev/null +++ b/docs/annotated/annotated.md @@ -0,0 +1,28 @@ +## Annotated Commands + +`command-flow` allows developers to create full command trees in a declarative way +using annotated classes and methods. + +The annotated command API is just an alternative way to create `Command` instances, +we use this instead of the classic `Command.builder(String)` method. + +### Comparison + +You can make a side-by-side comparison of the two approaches in the following table +and links: + +| Imperative | Annotated | +|--------------------------------------------------------------------------|-----------------------------------------------------------------------| +| [Basic Command Creation](../imperatively/basic.md) | [Basic Command Creation](../annotated/basic.md) | +| [Command with single Argument](../imperatively/argument.md) | [Command with single Argument](../annotated/argument.md) | +| [Command with multiple Arguments](../imperatively/multiple-arguments.md) | [Command with multiple Arguments](../annotated/multiple-arguments.md) | +| [Command with optional Arguments](../imperatively/optional-arguments.md) | [Command with optional Arguments](../annotated/optional-arguments.md) | +| [Command with Permission](../imperatively/with-permission.md) | [Command with Permission](../annotated/with-permission.md) | +| [Command with Switch Arguments](../imperatively/with-switches.md) | [Command with Switch Arguments](../annotated/with-switches.md) | +| [Command with Value Flags](../imperatively/with-value-flags.md) | [Command with Value Flags](../annotated/with-value-flags.md) | + +### Elements of the Annotated Command API + +The annotated command API is composed of: +- The `@Command` annotation and others. +- The `AnnotatedCommandTreeBuilder` interface \ No newline at end of file diff --git a/docs/annotated/argument.md b/docs/annotated/argument.md new file mode 100644 index 00000000..249a17f8 --- /dev/null +++ b/docs/annotated/argument.md @@ -0,0 +1,20 @@ +## #2 With Arguments + +Check this command example with a single argument: + +```java +@Command(names = "hello") +public class TestCommand implements CommandClass { + + @Command(names = "") + public void run(String name) { + System.out.println("Hi " + name); + } + +} +``` + +In this example: +- Executing `hello yusshu` will print `Hi yusshu` +- Executing `hello Fixed` will print `Hi Fixed` +- Executing `hello` will result in an error \ No newline at end of file diff --git a/docs/annotated/basic.md b/docs/annotated/basic.md new file mode 100644 index 00000000..c60e69fc --- /dev/null +++ b/docs/annotated/basic.md @@ -0,0 +1,18 @@ +## #1 Basic Command + +Check this basic command example, with no arguments, no subcommands and no permissions, +using the annotated command creation approach: + +```java +@Command(names = "test") +public class TestCommand implements CommandClass { + + @Command(names = "") + public void run() { + System.out.println("Hello World!"); + } + +} +``` + +Executing `test` will print `Hello World!` \ No newline at end of file diff --git a/docs/annotated/command-class.md b/docs/annotated/command-class.md new file mode 100644 index 00000000..db3fdb9b --- /dev/null +++ b/docs/annotated/command-class.md @@ -0,0 +1,78 @@ +## Command Class + +The annotated command API has a `CommandClass` interface used to mark a class as a container +of commands or a command itself. + +To declare the commands, we use annotations: + + +```java +@Command(names = "teleport") +public class TeleportCommand implements CommandClass { + + @Command(names = "") // empty name indicates the root command + public void run( + @Sender Player sender, // the sender player + Player target // the target player (argument) + ) { + sender.teleport(target); + } + +} +``` + + +In this example we have created a `/teleport` command that takes a player name +as an argument *(command-flow will parse it and find the right Player)* and +teleports the sender to the target player. + +The `@Command` annotation is used to mark a method as a command, it can be used +on methods inside a `CommandClass` or on the class itself. + +See this other example: + + +```java +@Command(names = { "friends", "friend", "f", "fr" }) // name, ...aliases +public class FriendsCommand implements CommandClass { + + @Command(names = "add") + public void add(@Sender Player sender, Player target) { + // add 'target' to 'sender' friend list + } + + @Command(names = "remove") + public void remove(@Sender Player sender, Player target) { + // remove 'target' from 'sender' friend list + } + + @Command(names = "list") + public void list(@Sender Player sender) { + // list all 'sender' friends and show + } + + @Command(names = "broadcast") + public void broadcast(@Sender Player sender, @Text String message) { + // send 'message' to all the friends of 'sender' + } + +} +``` + + +In this example we have created a `/friends` command with 4 subcommands: +- `/friends add `: add a player to the sender friend list. +- `/friends remove `: remove a player from the sender friend list. +- `/friends list`: list all the sender friends. +- `/friends broadcast <...message>`: send a message to all the sender friends. + +In this new example we also see these new stuff: +- `@Command(names = {...})`: multiple names! The first one is considered the actual +command name and the rest are aliases. +- `@Text`: used to mark a parameter as a text argument, this means that it will +consume all the arguments left in the command and join them with spaces. + +### Registering them + +To register command classes we must convert them to `Command` first, we can do +that using the `AnnotatedCommandTreeBuilder` interface, check the next page! \ No newline at end of file diff --git a/docs/annotated/command-tree-builder.md b/docs/annotated/command-tree-builder.md new file mode 100644 index 00000000..8da50787 --- /dev/null +++ b/docs/annotated/command-tree-builder.md @@ -0,0 +1,35 @@ +## Command Builder + +The `AnnotatedCommandTreeBuilder` is the interface responsible for building `Command` +instances from annotated commands and classes. + +### Creating an AnnotatedCommandTreeBuilder + +To create a `AnnotatedCommandTreeBuilder` we must create a `PartInjector` first and +*(optionally)* a `SubCommandInstanceCreator`. More about this in the next pages. + +```java +PartInjector injector = ...; +SubCommandInstanceCreator instanceCreator = ...; + +AnnotatedCommandTreeBuilder builder = AnnotatedCommandTreeBuilder.create(injector, instanceCreator); +``` + +### Building a Command + +Now that we have a `AnnotatedCommandTreeBuilder` we can build a command (or multiple commands). +To do this we must call the `fromClass` method with the class of the command we want to build. + +Note that this method returns a list of commands, since a single class may contain multiple +root commands on it. + +```java +// create the builder +AnnotatedCommandTreeBuilder builder = ...; + +// build the Command list from our class +List commands = builder.fromClass(MyCommand.class); + +// now we can register them using the CommandManager#registerCommands convenience method +commandManager.registerCommands(commands); +``` \ No newline at end of file diff --git a/docs/annotated/index.txt b/docs/annotated/index.txt new file mode 100644 index 00000000..9fda5299 --- /dev/null +++ b/docs/annotated/index.txt @@ -0,0 +1,12 @@ +annotated.md +command-class.md +command-tree-builder.md +part-injector.md +subcommand-instance-creator.md +basic.md +argument.md +multiple-arguments.md +optional-arguments.md +with-permission.md +with-switches.md +with-value-flags.md \ No newline at end of file diff --git a/docs/annotated/multiple-arguments.md b/docs/annotated/multiple-arguments.md new file mode 100644 index 00000000..1649ec2a --- /dev/null +++ b/docs/annotated/multiple-arguments.md @@ -0,0 +1,25 @@ +## #3 Command with Multiple Args + +This example shows how to create a command with multiple arguments +using the annotated approach: + +```java +@Command(names = "greet") +public class GreetingCommand implements CommandClass { + + @Command(names = "") + public void run(String name, boolean formal) { + if (formal) { + System.out.println("Hello, " + name + "!"); + } else { + System.out.println("Hi, " + name + "!"); + } + } + +} +``` + +- Executing `greet John false` will print `Hi, John!` +- Executing `greet John true` will print `Hello, John!` +- Executing `greet John` will result in a usage error +- Executing `greet` will result in a usage error \ No newline at end of file diff --git a/docs/annotated/optional-arguments.md b/docs/annotated/optional-arguments.md new file mode 100644 index 00000000..ed9ff56b --- /dev/null +++ b/docs/annotated/optional-arguments.md @@ -0,0 +1,24 @@ +## #4 With Optional Args + +This example shows how to create a command with optional arguments +with the annotated approach: + +```java +@Command(names = "greet") +public class GreetingCommand implements CommandClass { + + @Command(names = "") + public void run(String name, @OptArg("Mr.") String title) { + System.out.println("Hello, " + title + " " + name + "!"); + } + +} +``` + +The `@OptArg` annotation is used to mark an argument as optional, and +it accepts a default value as a parameter, which will be used if the +argument is not present in the input. + +- Executing `greet John` will print `Hello, Mr. John!` +- Executing `greet John Dr.` will print `Hello, Dr. John!` +- Executing `greet John Mr.` will print `Hello, Mr. John!` \ No newline at end of file diff --git a/docs/annotated/part-injector.md b/docs/annotated/part-injector.md new file mode 100644 index 00000000..bf223c7f --- /dev/null +++ b/docs/annotated/part-injector.md @@ -0,0 +1,34 @@ +## Part Injector + +The `PartInjector` is a registry which holds the registered PartFactories +and PartModifiers. + +- A `PartFactory` is a class that provides a way to create an specific type of part. +- A `PartModifier` is like a PartFactory, the difference is that it may wrap the original part + or modify it instead of creating a new one. + +### Creating a PartInjector + +Creating a `PartInjector` is simple, just do the following: + +```java +PartInjector partInjector = PartInjector.create(); + +// install the default bindings! +// parts for native and core types like String, Boolean, Double, Float, Integer, +// Text(String), ArgumentStack, CommandContext, also modifiers like LimitModifier, +// OptionalModifier, ValueFlagModifier +partInjector.install(new DefaultsModule()); +``` + +### Platform Specific + +Some of the platform-specific subprojects include a `Module` for the `PartInjector` +that can be easily installed to obtain the default bindings for that platform. + +For example, for Bukkit (`commandflow-bukkit` subproject): +```java +// install bindings for the default bindings for Bukkit, such as +// CommandSender, OfflinePlayer, Player, World, GameMode and @Sender Player +partInjector.install(new BukkitModule()); +``` \ No newline at end of file diff --git a/docs/annotated/subcommand-instance-creator.md b/docs/annotated/subcommand-instance-creator.md new file mode 100644 index 00000000..afde64d9 --- /dev/null +++ b/docs/annotated/subcommand-instance-creator.md @@ -0,0 +1,53 @@ +## SubCommand Instantiation + +Sometimes we have a command that has subcommands declared with the `@SubCommandClasses` +annotation. In this case, the command framework needs to know how to instantiate the +subcommands. + +We can specify how to instantiate the subcommands by implementing the `SubCommandInstanceCreator` +interface. + +### The `SubCommandInstanceCreator` interface + +Functional interface that has a single method `createInstance(Class, CommandClass)`, +that receives the subcommand class and its parent instance, and returns the sub command instance. + +If we do not specify one, the annotated command tree builder will instantiate the subcommand +using Reflection, by looking for constructor that accepts the parent's class as the single +parameter an empty constructor. + +The `SubCommandInstanceCreator` is set to the `AnnotatedCommandTreeBuilder` when instantiating +it, for example: + + +```java +// will use the default SubCommandInstanceCreator +AnnotatedCommandTreeBuilder commandBuilder = AnnotatedCommandTreeBuilder.create(partInjector); + +// will use a custom SubCommandInstanceCreator +AnnotatedCommandTreeBuilder commandBuilder = AnnotatedCommandTreeBuilder.create( + partInjector, + new MyCustomSubCommandInstanceCreator() +); +``` + + +### Common Implementations + +It is common for `command-flow` to be used along with dependency injection frameworks, so +you can just create a `SubCommandInstanceCreator` that does the following: + +**For [Google's Guice](https://github.com/google/guice) or [Unnamed Team's inject](https://github.com/unnamed/inject):** +```java +// obtain the Injector instance +Injector injector = ...; + +// create the SubCommandInstanceCreator +SubCommandInstanceCreator subCommandCreator = (clazz, parent) -> injector.getInstance(clazz); + +// now set it when creating an AnnotatedCommandTreeBuilder +AnnotatedCommandTreeBuilder commandBuilder = AnnotatedCommandTreeBuilder.create( + partInjector, + subCommandCreator +); +``` \ No newline at end of file diff --git a/docs/annotated/with-permission.md b/docs/annotated/with-permission.md new file mode 100644 index 00000000..ed0f8bb7 --- /dev/null +++ b/docs/annotated/with-permission.md @@ -0,0 +1,22 @@ +## #5 With Permission + +This example shows how to create a command with a permission requirement +with the annotated approach: + +```java +@Command(names = "greet", permission = "myperm.command.greet") +public class GreetingCommand implements CommandClass { + + @Command(names = "") + public void run(String name) { + System.out.println("Hello, " + name + "!"); + } + +} +``` + +The `@Command` annotation accepts a `permission` parameter, which is +used to specify the permission required to execute the command. + +Check the [Authorizer page](../configuration/authorizer.md) for more information +on how to specify how the `CommandManager` should check for permissions. \ No newline at end of file diff --git a/docs/annotated/with-switches.md b/docs/annotated/with-switches.md new file mode 100644 index 00000000..bb2573d4 --- /dev/null +++ b/docs/annotated/with-switches.md @@ -0,0 +1,28 @@ +## #6 With Switches + +This example shows how to create a command with "switch" arguments +(boolean flags) with the imperative approach, note that switch arguments +values are always present. Presence of the switch argument indicates `true`, +and its absence indicates `false`. + + +```java +@Command(names = "test") +public class TestCommand implements CommandClass { + + @Command(names = "") + public void run(String name, @Switch("g") boolean goodBye) { + if (goodBye) { + System.out.println("Goodbye " + name); + return; + } + System.out.println("Hi " + name); + } + +} +``` + + +- Executing `test Fixed` will print `Hi Fixed` +- Executing `test -g Fixed` will print `Goodbye Fixed` +- Executing `test Fixed -g` will print `Goodbye Fixed` \ No newline at end of file diff --git a/docs/annotated/with-value-flags.md b/docs/annotated/with-value-flags.md new file mode 100644 index 00000000..1fdb09c4 --- /dev/null +++ b/docs/annotated/with-value-flags.md @@ -0,0 +1,30 @@ +## #7 With Value Flags + +This example shows how to create a command with value flag arguments +the main difference between a value flag and a switch is that the value flag +takes the next argument as its value, and the switch does not. + +For example: `-g true`, `-p 25565`, `-n Fixed` + + +```java +@Command(names = "test") +public class TestCommand implements CommandClass { + + @Command(names = "") + public void run(String name, @Flag("g") String greeting) { + System.out.println(greeting + " " + name); + } + +} +``` + + +- Executing `test Fixed` will print `Hi Fixed` +- Executing `test Fixed -g GoodBye` will print `GoodBye Fixed` +- Executing `test Fixed -g Hello` will print `Hello Fixed` +- Executing `test -g Fixed` will throw a `NoMoreArguments` exception, meaning that the parsing failed + because the `Fixed` argument was taken as the value for the flag and no argument + is remaining for the name. +- Executing `test Fixed -g` will throw a `NoMoreArguments` exception, meaning that the parsing failed + because the flag doesn't has any argument left to use. \ No newline at end of file diff --git a/docs/concepts/command-context.md b/docs/concepts/command-context.md new file mode 100644 index 00000000..d05360cb --- /dev/null +++ b/docs/concepts/command-context.md @@ -0,0 +1,5 @@ +## Command Context + +A mutable object which contains the context for a command execution, including but not +limited to the values for every parsed part, the raw arguments list and the raw arguments +for every part, the labels and the Command execution path (which path of subcommands was taken). \ No newline at end of file diff --git a/docs/concepts/command-manager.md b/docs/concepts/command-manager.md new file mode 100644 index 00000000..e536a29f --- /dev/null +++ b/docs/concepts/command-manager.md @@ -0,0 +1,43 @@ +## Command Manager + +The CommandManager manages the command registration, parsing and execution. Also provides +a way to obtain command suggestions *(which can be used for tab-completion in programs like +CLI applications or Minecraft plugins)* for a given input. + +### Creating a Command Manager + +Depending on your platform, you may want to use a different implementation of the +`CommandManager`. Check your [platform](../platforms/platforms.md)'s documentation +to get more information. + +However, there is a default implementation called `SimpleCommandManager` which can be +used in any platform, but it doesn't provide any implementation-specific features. + + +```java +CommandManager commandManager = new SimpleCommandManager(); +``` + + +### Registering commands + +To register a command, you need to create a `Command` object and register it using +the `CommandManager.registerCommand(Command)` method. + + +```java +// create CommandManager +CommandManager manager = ...; + +// create Command using builder +Command command = Command.builder("test") + .description("A test command") + .action(context -> { + System.out.println("Hello world!"); + }) + .build(); + +// register the command +manager.registerCommand(command); +``` + \ No newline at end of file diff --git a/docs/concepts/command-part.md b/docs/concepts/command-part.md new file mode 100644 index 00000000..887b5523 --- /dev/null +++ b/docs/concepts/command-part.md @@ -0,0 +1,10 @@ +## Command Part + +This is the second most fundamental component, it can be understood as every argument +of a [Command](./command.md), including things like subcommands, flags, non positional +arguments, etc. + +It can use arguments from the argument list, or provide them using any other means. They +can also forward the parsing responsibility to another part and just act as a modifier. + +Most of the default parts can be found at the `Parts` class. \ No newline at end of file diff --git a/docs/concepts/command.md b/docs/concepts/command.md new file mode 100644 index 00000000..584ffd33 --- /dev/null +++ b/docs/concepts/command.md @@ -0,0 +1,19 @@ +## Command + +Commands are the most fundamental component of the framework. It contains all the +information related to a command, included but not limited to name, aliases, permission, +parts, etc. + +Commands are created using the `Command.builder(String)` method, which returns an +`Command.Builder` instance where you can set all the information of the command. + +Example: + +```java +Command command = Command.builder("Test") + .action(context -> { + System.out.println("Hi!"); + }) + .build(); +``` + \ No newline at end of file diff --git a/docs/concepts/index.txt b/docs/concepts/index.txt new file mode 100644 index 00000000..33d9789e --- /dev/null +++ b/docs/concepts/index.txt @@ -0,0 +1,5 @@ +command.md +command-manager.md +command-part.md +command-context.md +namespaces.md \ No newline at end of file diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md new file mode 100644 index 00000000..d7436229 --- /dev/null +++ b/docs/concepts/namespaces.md @@ -0,0 +1,34 @@ +## Namespaces + +*(If you use an already implemented platform and annotated commands, you don't need +to use Namespaces, yay!)* + +A namespace is a mapping/set of the arguments that are injected into the execution. +For example, we can inject the user executor of a command and use it later. + +Creating a namespace: + +```java +// Create a namespace +Namespace namespace = Namespace.create(); +``` + + +Now we can set any object in the namespace, by type and name, for example, suppose we +have a `User` class: + +```java +namespace.setObject(User.class, "USER", new User("Fixed", 16)); +``` + + +And now, we can retrieve the object from the namespace, using the exact same +type and name, for example: + +```java +User user = namespace.getObject(User.class, "USER"); + +System.out.println(user.getName()); // Fixed +``` + + diff --git a/docs/configuration/authorizer.md b/docs/configuration/authorizer.md new file mode 100644 index 00000000..c3cb4dce --- /dev/null +++ b/docs/configuration/authorizer.md @@ -0,0 +1,74 @@ +## Authorizer + +`command-flow` has a functional interface `Authorizer` with a single method responsible for +determining if a command should be executed. + +### Platform Specific + +There are some platform specific implementations of `Authorizer`, which are automatically +set if you use the specific `CommandManager` class, for example, if you instantiate the +`BukkitCommandManager`, it will already use the `BukkitAuthorizer` by default, awesome! + +### Implementation + +The `Authorizer` single method accepts a `Namespace` and the actual permission string, +and returns a boolean value indicating if the command should be executed or not. + +Check the following implementation example, suppose we have a `User` class, and we have +previously set the sender user instance in the execution namespace *(Check page about +Command Dispatch)* + + +```java +public class MyAuthorizer implements Authorizer { + @Override + public boolean isAuthorized(Namespace namespace, String permission) { + User user = namespace.getObject(User.class, "USER"); + + // User not set! + if (user == null) { + return false; + } + + return user.hasPermission(permission); + } +} +``` + + +Now we can set our `Authorizer` in the `CommandManager` + + +```java +CommandManager commandManager = ...; + +commandManager.setAuthorizer(new MyAuthorizer()); +``` + + +### Permission String + +The permission string is an optional attribute for `Command` instances, set during +its instantiation, for example: + +Using builders: + +```java +Command command = Command.builder("test") + .permission("this.is.the.permission.string") + .action(context -> { + System.out.println("Hello World!"); + }) + .build(); +``` + + +Or annotations: + +```java +@Command(names = "test", permission = "this.is.the.permission.string") +public class TestCommand implements CommandClass { + ... +} +``` + \ No newline at end of file diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md new file mode 100644 index 00000000..7f1cb42f --- /dev/null +++ b/docs/configuration/configuration.md @@ -0,0 +1,8 @@ +## Configuring the Command Manager + +The `CommandManager` allows developers to fully configurate its behavior and +how to determine certain aspects of the command parsing & dispatching process. + +The `CommandManager` can also be extended for platform-specific features and +native integration, like, automatically registering commands in a platform +command registry. \ No newline at end of file diff --git a/docs/configuration/index.txt b/docs/configuration/index.txt new file mode 100644 index 00000000..4dffbc13 --- /dev/null +++ b/docs/configuration/index.txt @@ -0,0 +1,3 @@ +configuration.md +authorizer.md +tokenizer.md \ No newline at end of file diff --git a/docs/configuration/tokenizer.md b/docs/configuration/tokenizer.md new file mode 100644 index 00000000..1b326f7d --- /dev/null +++ b/docs/configuration/tokenizer.md @@ -0,0 +1,35 @@ +## Input Tokenizer + +The input tokenizer is responsible for converting a simple input string into a +list of tokens. The tokens are then used by the parser to enter the desired +command path and either dispatch a command or get suggestions for the next +token. + +The `InputTokenizer` is a functional interface that can be set to the +`CommandManager` via the `CommandManager#setInputTokenizer` method. + +For example: + +```java +// create a tokenizer +InputTokenizer tokenizer = ...; + +// set the tokenizer +CommandManager manager = ...; +manager.setInputTokenizer(tokenizer); +``` + +### Default Implementations + +`command-flow` provides two default implementations of the `InputTokenizer` +interface, `StringSpaceTokenizer` and `QuotedSpaceTokenizer`: + +- `StringSpaceTokenizer` - This tokenizer splits the input string by spaces + and returns the resulting tokens. This is the default tokenizer used by + `CommandManager` if no other tokenizer is set. +- `QuotedSpaceTokenizer` - This tokenizer splits the input string by spaces + but also supports quoted strings. For example, the input string + `hello "world of commands"` would be split into the tokens `hello` and + `world of commands`. + + diff --git a/docs/execution/context.md b/docs/execution/context.md new file mode 100644 index 00000000..077c0670 --- /dev/null +++ b/docs/execution/context.md @@ -0,0 +1,48 @@ +## Command Context + +As we have seen in [Command Execution](./execution.md), we can access previously set +variables in the execution namespace, from a command action, awesome! + +But now, we are going to explore more about the command context, and how we can +access more information about the command execution. + +### Information + +The `CommandContext` contains information about the command execution, such as: +- A reference to the command *(it may be root or sub command)* being executed +- A reference to the root command being executed +- The execution path +- The input tokens or arguments +- The used labels (which name/alias was used for every command and sub-command) +- A command part value +- All the data from the execution namespace + +### Example + + +```java +// create the command manager +CommandManager manager = ...; + +// create & register our command +Command command = Command.builder("test") + .addAlias("testalias") + .action(context -> { + // Here we have an action of the command, and here we can use the context for this command + // The CommandContext is the result of parsing the arguments of a command. + // It also extends Namespace, so you can use the Namespace methods on it. + + // The labels are the names for every command/subcommand executed. + // This is the name of the last subcommand/command + String label = context.getLabels().get(context.getLabels().size() - 1); + + System.out.println("Label: " + label); + }) + .build(); +manager.registerCommand(command); + +// execute the command +manager.execute(Namespace.create(), "testalias"); // Will print 'Label: testalias' +manager.execute(Namespace.create(), "test"); // Will print 'Label: test' +``` + diff --git a/docs/execution/execution.md b/docs/execution/execution.md new file mode 100644 index 00000000..f19433b9 --- /dev/null +++ b/docs/execution/execution.md @@ -0,0 +1,57 @@ +## Command Execution + +We already know how to create commands and register them, but now we need to +actually execute the commands. + +> Note that, in certain platforms like Bukkit, Velocity, Discord with JDA, you will not +have to worry about executing commands since the platform-specific `CommandManager` implementation +will listen to native events and automatically execute the commands for you, awesome! + +We can execute a command by calling the `CommandManager#execute` method, which will +parse and execute the command from a string command line. + +For example: + + +```java +// create a command manager +CommandManager manager = ...; + +// set up the variables passed to the command +Namespace namespace = Namespace.create(); +namespace.setObject(User.class, "USER", new User("Fixed", 16)); + +// executes the command "greet" with an argument "Yusshu" +manager.execute(namespace, "greet Yusshu"); +``` + + +Note that a `Namespace` is given to the `execute` command, namespaces are used to +store variables that can be accessed by the command. For example, the `greet` command +can access the `USER` variable that we set in the namespace. + +Here is an example where we access a previously set variable from inside the `Command` +action: + + +```java +// create a command manager +CommandManager manager = ...; + +// create & register the command +Command command = Command.builder("test") + .action(context -> { + User sender = context.getObject(User.class, "SENDER"); + System.out.println("'test' command used by " + sender.getName()); + }) + .build(); +manager.registerCommand(command); + +// set up the variables passed to the command +Namespace namespace = Namespace.create(); +namespace.setObject(User.class, "SENDER", new User("Yusshu", 18)); + +// executes the command "test" +manager.execute(namespace, "test"); +``` + \ No newline at end of file diff --git a/docs/execution/index.txt b/docs/execution/index.txt new file mode 100644 index 00000000..e69de29b diff --git a/docs/getting-started.md b/docs/getting-started.md index 7c98b6c9..a168178e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,24 +2,22 @@ Welcome to the `command-flow` documentation -CommandFlow is a flexible command framework which removes lots of -boilerplate code used in commands - -The command framework is divided into two parts. One is the actual -command framework and the other one is the API to allow creation of -a complete command tree based on annotations - -### Components of the Command Framework - -There are some basic components that you should at least be aware of to use **CommandFlow** -- **Command:** This is the most fundamental component, the **Command**. It contains all the information related to a command, included but not limited to name, aliases, permission, parts, etc. - Those are created using the `Command.builder(String)` method, which returns an `Command.Builder` instance where you can set all the information of the command. - -- **CommandPart:** This is the second most fundamental component, it can be understood as every argument of a **Command**, including things like subcommands, flags, non positional arguments, etc. It can use arguments from the argument list, or provide them using any other means also they can forward the parsing to another part and just act as a modifier. Most of the default parts can be found at the class `Parts` - -- **CommandContext**: This is a mutable object which contains the context for the called command including but not limited to the values for every part parsed, the raw arguments list and the raw arguments for every part, the labels and the Command execution path(which path of subcommands was taken). - - -### Known bugs or misbehaviours -The next list are the known bugs that must be resolved at some point: -- No known bugs at the moment. +`command-flow` is a flexible and platform-agnostic command framework +for Java. With this framework you can *imperatively* create command trees +using builders or *declaratively* using annotated classes and methods. + +See the following example: +```java +@Command("test") +public class TestCommand implements CommandClass { + + @Command("hello") + public void hello(CommandSender sender) { + sender.sendMessage("Hello World!"); + } + +} +``` + +### Features +- Easily create commands using builders or annotations \ No newline at end of file diff --git a/docs/imperatively/argument.md b/docs/imperatively/argument.md new file mode 100644 index 00000000..81fa8941 --- /dev/null +++ b/docs/imperatively/argument.md @@ -0,0 +1,30 @@ +## #2 With Arguments + +Check this command example with a single argument: + + +```java +import static me.fixeddev.commandflow.part.Parts.*; + +//... + +// create an (argument) part of type string, with the name 'name' +CommandPart nameArg = string("name"); + +// create the command +Command helloCommand = Command.builder("hello") + .addPart(nameArg) + .action(context -> { + // get the value of the name argument and print 'Hi ' + context.getValue(nameArg).ifPresent(name -> { + System.out.println("Hi " + name); + }); + }) + .build(); +``` + + +In this example: +- Executing `hello yusshu` will print `Hi yusshu` +- Executing `hello Fixed` will print `Hi Fixed` +- Executing `hello` will print nothing \ No newline at end of file diff --git a/docs/imperatively/basic.md b/docs/imperatively/basic.md new file mode 100644 index 00000000..72c4d440 --- /dev/null +++ b/docs/imperatively/basic.md @@ -0,0 +1,16 @@ +## #1 Basic Command + +Check this basic command example, with no arguments, no subcommands and no permissions, +using the imperative command creation approach: + + +```java +Command testCommand = Command.builder("test") + .action(context -> { + System.out.println("Hello World!"); + }) + .build(); +``` + + +Executing `test` will print `Hello World!` \ No newline at end of file diff --git a/docs/imperatively/index.txt b/docs/imperatively/index.txt new file mode 100644 index 00000000..af0139e3 --- /dev/null +++ b/docs/imperatively/index.txt @@ -0,0 +1,8 @@ +intro.md +basic.md +argument.md +multiple-arguments.md +optional-arguments.md +with-permission.md +with-switches.md +with-value-flags.md \ No newline at end of file diff --git a/docs/imperatively/intro.md b/docs/imperatively/intro.md new file mode 100644 index 00000000..e988524c --- /dev/null +++ b/docs/imperatively/intro.md @@ -0,0 +1,21 @@ +## Imperative Command Creation + +We can imperatively create commands using the command builder interface. This +way to create commands is the most flexible one, but also the most verbose. + +An alternative to this approach is to use the [annotation-based command creation](../annotated/annotated.md). + +### Comparison + +You can make a side-by-side comparison of the two approaches in the following table +and links: + +| Imperative | Annotated | +|--------------------------------------------------------------------------|-----------------------------------------------------------------------| +| [Basic Command Creation](../imperatively/basic.md) | [Basic Command Creation](../annotated/basic.md) | +| [Command with single Argument](../imperatively/argument.md) | [Command with single Argument](../annotated/argument.md) | +| [Command with multiple Arguments](../imperatively/multiple-arguments.md) | [Command with multiple Arguments](../annotated/multiple-arguments.md) | +| [Command with optional Arguments](../imperatively/optional-arguments.md) | [Command with optional Arguments](../annotated/optional-arguments.md) | +| [Command with Permission](../imperatively/with-permission.md) | [Command with Permission](../annotated/with-permission.md) | +| [Command with Switch Arguments](../imperatively/with-switches.md) | [Command with Switch Arguments](../annotated/with-switches.md) | +| [Command with Value Flags](../imperatively/with-value-flags.md) | [Command with Value Flags](../annotated/with-value-flags.md) | \ No newline at end of file diff --git a/docs/imperatively/multiple-arguments.md b/docs/imperatively/multiple-arguments.md new file mode 100644 index 00000000..6313e108 --- /dev/null +++ b/docs/imperatively/multiple-arguments.md @@ -0,0 +1,40 @@ +## #3 Command with Multiple Args + +This example shows how to create a command with multiple arguments +using the imperative approach: + + +```java +// Here we create a StringPart(String argument) with the name "name" +CommandPart name = string("name"); + +// Here we create a BooleanPart(boolean argument) with the name "formal" +CommandPart formalPart = booleanPart("formal"); + +Command greetCommand = Command.builder("greet") + // Here we add a part into the Command + .addPart(name) + // You can add multiple parts into a command + // They will be added into a main part called SequentialCommandPart + // Which will call every part of the Command in a sequence to parse every argument. + .addPart(formalPart) + .action(context -> { + // The values for a Part(argument) may not be present, check if they're + // before trying to use them + boolean formal = context.getValue(formalPart).orElse(false); + context.getValue(name).ifPresent(s -> { + if (formal) { + System.out.println("Hello, " + s + "!"); + } else { + System.out.println("Hi, " + s + "!"); + } + }); + }) + .build(); +``` + + +- Executing `greet John false` will print `Hi, John!` +- Executing `greet John true` will print `Hello, John!` +- Executing `greet John` will print `Hi, John!` +- Executing `greet` will print nothing \ No newline at end of file diff --git a/docs/imperatively/optional-arguments.md b/docs/imperatively/optional-arguments.md new file mode 100644 index 00000000..cc568b6a --- /dev/null +++ b/docs/imperatively/optional-arguments.md @@ -0,0 +1,33 @@ +## #4 With Optional Args + +This example shows how to create a command with optional arguments +with the imperative approach: + + +```java +CommandPart titleValue = string("title"); + +// This makes the titleValue argument optional, with the default value "Mr." +CommandPart title = optional(titleValue, Collections.singletonList("Mr.")); + +CommandPart name = string("name"); + +Command greetCommand = Command.builder("greet") + .addPart(name) + .addPart(title) + .action(context -> { + String nameString = context.getValue(nameValue).orElse("User"); + + // Should be present every time, since it has a default value + // If the default value is not valid for the specified part, then the argument could be absent + String titleString = context.getValue(titleValue).get(); + + System.out.println("Hello, " + titleString + " " + nameString); + }) + .build(); +``` + + +- Executing `greet John` will print `Hello, Mr. John!` +- Executing `greet John Dr.` will print `Hello, Dr. John!` +- Executing `greet John Mr.` will print `Hello, Mr. John!` \ No newline at end of file diff --git a/docs/imperatively/with-permission.md b/docs/imperatively/with-permission.md new file mode 100644 index 00000000..1cef6ea8 --- /dev/null +++ b/docs/imperatively/with-permission.md @@ -0,0 +1,22 @@ +## #5 With Permission + +This example shows how to create a command with a permission requirement +with the annotated approach: + + +```java +Command testUserCommand = Command.builder("test") + // We set the permission of the test command into admin + .permission("admin") + .action(context -> { + System.out.println("Hi"); + }) + .build(); +``` + + +Executing the `testUserCommand` will print `Hi` if the user has the `admin` +permission, but will result in a `NoPermissionException` if the user does not. + +Check the [Authorizer page](../configuration/authorizer.md) for more information +on how to specify how the `CommandManager` should check for permissions. \ No newline at end of file diff --git a/docs/imperatively/with-switches.md b/docs/imperatively/with-switches.md new file mode 100644 index 00000000..fdfbaf33 --- /dev/null +++ b/docs/imperatively/with-switches.md @@ -0,0 +1,39 @@ +## #6 With Switches + +This example shows how to create a command with "switch" arguments +(boolean flags) with the imperative approach, note that switch arguments +values are always present. Presence of the switch argument indicates `true`, +and its absence indicates `false`. + + +```java +CommandPart name = string("name"); + +// If the -g argument is present, then the switch value will be true, otherwise false +// It can be in any position, but the parts registered before will take priority, +// that means that if the name part is registered before the switch part, the -g only +// can be after the name +CommandPart goodByeSwitch = switchPart("goodBye", "g"); + +Command testUserCommand = Command.builder("test") + .addPart(goodByeSwitch) + .addPart(name) + .action(context -> { + // The value for a switch is never absent + boolean goodBye = context.getValue(goodByeSwitch).get(); + + context.getValue(name).ifPresent(s -> { + if (goodBye) { + System.out.println("Goodbye " + s); + return; + } + System.out.println("Hi " + s); + }); + }) + .build(); +``` + + +- Executing `test Fixed` will print `Hi Fixed` +- Executing `test -g Fixed` will print `Goodbye Fixed` +- Executing `test Fixed -g` will print `Goodbye Fixed` \ No newline at end of file diff --git a/docs/imperatively/with-value-flags.md b/docs/imperatively/with-value-flags.md new file mode 100644 index 00000000..1fd5d2ac --- /dev/null +++ b/docs/imperatively/with-value-flags.md @@ -0,0 +1,40 @@ +## #7 With Value Flags + +This example shows how to create a command with value flag arguments +the main difference between a value flag and a switch is that the value flag +takes the next argument as its value, and the switch does not. + +For example: `-g true`, `-p 25565`, `-n Fixed` + + +```java +CommandPart name = string("name"); + +// This part is like a switch part, the difference is that when the main "switch" is found +// the part provided in the first argument takes the parsing, consuming one or more arguments +// from the stack at that position. +CommandPart greetingValue = string("greeting"); +CommandPart greetingValueFlag = valueFlag(greetingValue, "g"); + +Command testUserCommand = Command.builder("test") + .addPart(greetingValueFlag) + .addPart(name) + .action(context -> { + // The value for a value flag can be absent + String greeting = context.getValue(greetingValue).orElse("Hi"); + context.getValue(name).ifPresent(s -> { + System.out.println(greeting + " " + s); + }); + }) + .build(); +``` + + +- Executing `test Fixed` will print `Hi Fixed` +- Executing `test Fixed -g GoodBye` will print `GoodBye Fixed` +- Executing `test Fixed -g Hello` will print `Hello Fixed` +- Executing `test -g Fixed` will throw a `NoMoreArguments` exception, meaning that the parsing failed + because the `Fixed` argument was taken as the value for the flag and no argument + is remaining for the name. +- Executing `test Fixed -g` will throw a `NoMoreArguments` exception, meaning that the parsing failed + because the flag doesn't has any argument left to use. \ No newline at end of file diff --git a/docs/index.txt b/docs/index.txt index 039ce811..be08b98e 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -1,2 +1,8 @@ getting-started.md -installation.md \ No newline at end of file +installation.md +concepts +configuration +imperatively +annotated +execution +platforms \ No newline at end of file diff --git a/docs/platforms/brigadier.md b/docs/platforms/brigadier.md new file mode 100644 index 00000000..e979d0ed --- /dev/null +++ b/docs/platforms/brigadier.md @@ -0,0 +1 @@ +## Bukkit with Brigadier \ No newline at end of file diff --git a/docs/platforms/bukkit.md b/docs/platforms/bukkit.md new file mode 100644 index 00000000..fb5a46b2 --- /dev/null +++ b/docs/platforms/bukkit.md @@ -0,0 +1,77 @@ +## Bukkit + +`command-flow` provides a Bukkit implementation of the `CommandManager` interface and +some other common interfaces. These implementations are provided by the `commandflow-bukkit` +subproject that can be included in your project by doing the following: + +**Gradle:** +```kotlin +dependencies { + // ... + implementation("me.fixeddev:commandflow-bukkit:%%REPLACE_latestReleaseOrSnapshot{me.fixeddev:commandflow-bukkit}%%") +} +``` + +**Maven:** +```xml + + + + me.fixeddev + commandflow-bukkit + %%REPLACE_latestReleaseOrSnapshot{me.fixeddev:commandflow-bukkit}%% + + +``` + +### Usage + +The `commandflow-bukkit` subproject consists on providing the following classes: +- `CommandManager` -> `BukkitCommandManager`: Bukkit's CommandManager implementation +- `Authorizer` -> `BukkitAuthorizer`: Checks permissions using `CommandSender#hasPermission` +- `Module` -> `BukkitModule`: Provides part factories for `CommandSender`, `GameMode`, +`OfflinePlayer`, `Player`, `@Sender Player`, `World` +- `@Sender`, `@PlayerOrSource`, `@Exact` extra annotations + +### Example + +```java +@Command(names = "gamemode", permission = "myplugin.gamemode") +class GameModeCommand implements CommandClass { + + @Command(names = "") + public void run(@Sender Player sender, GameMode gameMode, @OptArg Player target) { + if (target == null) { + sender.setGameMode(gameMode); + } else { + target.setGameMode(gameMode); + } + sender.sendMessage("Success!"); + } + +} + +class MyPlugin extends JavaPlugin { + + @Override + public void onEnable() { + registerCommands(); + + // now you can use '/gamemode [target]' in game! + } + + private void registerCommands() { + CommandManager manager = new BukkitCommandManager("myplugin"); // <-- your plugin name + + // annotated! + PartInjector injector = PartInjector.create(); + injector.install(new DefaultsModule()); + injector.install(new BukkitModule()); // <-- BukkitModule! + AnnotatedCommandTreeBuilder builder = AnnotatedCommandTreeBuilder.create(injector); + + // register + manager.registerCommands(builder.fromClass(new GameModeCommand())); + } + +} +``` \ No newline at end of file diff --git a/docs/platforms/bungeecord.md b/docs/platforms/bungeecord.md new file mode 100644 index 00000000..fb08c682 --- /dev/null +++ b/docs/platforms/bungeecord.md @@ -0,0 +1 @@ +## BungeeCord \ No newline at end of file diff --git a/docs/platforms/discord-jda.md b/docs/platforms/discord-jda.md new file mode 100644 index 00000000..7d73374f --- /dev/null +++ b/docs/platforms/discord-jda.md @@ -0,0 +1 @@ +## Discord (JDA) \ No newline at end of file diff --git a/docs/platforms/index.txt b/docs/platforms/index.txt new file mode 100644 index 00000000..3e24d27c --- /dev/null +++ b/docs/platforms/index.txt @@ -0,0 +1,6 @@ +platforms.md +bukkit.md +brigadier.md +velocity.md +bungeecord.md +discord-jda.md \ No newline at end of file diff --git a/docs/platforms/platforms.md b/docs/platforms/platforms.md new file mode 100644 index 00000000..b586e0f2 --- /dev/null +++ b/docs/platforms/platforms.md @@ -0,0 +1,14 @@ +## Platforms + +`command-flow` core is platform-agnostic, however, it provides a way to integrate with +platform-specific features, such as native command registration, tab-completion/suggestions, +etc. + +At the moment we support the following platforms: +- [Bukkit](bukkit.md) +- [Bukkit with Brigadier](brigadier.md) +- [BungeeCord](bungeecord.md) +- [Discord with JDA](discord-jda.md) +- [Velocity](velocity.md) + +However, you can make your own platform implementation easily. \ No newline at end of file diff --git a/docs/platforms/velocity.md b/docs/platforms/velocity.md new file mode 100644 index 00000000..01f81009 --- /dev/null +++ b/docs/platforms/velocity.md @@ -0,0 +1 @@ +## Velocity \ No newline at end of file