-
-
Notifications
You must be signed in to change notification settings - Fork 67
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 #394
Conversation
There is actually a use case for this! A help message stating the usage for this command. If a player types |
commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java
Show resolved
Hide resolved
// TODO: Example command, remove before merging | ||
new CommandAPICommand("rate") | ||
.withArguments( | ||
new StringArgument("topic").setOptional(true), | ||
new IntegerArgument("rating", 0, 10), | ||
new PlayerArgument("target").setOptional(true) | ||
) | ||
.executes(info -> { | ||
String topic = (String) info.args().get("topic"); | ||
if(topic == null) { | ||
// Honestly not sure how to justify topic being optional here, | ||
// causing there to be a required argument after an optional one, | ||
// but I'm sure an example exists, right? | ||
info.sender().sendMessage("You didn't give a rating"); | ||
return; | ||
} | ||
|
||
// We know this is not null because rating is required if topic is given | ||
int rating = (int) info.args().get("rating"); | ||
|
||
// The target player is optional, so give it a default here | ||
CommandSender target = (CommandSender) info.args().getOrDefault("target", info.sender()); | ||
|
||
target.sendMessage("Your " + topic + " was rated: " + rating + "/10"); | ||
}) | ||
.register(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Migrate this to the documentation before merging, it's a useful example.
Writing this here again (this comment comes from this comment on discord): That wouldn't invalidate the |
So you would declare your arguments by doing this: withOptionalArguments(new StringArgument("topic").link(new IntegerArgument("rating", 0, 10))) // "Links" the IntegerArgument to the StringArgument
withOptionalArguments(new PlayerArgument("target")) That "linking" makes it so that when checking the optional argument constraints the required IntegerArgument is ignored. |
Also, how would this system handle this:
This was my way of testing that system without needing to import something and I am able to test this in a plain Java project.
However, I would expect it to only generate one result because I have a required argument at the end. |
Yeah, the output there is what I expected. Simplifying that a bit so its easier to read and talk about, the input is:
And the output is:
So, I guess calling a required argument that is after an optional argument "required" isn't quite right, since you don't need to always include it (E is "required", but not present in all of the commands). That is necessary to avoid ambiguity while still being useful. With the example, if E always had to be included, you could either add it to the end of every command and get:
or require all arguments before it, basically ignoring their optional status. So yeah, you are correct that if E (the last argument) was "required" that wouldn't really work. What my system is doing instead is actually pretty much linking the arguments. So E isn't strictly fully required, it is required if D is present. You could say E is linked to D, but without an explicitly linked argument structure, more implicitly by the fact that E is not optional (required) and after D. I hope that explains why the output was correct to be like that? Maybe there is a better word than required for arguments like E and |
Well, what I think is this:
|
Note the example command: new CommandAPICommand("rate")
.withArguments(
new StringArgument("topic").setOptional(true),
new IntegerArgument("rating", 0, 10),
new PlayerArgument("target").setOptional(true)
)
.executes(info -> {
String topic = (String) info.args().get("topic");
if(topic == null) {
info.sender().sendMessage("You didn't give a rating");
return;
}
int rating = (int) info.args().get("rating");
CommandSender target = (CommandSender) info.args().getOrDefault("target", info.sender());
target.sendMessage("Your " + topic + " was rated: " + rating + "/10");
})
.register(); is equivalent to this CommandTree: new CommandTree("rate")
.executes((info) -> info.sender().sendMessage("You didn't give a rating"))
.then(
new StringArgument("topic").then(
new IntegerArgument("rating")
.executes((info) -> {
String topic = (String) info.args().get("topic");
int rating = (int) info.args().get("rating");
info.sender().sendMessage("Your " + topic + " was rated: " + rating + "/10");
})
.then(
new PlayerArgument("target")
.executes((info) -> {
String topic = (String) info.args().get("topic");
int rating = (int) info.args().get("rating");
Player target = (Player) info.args().get("target");
target.sendMessage("Your " + topic + " was rated: " + rating + "/10");
})
)
)
)
.register(); I would consider the example command simpler and easier to understand, making this an example of when this sort of interaction between optional and required arguments would be useful. |
I think there's a bit of merit in the "linked" concept and I think it resolves this ambiguity in the declaration of the The claim is that the new CommandAPICommand("rate")
.withArguments(
new StringArgument("topic").setOptional(true),
new IntegerArgument("rating", 0, 10),
new PlayerArgument("target").setOptional(true)
) leads to this list of possible commands:
However, the ambiguity here is that this could also be interpreted as a valid command:
The requirement that "rating" must follow "topic" is 'lost' in this (the original) implementation. By 'lost', it may be inferred by the implementation of the CommandAPI, but as someone reading the code, it doesn't state this. The idea of having linked arguments addresses this - one (or more) argument(s) that must follow another argument and the whole thing can be classed as "an optional argument". I think the idea of some sort of "combined" argument makes sense. Say we have something like this: class CombinedArgument {
CombinedArgument(Argument... args) { /* Some implementation */ }
} new CommandAPICommand("rate")
.withArguments(
new CombinedArgument(
new StringArgument("topic").setOptional(true), // [1]
new IntegerArgument("rating", 0, 10)
).setOptional(true),
new PlayerArgument("target").setOptional(true)
) In this example, it's absolutely clear that "topic and rating go together". All properties of the inner arguments (e.g. The reason I think this works better than the original "linked" argument is because it avoids conflicting permissions on arguments - if you have a permission set on Briefly touching on some of the earlier conversation, in particular this example:
This was one of the primary reasons that the implementation of optional arguments stood around for 2 years. I initially wanted to have this sort of behaviour, but upon seeing the implementation by @DerEchtePilz, I found that it became a lot easier to interpret and design commands without having to worry about this. (Of course, being able to access arguments by their node name makes this significantly easier to deal with as well!) |
I think we're going to sunset this PR in favour of #395. Linked/combined arguments seems to flow better and have less ambiguity. |
This PR is me implementing what I was trying to explain here in discord so I can explain it better.
Currently, if you put a required argument after an optional argument, you get an
OptionalArgumentException
. However, I think this example command makes a good case for changing that:This command can be executed in the following ways:
Putting the required
<rating>
argument after the optional<topic>
argument indicates that if atopic
is given, arating
is required.To make this work was actually very simple, just a small change to the old system.
AbstractCommandAPICommand#getArgumentsToRegister
now looks like this:TODO before merge:
OptionalArgumentException
? (I don't think it is used anymore)commandapi-bukkit-plugin-test