8000 Implementing optional arguments by DerEchtePilz · Pull Request #393 · CommandAPI/CommandAPI · GitHub
[go: up one dir, main page]

Skip to content

Implementing optional arguments #393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cdb4c08
implementing optional arguments
DerEchtePilz Jan 3, 2023
96a03ec
Add optional arguments to the Kotlin DSL. Rename commandapi-kotlin-bu…
DerEchtePilz Jan 4, 2023
5b46749
add missing JavaDocs on AbstractArgument#setOptional(boolean)
DerEchtePilz Jan 4, 2023
2ad5828
fixing build error
DerEchtePilz Jan 4, 2023
3cf8297
adding documentation for optional arguments
DerEchtePilz Jan 4, 2023
6eb09e5
fixing dependency in the Bukkit Kotlin DSL example
DerEchtePilz Jan 4, 2023
7c4f635
address code review
DerEchtePilz Jan 4, 2023
a25547d
create optional_arguments.md
DerEchtePilz Jan 4, 2023
d1c0088
change /kill command to /sayhi
DerEchtePilz Jan 4, 2023
5ad1a21
make Mardown Linter happy (hopefully)
DerEchtePilz Jan 4, 2023
c82feff
fix wrong enchor end
DerEchtePilz Jan 4, 2023
f777925
address code review
DerEchtePilz Jan 4, 2023
33228d4
convert tabs to spaces in Examples.java
DerEchtePilz Jan 4, 2023
c226296
Merge branch 'dev/dev' into dev/dev
JorelAli Jan 4, 2023
210bed6
refactor AbstractCommandAPICommand#register()
DerEchtePilz Jan 4, 2023
afbd40a
Merge branch 'DerEchtePilz:dev/dev' into 'dev/dev'
DerEchtePilz Jan 4, 2023
7e36de1
simplifying AbstractCommandAPICommand#getArgumentsToRegister()
DerEchtePilz Jan 4, 2023
458b8d8
refactor AbstractCommandAPICommand#register() again
DerEchtePilz Jan 4, 2023
3e7c6ca
Add a section 'Setting existing arguments as optional arguments' to o…
DerEchtePilz Jan 4, 2023
d913acc
remove the List parameter from AbstractCommandAPICommand#getCommandsT…
DerEchtePilz Jan 4, 2023
6266990
Merge branch 'dev/dev' into dev/dev
DerEchtePilz Jan 4, 2023
ce835c6
fix a bug where a required argument would be registered as an optiona…
DerEchtePilz Jan 4, 2023
473bc75
implement multiple getOrDefault methods for the CommandArguments class
DerEchtePilz Jan 4, 2023
a31b755
adding documentation for the getOrDefault method
DerEchtePilz Jan 4, 2023
572beb9
address code review
DerEchtePilz Jan 4, 2023
c8c4960
make CommandArguments#get(int) not throw an ArrayIndexOutOfBoundsExce…
DerEchtePilz Jan 4, 2023
3008d60
Merge branch 'dev/dev' into dev/dev
DerEchtePilz Jan 4, 2023
a76530d
fix variable names after merge
DerEchtePilz Jan 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import dev.jorel.commandapi.arguments.AbstractArgument;
import dev.jorel.commandapi.arguments.IGreedyArgument;
import dev.jorel.commandapi.exceptions.GreedyArgumentException;
import dev.jorel.commandapi.exceptions.OptionalArgumentException;

import java.io.IOException;
import java.util.ArrayList;
Expand All @@ -39,7 +40,7 @@
public abstract class AbstractCommandAPICommand<Impl extends AbstractCommandAPICommand<Impl, Argument, CommandSender>,
Argument extends AbstractArgument<?, ?, Argument, CommandSender>, CommandSender> extends ExecutableCommand<Impl, CommandSender> {

protected List<Argument> args = new ArrayList<>();
protected List<Argument> arguments = new ArrayList<>();
protected List<Impl> subcommands = new ArrayList<>();
protected boolean isConverted;

Expand Down Expand Up @@ -71,7 +72,7 @@ protected AbstractCommandAPICommand(CommandMetaData<CommandSender> metaData) {
* @return this command builder
*/
public Impl withArguments(List<Argument> args) {
this.args.addAll(args);
this.arguments.addAll(args);
return instance();
}

Expand All @@ -83,7 +84,41 @@ public Impl withArguments(List<Argument> args) {
*/
@SafeVarargs
public final Impl withArguments(Argument... args) {
this.args.addAll(Arrays.asList(args));
this.arguments.addAll(Arrays.asList(args));
return instance();
}

/**
* Appends the optional arguments to the current command builder.
*
* This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional
*
* @param args A <code>List</code> that represents the arguments that this
* command can accept
* @return this command builder
*/
public Impl withOptionalArguments(List<Argument> args) {
for (Argument argument : args) {
argument.setOptional(true);
this.arguments.add(argument);
}
return instance();
}

/**
* Appends the optional arguments to the current command builder.
*
* This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional
*
* @param args Arguments that this command can accept
* @return this command builder
*/
@SafeVarargs
public final Impl withOptionalArguments(Argument... args) {
for (Argument argument : args) {
argument.setOptional(true);
this.arguments.add(argument);
}
return instance();
}

Expand Down Expand Up @@ -115,7 +150,7 @@ public Impl withSubcommands(Impl... subcommands) {
* @return the list of arguments that this command has
*/
public List<Argument> getArguments() {
return args;
return arguments;
}

/**
Expand All @@ -124,7 +159,7 @@ public List<Argument> getArguments() {
* @param args the arguments that this command has
*/
public void setArguments(List<Argument> args) {
this.args = args;
this.arguments = args;
}

/**
Expand Down Expand Up @@ -194,8 +229,8 @@ void flatten(Impl rootCommand, List<Argument> prevArguments, Impl subcommand) {
// - has no subcommands(?)
// Honestly, if you're asking how or why any of this works, I don't
// know because I just trialled random code until it started working
rootCommand.args = prevArguments;
rootCommand.withArguments(subcommand.args);
rootCommand.arguments = prevArguments;
rootCommand.withArguments(subcommand.arguments);
rootCommand.executor = subcommand.executor;
rootCommand.subcommands = new ArrayList<>();
rootCommand.register();
Expand All @@ -212,29 +247,38 @@ public void register() {
CommandAPI.logWarning("Command /" + meta.commandName + " is being registered after the server had loaded. Undefined behavior ahead!");
}
try {
Argument[] argumentsArr = (Argument[]) (args == null ? new AbstractArgument[0] : args.toArray(AbstractArgument[]::new));
Argument[] argumentsArray = (Argument[]) (arguments == null ? new AbstractArgument[0] : arguments.toArray(AbstractArgument[]::new));

// Check IGreedyArgument constraints
for (int i = 0, numGreedyArgs = 0; i < argumentsArr.length; i++) {
if (argumentsArr[i] instanceof IGreedyArgument) {
if (++numGreedyArgs > 1 || i != argumentsArr.length - 1) {
throw new GreedyArgumentException(argumentsArr);
for (int i = 0, numGreedyArgs = 0; i < argumentsArray.length; i++) {
if (argumentsArray[i] instanceof IGreedyArgument) {
if (++numGreedyArgs > 1 || i != argumentsArray.length - 1) {
throw new GreedyArgumentException(argumentsArray);
}
}
}

// Assign the command's permissions to arguments if the arguments don't already
// have one
for (Argument argument : argumentsArr) {
for (Argument argument : argumentsArray) {
if (argument.getArgumentPermission() == null) {
argument.withPermission(meta.permission);
}
}

// Create a List<Argument[]> that is used to register optional arguments
List<Argument[]> argumentsToRegister = getArgumentsToRegister(argumentsArray);

if (executor.hasAnyExecutors()) {
// Need to cast handler to the right CommandSender type so that argumentsArr and executor are accepted
// Need to cast handler to the right CommandSender type so that argumentsArray and executor are accepted
CommandAPIHandler<Argument, CommandSender, ?> handler = (CommandAPIHandler<Argument, CommandSender, ?>) CommandAPIHandler.getInstance();
handler.register(meta, argumentsArr, executor, isConverted);
if (argumentsToRegister.isEmpty()) {
handler.register(meta, argumentsArray, executor, isConverted);
} else {
for (Argument[] arguments : argumentsToRegister) {
handler.register(meta, arguments, executor, isConverted);
}
}
}

// Convert subcommands into multiliteral arguments
Expand All @@ -248,11 +292,43 @@ public void register() {

public Impl copy() {
Impl command = newConcreteCommandAPICommand(new CommandMetaData<>(this.meta));
command.args = new ArrayList<>(this.args);
command.arguments = new ArrayList<>(this.arguments);
command.subcommands = new ArrayList<>(this.subcommands);
command.isConverted = this.isConverted;
return command;
}

protected abstract Impl newConcreteCommandAPICommand(CommandMetaData<CommandSender> metaData);

private List<Argument[]> getArgumentsToRegister(Argument[] argumentsArray) {
List<Argument[]> argumentsToRegister = new ArrayList<>();

// Check optional argument constraints
// They can only be at the end, no required argument can follow an optional argument
int firstOptionalArgumentIndex = -1;
for (int i = 0, optionalArgumentIndex = -1; i < argumentsArray.length; i++) {
if (argumentsArray[i].isOptional()) {
if (firstOptionalArgumentIndex == -1) {
firstOptionalArgumentIndex = i;
}
optionalArgumentIndex = i;
} else if (optionalArgumentIndex != -1) {
// Argument is not optional
throw new OptionalArgumentException();
}
}

// Create a List of arrays that hold arguments to register optional arguments
// if optional arguments have been found
if (firstOptionalArgumentIndex != -1) {
for (int i = 0; i <= argumentsArray.length; i++) {
if (i >= firstOptionalArgumentIndex) {
Argument[] arguments = (Argument[]) new AbstractArgument[i];
System.arraycopy(argumentsArray, 0, arguments, 0, i);
argumentsToRegister.add(arguments);
}
}
}
return argumentsToRegister;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,32 @@ public Impl setListed(boolean listed) {
return instance();
}

/////////////////
// Optionality //
/////////////////

private boolean isOptional = false;

/**
* Returns true if this argument will be optional when executing the command this argument is included in
*
* @return true if this argument will be optional when executing the command this argument is included in
*/
public boolean isOptional() {
return isOptional;
}

/**
* Sets whether this argument will be optional when executing the command this argument is included in
*
* @param optional if true, this argument will be optional when executing the command this argument is included in
* @return this current argument
*/
public Impl setOptional(boolean optional) {
this.isOptional = optional;
return instance();
}

///////////
// Other //
///////////
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.jorel.commandapi.exceptions;

public class OptionalArgumentException extends RuntimeException {

public OptionalArgumentException() {
super("Optional argument can not be followed by a required argument!");
}

}
2 changes: 1 addition & 1 deletion commandapi-documentation-code/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
</dependency>
<dependency>
<groupId>dev.jorel</groupId>
<artifactId>commandapi-kotlin-bukkit</artifactId>
<artifactId>commandapi-bukkit-kotlin</artifactId>
<version>${project.version}</version>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,23 +1304,19 @@ void resultingcommandexecutor3(){
}

{
/* ANCHOR: argumentkillcmd */
new CommandAPICommand("kill")
/* ANCHOR: argumentsayhicmd */
new CommandAPICommand("sayhi")
.withOptionalArguments(new PlayerArgument("target"))
.executesPlayer((player, args) -> {
player.setHealth(0);
})
.register();
/* ANCHOR_END: argumentkillcmd */

/* ANCHOR: argumentkillcmd2 */
// Register our second /kill <target> command
new CommandAPICommand("kill")
.withArguments(new PlayerArgument("target"))
.executesPlayer((player, args) -> {
((Player) args.get(0)).setHealth(0);
Player target = (Player) args.get("target");
if (target != null) {
target.sendMessage("Hi!");
} else {
player.sendMessage("Hi!");
}
})
.register();
/* ANCHOR_END: argumentkillcmd2 */
/* ANCHOR_END: argumentsayhicmd */
}

@SuppressWarnings("unused")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,24 +1230,20 @@ CommandAPICommand("mycommand")
/* ANCHOR_END: argumentsyntax3 */
}

fun argumentkillcmd() {
/* ANCHOR: argumentkillcmd */
CommandAPICommand("kill")
.executesPlayer(PlayerCommandExecutor { player, _ ->
player.health = 0.0
})
.register()
/* ANCHOR_END: argumentkillcmd */

/* ANCHOR: argumentkillcmd2 */
// Register our second /kill <target> command
CommandAPICommand("kill")
.withArguments(PlayerArgument("target"))
.executesPlayer(PlayerCommandExecutor { _, args ->
(args[0] as Player).health = 0.0
fun argumentsayhicmd() {
/* ANCHOR: argumentsayhicmd */
CommandAPICommand("sayhi")
.withOptionalArguments(PlayerArgument("target"))
.executesPlayer(PlayerCommandExecutor { player, args ->
val target: Player? = args["target"] as Player?
if (target != null) {
target.sendMessage("Hi!")
} else {
player.sendMessage("Hi!")
}
})
.register()
/* ANCHOR_END: argumentkillcmd2 */
/* ANCHOR_END: argumentsayhicmd */
}

@Suppress("unused")
Expand Down Expand Up @@ -1727,7 +1723,7 @@ fun argumentsuggestions2_2() {
val arguments = listOf<Argument<*>>(
PlayerArgument("friend").replaceSuggestions(ArgumentSuggestions.strings { info ->
Friends.getFriends(info.sender())
})
} )
)

CommandAPICommand("friendtp")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1383,24 +1383,20 @@ commandAPICommand("mycommand") {
/* ANCHOR_END: argumentsyntax3 */
}

fun argumentkillcmd() {
/* ANCHOR: argumentkillcmd */
commandAPICommand("kill") {
playerExecutor { player, _ ->
player.health = 0.0
}
}
/* ANCHOR_END: argumentkillcmd */

/* ANCHOR: argumentkillcmd2 */
// Register our second /kill <target> command#
commandAPICommand("kill") {
playerArgument("target")
playerExecutor { _, args ->
(args[0] as Player).health = 0.0
fun argumentsayhicmd() {
/* ANCHOR: argumentsayhicmd */
commandAPICommand("sayhi") {
optionalArgument(PlayerArgument("target"))
playerExecutor { player, args ->
val target: Player? = args["target"] as Player?
if (target != null) {
target.sendMessage("Hi!")
} else {
player.sendMessage("Hi!")
}
}
}
/* ANCHOR_END: argumentkillcmd2 */
/* ANCHOR_END: argumentsayhicmd */
}

@Suppress("unused")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<version>9.0.0-SNAPSHOT</version>
</parent>

<artifactId>commandapi-kotlin-bukkit</artifactId>
<artifactId>commandapi-bukkit-kotlin</artifactId>

<properties>
<kotlin.version>1.7.20</kotlin.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ inline fun commandAPICommand(name: String, predicate: Predicate<CommandSender>,
inline fun CommandAPICommand.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(base.apply(block))
fun CommandAPICommand.arguments(vararg arguments: Argument<*>): CommandAPICommand = withArguments(*arguments)

inline fun CommandAPICommand.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withOptionalArguments(base.apply(block))
fun CommandAPICommand.optionalArguments(vararg arguments: Argument<*>): CommandAPICommand = withOptionalArguments(*arguments)

inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = CommandAPICommand(name).apply(command)
fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command)
inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command))
Expand Down
2 changes: 1 addition & 1 deletion commandapi-platforms/commandapi-bukkit/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
<module>commandapi-bukkit-plugin</module>
<!-- TODO: MAKE THIS COMPILE <module>commandapi-bukkit-plugin-test</module> -->
<module>commandapi-bukkit-shade</module>
<module>commandapi-kotlin-bukkit</module>
<module>commandapi-bukkit-kotlin</module>
</modules>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ inline fun commandAPICommand(name: String, predicate: Predicate<CommandSource>,
inline fun CommandAPICommand.argument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withArguments(base.apply(block))
fun CommandAPICommand.arguments(vararg arguments: Argument<*>): CommandAPICommand = withArguments(*arguments)

inline fun CommandAPICommand.optionalArgument(base: Argument<*>, block: Argument<*>.() -> Unit = {}): CommandAPICommand = withOptionalArguments(base.apply(block))
fun CommandAPICommand.optionalArguments(vararg arguments: Argument<*>): CommandAPICommand = withOptionalArguments(*arguments)

inline fun subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = CommandAPICommand(name).apply(command)
fun CommandAPICommand.subcommand(command: CommandAPICommand): CommandAPICommand = withSubcommand(command)
inline fun CommandAPICommand.subcommand(name: String, command: CommandAPICommand.() -> Unit = {}): CommandAPICommand = withSubcommand(CommandAPICommand(name).apply(command))
Expand Down
Loading
0