Skip to content

Commit 9d03fba

Browse files
authored
feat: add DefaultExecutes annotation (#32)
Accidentally also did a minor (actually it was kinda major) rewrite of the path logic back into single argument nodes, as it turns out that it makes a lot of steps much simpler.
1 parent 413ee2c commit 9d03fba

File tree

59 files changed

+1917
-2644
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1917
-2644
lines changed

.editorconfig

Lines changed: 5 additions & 1230 deletions
Large diffs are not rendered by default.

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export default defineConfig({
3030
{ slug: "docs" },
3131
{ slug: "docs/dependency" },
3232
{ slug: "docs/commands" },
33+
{ slug: "docs/default-executors" },
3334
{ slug: "docs/arguments" },
3435
{ slug: "docs/permissions" },
3536
{ slug: "docs/records" },
21 KB
Loading
17.9 KB
Loading
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: Default executors
3+
description: A guide to setting default executors on commands.
4+
---
5+
6+
Since **StrokkCommands v1.5**, you can now define executors to be put on command node by default if not explicit
7+
executor is present. This feature is meant to act as a simple way of adding help descriptions or other miscellaneous
8+
'default' behavior.
9+
10+
## The `@DefaultExecutor` annotation
11+
This annotation has the exact same syntax as `@Executor`. It behaves the same way (in fact, you can annotate
12+
the same method both `@Executor` *and* `@DefaultExecutor`) and takes in the same parameter input for a preceding
13+
literal path.
14+
15+
The only difference is that it will also apply itself to all child notes, instead of only the current argument path.
16+
17+
So if you have a structure similar to this:
18+
```java title=MyCommand.java
19+
@Command("command")
20+
class MyCommand {
21+
22+
@DefaultExecutes
23+
void help(CommandSender sender, String[] extraArgs) {
24+
sender.sendPlainMessage("/" + String.join(" ", extraArgs) + " is missing some arguments!");
25+
}
26+
27+
@Executes("some literals")
28+
void logic(CommandSender sender, int num, String word) {
29+
// ...
30+
}
31+
}
32+
```
33+
The following paths will be valid for execution:
34+
```
35+
/command - MyCommand#help
36+
/command some - MyCommand#help
37+
/command some literals - MyCommand#help
38+
/command some literals <num> - MyCommand#help
39+
/command some literals <num> <word> - MyCommand#logic
40+
```
41+
42+
You can optionally add either a `String[]` or `List<String>` parameter to the end of the method parameters
43+
to obtain all arguments (including the command name, without the slash) a user has provided.
44+
This acts as a way to retrieving the command name (and alias) a user has used and for getting any additional
45+
input.
46+
47+
## A practical example: extending `MyFirstCommand`
48+
In [Creating your first command](/docs/commands) we have created a super simple command with a bunch of literals.
49+
The large number of literals might feel very unintuitive to users. For this reason, it makes sense to add a `@DefaultExecutor`
50+
for both the `two three four` sequence and for the general `/firstcommand` command.
51+
52+
Doing this is incredibly easy; we simply need to add two default executors for those cases.
53+
```diff lang=java title=MyFirstCommand.java
54+
@Command("firstcommand")
55+
@Aliases("fc")
56+
@Description("My first StrokkCommands-command!")
57+
class MyFirstCommand {
58+
59+
+ @DefaultExecutes
60+
+ void help(CommandSender sender, String[] args) {
61+
+ sender.sendRichMessage("""
62+
+ <blue>Command Help for /<cmd></blue><dark_gray>
63+
+ - <gray><aqua>/<cmd> two three four</aqua> a regular fun command >_<</gray>
64+
+ - <gray><aqua>/<cmd> fling</aqua> yeet yourself!</gray>""",
65+
+ Placeholder.parsed("cmd", args[0])
66+
+ );
67+
+ }
68+
+
69+
+ @DefaultExecutes("two")
70+
+ @RequiresOP
71+
+ void helpTwo(CommandSender sender, String[] args) {
72+
+ sender.sendRichMessage("<aqua>/<cmd></aqua> is incomplete! You probably meant <gold>/<cmd_name> two three four<gold> :^)",
73+
+ Placeholder.parsed("cmd", String.join(" ", args)),
74+
+ Placeholder.parsed("cmd_name", args[0])
75+
+ );
76+
+ }
77+
78+
@Executes("two three four")
79+
@RequiresOP
80+
void onExecute(CommandSender sender, @Executor Player player) {
81+
sender.sendRichMessage("<#f29def>Hey there! You just executed your first command ^-^");
82+
}
83+
84+
@Executes("fling")
85+
@Permission("some.permission")
86+
void onFling(CommandSender sender, @Executor Player player) {
87+
player.setVelocity(player.getVelocity().add(new Vector(0, 10, 0)));
88+
player.sendRichMessage("<b><#c4e6ff>WOOSH</b> <#c4fffd>You've been flung!");
89+
}
90+
}
91+
```
92+
93+
### In-game preview
94+
This simple edit behaves exactly how one would expect! Running the command with no arguments will
95+
display the help declared in the `help` method. It will even include the command name exactly how
96+
a user has entered it into the chat bar.
97+
98+
![](./assets/default-executors-preview-1.png)
99+
100+
As soon as you enter the `two` literal argument, it will use the `helpTwo` method.
101+
102+
![](./assets/default-executors-preview-2.png)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* StrokkCommands - A super simple annotation based zero-shade Paper command API library.
3+
* Copyright (C) 2025 Strokkur24
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package net.strokkur.commands.annotations;
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
import java.util.List;
25+
26+
/// The default method to call if an invalid number of arguments were declared
27+
///
28+
/// This annotation acts very similar to the standard [Executes] annotation, with
29+
/// the only difference being that it will also apply itself to all child notes.
30+
///
31+
/// So if you have a structure similar to this:
32+
/// ```java
33+
/// @Command("command")
34+
/// class MyCommand {
35+
///
36+
/// @DefaultExecutes
37+
/// void help(CommandSender sender, String[] extraArgs) {
38+
/// sender.sendPlainMessage("/" + String.join(" ", extraArgs) + " is missing some arguments!")
39+
/// }
40+
///
41+
/// @Executes("some literals")
42+
/// void logic(CommandSender sender, int num, String word) {
43+
/// // ...
44+
/// }
45+
/// }
46+
/// ```
47+
/// The following paths will be valid for execution:
48+
/// ```
49+
/// /command - MyCommand#help
50+
/// /command some - MyCommand#help
51+
/// /command some literals - MyCommand#help
52+
/// /command some literals <num> - MyCommand#help
53+
/// /command some literals <num> <word> - MyCommand#logic
54+
/// ```
55+
///
56+
/// You can add either a `String[]` or `List<String>` parameter to the end of the method parameters
57+
/// to obtain all arguments (including the command name, without the slash) a user has provided. The [List] is **immutable**.
58+
///
59+
/// You can provide a literal path to precede to the [DefaultExecutes] method, exactly the same way as
60+
/// with the [Executes] annotation. You can also add arguments the exactly same way.
61+
///
62+
/// If multiple [DefaultExecutes]-annotated methods are present, the **deeper one** in the tree takes precedence.
63+
/// If multiple [DefaultExecutes]-annotated methods are present on the same path, the first declared one takes precedence.
64+
@Retention(RetentionPolicy.SOURCE)
65+
@Target(ElementType.METHOD)
66+
public @interface DefaultExecutes {
67+
/// A literal path to prepend to the method.
68+
///
69+
/// This
70+
/// ```java
71+
/// @DefaultExecutes("the literal path")
72+
/// void help(CommandSender sender, /* rest of arguments */);
73+
/// ```
74+
/// is the same as writing
75+
/// ```java
76+
/// @DefaultExecutes
77+
/// void help(CommandSender sender, @Literal("the literal path") String lit, /* rest of arguments */);
78+
/// ```
79+
///
80+
/// @return the literal path to prepend to the argument path
81+
String value() default "";
82+
}

strokk-commands-processor/src/main/java/net/strokkur/commands/internal/StrokkCommandsProcessor.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import net.strokkur.commands.annotations.Description;
2424
import net.strokkur.commands.internal.arguments.BrigadierArgumentConverter;
2525
import net.strokkur.commands.internal.intermediate.CommandInformation;
26-
import net.strokkur.commands.internal.intermediate.paths.CommandPath;
27-
import net.strokkur.commands.internal.intermediate.paths.PathFlattener;
26+
import net.strokkur.commands.internal.intermediate.TreePostProcessor;
27+
import net.strokkur.commands.internal.intermediate.tree.CommandNode;
2828
import net.strokkur.commands.internal.parsing.CommandParser;
2929
import net.strokkur.commands.internal.parsing.CommandParserImpl;
3030
import net.strokkur.commands.internal.printer.CommandTreePrinter;
@@ -85,7 +85,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
8585
final MessagerWrapper messagerWrapper = MessagerWrapper.wrap(super.processingEnv.getMessager());
8686
final BrigadierArgumentConverter converter = new BrigadierArgumentConverter(messagerWrapper);
8787
final CommandParser parser = new CommandParserImpl(messagerWrapper, converter);
88-
final PathFlattener pathFlattener = new PathFlattener(messagerWrapper);
88+
final TreePostProcessor treePostProcessor = new TreePostProcessor(messagerWrapper);
8989

9090
final String debugOnly = System.getProperty(MessagerWrapper.DEBUG_ONLY_SYSTEM_PROPERTY);
9191
if (debugOnly != null) {
@@ -103,7 +103,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
103103
}
104104

105105
try {
106-
processElement(typeElement, messagerWrapper, parser, pathFlattener);
106+
processElement(typeElement, messagerWrapper, parser, treePostProcessor);
107107
} catch (Exception e) {
108108
messagerWrapper.errorElement("An error occurred: {}", typeElement, e.getMessage());
109109
e.printStackTrace(new PrintWriter(System.out));
@@ -119,31 +119,27 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
119119
return true;
120120
}
121121

122-
private void processElement(TypeElement typeElement, MessagerWrapper messagerWrapper, CommandParser parser, PathFlattener pathFlattener) {
122+
private void processElement(TypeElement typeElement, MessagerWrapper messagerWrapper, CommandParser parser, TreePostProcessor treePostProcessor) {
123123
boolean debug = System.getProperty(MessagerWrapper.DEBUG_SYSTEM_PROPERTY) != null;
124124

125125
final CommandInformation commandInformation = getCommandInformation(typeElement);
126-
final CommandPath<?> commandPath = parser.createCommandTree(typeElement);
127-
128-
if (debug) {
129-
// debug log all paths.
130-
messagerWrapper.debug("Before flatten: \n{}\n ", commandPath.toString());
126+
final CommandNode commandTree = parser.createCommandTree(typeElement.getAnnotation(Command.class).value(), typeElement);
127+
if (commandTree == null) {
128+
return;
131129
}
132130

133-
// Before we print the paths, we do a step I like to refer to as "flattening".
134-
// This does not actually change the structure of the paths, but it moves up any attributes
135-
// relevant for certain things to print correctly (a.e. executor requirements).
136-
pathFlattener.cleanupEmptyPaths(commandPath);
137-
pathFlattener.cleanupPath(commandPath);
138-
pathFlattener.flattenPath(commandPath);
131+
// Before we print the paths we do some post-processing to move some stuff around, which
132+
// is relevant for certain things to print correctly (a.e. executor requirements).
133+
treePostProcessor.cleanupPath(commandTree);
134+
treePostProcessor.applyDefaultExecutorPaths(commandTree);
139135

140136
if (debug) {
141137
// debug log all paths.
142-
messagerWrapper.debug("After flatten: \n{}\n ", commandPath.toString());
138+
messagerWrapper.debug("Command Tree: \n\n{}\n ", commandTree.toString());
143139
}
144140

145141
try {
146-
final CommandTreePrinter printer = new CommandTreePrinter(0, null, commandPath, commandInformation);
142+
final CommandTreePrinter printer = new CommandTreePrinter(0, null, commandTree, commandInformation);
147143
final JavaFileObject obj = processingEnv.getFiler().createSourceFile(printer.getPackageName() + "." + printer.getBrigadierClassName());
148144

149145
try (PrintWriter out = new PrintWriter(obj.openWriter())) {
@@ -178,4 +174,4 @@ private CommandInformation getCommandInformation(@NonNull TypeElement typeElemen
178174
aliases != null ? aliases.value() : null
179175
);
180176
}
181-
}
177+
}

strokk-commands-processor/src/main/java/net/strokkur/commands/internal/intermediate/paths/LiteralCommandPath.java renamed to strokk-commands-processor/src/main/java/net/strokkur/commands/internal/arguments/MultiLiteralCommandArgument.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,24 @@
1515
* You should have received a copy of the GNU Lesser General Public
1616
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
1717
*/
18-
package net.strokkur.commands.internal.intermediate.paths;
18+
package net.strokkur.commands.internal.arguments;
1919

20-
import net.strokkur.commands.internal.arguments.LiteralCommandArgument;
20+
import org.jetbrains.annotations.UnmodifiableView;
2121

22-
import java.util.List;
22+
import javax.lang.model.element.Element;
23+
import java.util.Set;
2324

24-
public class LiteralCommandPath extends SimpleCommandPathImpl<LiteralCommandArgument> {
25+
public interface MultiLiteralCommandArgument extends CommandArgument {
2526

26-
public LiteralCommandPath(final List<LiteralCommandArgument> arguments) {
27-
super(arguments);
27+
static MultiLiteralCommandArgument multiLiteral(Set<String> literals, Element element) {
28+
return new MultiLiteralCommandArgumentImpl(literals, element);
2829
}
2930

31+
@UnmodifiableView
32+
Set<String> literals();
33+
3034
@Override
31-
SimpleCommandPathImpl<LiteralCommandArgument> createLeftSplit(final List<LiteralCommandArgument> args) {
32-
return new LiteralCommandPath(args);
35+
default String getName() {
36+
return "<multiliteral>";
3337
}
3438
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* StrokkCommands - A super simple annotation based zero-shade Paper command API library.
3+
* Copyright (C) 2025 Strokkur24
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package net.strokkur.commands.internal.arguments;
19+
20+
import org.jetbrains.annotations.UnmodifiableView;
21+
22+
import javax.lang.model.element.Element;
23+
import java.util.Collections;
24+
import java.util.Set;
25+
26+
record MultiLiteralCommandArgumentImpl(Set<String> literals, Element element) implements MultiLiteralCommandArgument {
27+
28+
@Override
29+
public @UnmodifiableView Set<String> literals() {
30+
return Collections.unmodifiableSet(this.literals);
31+
}
32+
}

strokk-commands-processor/src/main/java/net/strokkur/commands/internal/arguments/RequiredCommandArgumentImpl.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.Objects;
2525

2626
public class RequiredCommandArgumentImpl implements RequiredCommandArgument {
27-
2827
private final BrigadierArgumentType argumentType;
2928
private final String name;
3029
private final Element element;
@@ -37,7 +36,7 @@ public RequiredCommandArgumentImpl(final BrigadierArgumentType argumentType, fin
3736
this.suggestionProvider = null;
3837
}
3938

40-
public RequiredCommandArgumentImpl(final BrigadierArgumentType argumentType, final String name, final Element element, final SuggestionProvider suggestionProvider) {
39+
public RequiredCommandArgumentImpl(final BrigadierArgumentType argumentType, final String name, final Element element, final @Nullable SuggestionProvider suggestionProvider) {
4140
this.argumentType = argumentType;
4241
this.name = name;
4342
this.element = element;

0 commit comments

Comments
 (0)