Skip to content
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

Allow required arguments after optional arguments (Linking Edition) #395

Merged
merged 18 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d143de1
allowing required arguments to come after optional arguments
DerEchtePilz Jan 5, 2023
09d1bd2
change AbstractArgument#linkArguments to accept an actual argument in…
DerEchtePilz Jan 5, 2023
5567697
Remove imports from CommandAPIMain. Rename AbstractArgument#linkArgum…
DerEchtePilz Jan 6, 2023
8aecdfe
make AbstractArgument#combineWith() copy requirements and permissions…
DerEchtePilz Jan 6, 2023
e206166
add documentation for the combineWith method
DerEchtePilz Jan 6, 2023
5ad5d9e
Merge branch 'JorelAli:dev/dev' into dev/dev
DerEchtePilz Jan 6, 2023
d340cd3
fixing argument generation in AbstractCommandAPICommand#getArgumentsT…
DerEchtePilz Jan 6, 2023
9b5b663
fix AbstractCommandAPICommand#getArgumentsToRegister() for real this …
DerEchtePilz Jan 6, 2023
a598955
handle chained calls of AbstractArgument#combineWith() correctly
DerEchtePilz Jan 7, 2023
b5f0ee9
make requirements and permissions copy values correctly
DerEchtePilz Jan 7, 2023
7e54622
removes an unused import in AbstractCommandAPICommand
DerEchtePilz Jan 7, 2023
162fd76
improve a message for the combined arguments examples
DerEchtePilz Jan 7, 2023
858097f
improve JavaDocs for AbstractArgument#combineWith()
DerEchtePilz Jan 7, 2023
6f0878e
optimize AbstractCommandAPICommand#getArgumentsToRegister(), modify O…
DerEchtePilz Jan 7, 2023
7a90e77
remove an unnecessary check in AbstractCommandAPICommand#register
DerEchtePilz Jan 7, 2023
af681d7
improve JavaDocs of AbstractArgument#combineWith()
DerEchtePilz Jan 7, 2023
2577d57
Merge JorelAli:dev/dev into dev/dev
DerEchtePilz Feb 3, 2023
b287a86
Merge 'DerEchtePilz:dev/dev' into 'dev/dev'
DerEchtePilz Feb 23, 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 @@ -22,6 +22,7 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

Expand All @@ -33,8 +34,9 @@

/**
* A builder used to create commands to be registered by the CommandAPI.
* @param <Impl> The class extending this class, used as the return type for chain calls
* @param <Argument> The implementation of AbstractArgument used by the class extending this class
*
* @param <Impl> The class extending this class, used as the return type for chain calls
* @param <Argument> The implementation of AbstractArgument used by the class extending this class
* @param <CommandSender> The CommandSender class used by the class extending this class
*/
public abstract class AbstractCommandAPICommand<Impl extends AbstractCommandAPICommand<Impl, Argument, CommandSender>,
Expand Down Expand Up @@ -90,7 +92,7 @@ public final Impl withArguments(Argument... args) {

/**
* Appends the optional arguments to the current command builder.
*
* <p>
* 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
Expand All @@ -107,7 +109,7 @@ public Impl withOptionalArguments(List<Argument> args) {

/**
* Appends the optional arguments to the current command builder.
*
* <p>
* This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional
*
* @param args Arguments that this command can accept
Expand Down Expand Up @@ -325,42 +327,46 @@ public Impl copy() {

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

DerEchtePilz marked this conversation as resolved.
Show resolved Hide resolved
// Check optional argument constraints
// They can only be at the end, no required argument can follow an optional argument
int firstOptionalArgumentIndex = -1;
boolean seenOptionalArgument = false;
for (int i = 0; i < argumentsArray.length; i++) {
if (argumentsArray[i].isOptional()) {
if (firstOptionalArgumentIndex == -1) {
firstOptionalArgumentIndex = i;
}
seenOptionalArgument = true;
} else if (seenOptionalArgument) {
// Argument is not optional
throw new OptionalArgumentException(meta.commandName);
Iterator<Argument> argumentIterator = List.of(argumentsArray).iterator();

// Collect all required arguments, adding them as a command once finding the first optional
while(argumentIterator.hasNext()) {
Argument next = argumentIterator.next();
if(next.isOptional()) {
argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
currentCommand.addAll(unpackCombinedArguments(next));
break;
}
currentCommand.addAll(unpackCombinedArguments(next));
}

// 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) {
@SuppressWarnings("unchecked")
Argument[] optionalArguments = (Argument[]) new AbstractArgument[i];
System.arraycopy(argumentsArray, 0, optionalArguments, 0, i);
argumentsToRegister.add(optionalArguments);
}
// Collect the optional arguments, adding each one as a valid command
while (argumentIterator.hasNext()) {
Argument next = argumentIterator.next();
if(!next.isOptional()) {
throw new OptionalArgumentException(meta.commandName); // non-optional argument after optional
}
}

// If there were no optional arguments, let's just return the array of
// arguments we were going to register normally.
if(argumentsToRegister.isEmpty()) {
argumentsToRegister.add(argumentsArray);
argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
currentCommand.addAll(unpackCombinedArguments(next));
}

// All the arguments expanded, also handles when there are no optional arguments
argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0]));
return argumentsToRegister;
}

private List<Argument> unpackCombinedArguments(Argument argument) {
if (!argument.hasCombinedArguments()) {
return List.of(argument);
}
List<Argument> combinedArguments = new ArrayList<>();
combinedArguments.add(argument);
for (Argument subArgument : argument.getCombinedArguments()) {
subArgument.copyPermissionsAndRequirements(argument);
combinedArguments.addAll(unpackCombinedArguments(subArgument));
}
return combinedArguments;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@
*******************************************************************************/
package dev.jorel.commandapi.arguments;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import dev.jorel.commandapi.AbstractArgumentTree;
import dev.jorel.commandapi.CommandPermission;
import dev.jorel.commandapi.executors.CommandArguments;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

/**
* The core abstract class for Command API arguments
*
Expand Down Expand Up @@ -227,6 +227,13 @@ public final Impl withRequirement(Predicate<CommandSender> requirement) {
return instance();
}

/**
* Resets the requirements for this command
*/
void resetRequirements() {
this.requirements = s -> true;
}

/////////////////
// Listability //
/////////////////
Expand Down Expand Up @@ -258,6 +265,7 @@ public Impl setListed(boolean listed) {
/////////////////

private boolean isOptional = false;
private final List<Argument> combinedArguments = new ArrayList<>();

/**
* Returns true if this argument will be optional when executing the command this argument is included in
Expand All @@ -279,6 +287,42 @@ public Impl setOptional(boolean optional) {
return instance();
}

/**
* Returns a list of arguments linked to this argument.
*
* @return A list of arguments linked to this argument
*/
public List<Argument> getCombinedArguments() {
return combinedArguments;
}

/**
* Returns true if this argument has linked arguments.
*
* @return true if this argument has linked arguments
*/
public boolean hasCombinedArguments() {
return !combinedArguments.isEmpty();
}

/**
* Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional arguments
* by ignoring they exist until they are added to the arguments array for registration.
*
* This method also causes permissions and requirements from this argument to be copied over to the arguments you want to combine
* this argument with. Their permissions and requirements will be ignored.
*
* @param combinedArguments The arguments to combine to this argument
* @return this current argument
*/
@SafeVarargs
public final Impl combineWith(Argument... combinedArguments) {
for (Argument argument : combinedArguments) {
this.combinedArguments.add(argument);
}
return instance();
}

///////////
// Other //
///////////
Expand All @@ -297,6 +341,18 @@ public List<String> getEntityNames(Object argument) {
return Collections.singletonList(null);
}

/**
* Copies permissions and requirements from the provided argument to this argument
* This also resets additional permissions and requirements.
*
* @param argument The argument to copy permissions and requirements from
*/
public void copyPermissionsAndRequirements(Argument argument) {
this.resetRequirements();
this.withRequirement(argument.getRequirements());
this.withPermission(argument.getArgumentPermission());
}

@Override
public String toString() {
return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
public class OptionalArgumentException extends RuntimeException {

public OptionalArgumentException(String commandName) {
super("Failed to register command /" + commandName + " because a required argument cannot follow an optional argument!");
super("Failed to register command /" + commandName + " because an unlinked required argument cannot follow an optional argument!");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,32 @@ void optionalArguments() {
})
.register();
/* ANCHOR_END: optionalArguments2 */

/* ANCHOR: optionalArguments3 */
new CommandAPICommand("rate")
.withOptionalArguments(new StringArgument("topic").combineWith(new IntegerArgument("rating", 0, 10)))
.withOptionalArguments(new PlayerArgument("target"))
.executes((sender, args) -> {
String topic = (String) args.get("topic");
if(topic == null) {
sender.sendMessage(
"Usage: /rate <topic> <rating> <player>(optional)",
"Select a topic to rate, then give a rating between 0 and 10",
"You can optionally add a player at the end to give the rating to"
);
return;
}

// We know this is not null because rating is required if topic is given
int rating = (int) args.get("rating");

// The target player is optional, so give it a default here
CommandSender target = (CommandSender) args.getOrDefault("target", sender);

target.sendMessage("Your " + topic + " was rated: " + rating + "/10");
})
.register();
/* ANCHOR_END: optionalArguments3 */
}

void permissions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,32 @@ CommandAPICommand("sayhi")
})
.register()
/* ANCHOR_END: optionalArguments2 */

/* ANCHOR: optionalArguments3 */
CommandAPICommand("rate")
.withOptionalArguments(StringArgument("topic").combineWith(IntegerArgument("rating", 0, 10)))
.withOptionalArguments(PlayerArgument("target"))
.executes(CommandExecutor { sender, args ->
val topic: String? = args["topic"] as String?
if (topic == null) {
sender.sendMessage(
"Usage: /rate <topic> <rating> <player>(optional)",
"Select a topic to rate, then give a rating between 0 and 10",
"You can optionally add a player at the end to give the rating to"
)
return@CommandExecutor
}

// We know this is not null because rating is required if topic is given
val rating = args["rating"] as Int

// The target player is optional, so give it a default here
val target: CommandSender = args.getOrDefault("target", sender) as CommandSender

target.sendMessage("Your $topic was rated: $rating/10")
})
.register()
/* ANCHOR_END: optionalArguments3 */
}

fun permissions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,32 @@ commandAPICommand("sayhi") {
}
}
/* ANCHOR_END: optionalArguments2 */

/* ANCHOR: optionalArguments3 */
commandAPICommand("rate") {
argument(StringArgument("topic").setOptional(true).combineWith(IntegerArgument("rating", 0, 10)))
playerArgument("target", optional = true)
anyExecutor { sender, args ->
val topic: String? = args["topic"] as String?
if (topic == null) {
sender.sendMessage(
"Usage: /rate <topic> <rating> <player>(optional)",
"Select a topic to rate, then give a rating between 0 and 10",
"You can optionally add a player at the end to give the rating to"
)
return@anyExecutor
}

// We know this is not null because rating is required if topic is given
val rating = args["rating"] as Int

// The target player is optional, so give it a default here
val target: CommandSender = args.getOrDefault("target", sender) as CommandSender

target.sendMessage("Your $topic was rated: $rating/10")
}
}
/* ANCHOR_END: optionalArguments3 */
}

fun proxysender() {
Expand Down
Loading