Skip to content

Commit

Permalink
GH-378 GH-459 Support literal arguments. Support collections types fo…
Browse files Browse the repository at this point in the history
…r OptionalArg (#458)

* Support literal arguments.

* Add support for OptionalArg with a collection.
  • Loading branch information
Rollczi authored Oct 23, 2024
1 parent 1cfccf1 commit e8edda5
Show file tree
Hide file tree
Showing 17 changed files with 421 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.rollczi.litecommands.annotations;

import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.argument.KeyAnnotationResolver;
import dev.rollczi.litecommands.annotations.argument.collection.ArgCollectionArgumentProcessor;
import dev.rollczi.litecommands.annotations.optional.OptionalArg;
import dev.rollczi.litecommands.annotations.varargs.VarargsAnyArgumentProcessor;
import dev.rollczi.litecommands.annotations.argument.nullable.NullableArgumentProcessor;
import dev.rollczi.litecommands.annotations.async.AsyncAnnotationResolver;
import dev.rollczi.litecommands.annotations.command.CommandAnnotationProcessor;
Expand All @@ -11,6 +13,7 @@
import dev.rollczi.litecommands.annotations.execute.ExecuteAnnotationResolver;
import dev.rollczi.litecommands.annotations.flag.FlagAnnotationProcessor;
import dev.rollczi.litecommands.annotations.join.JoinArgumentProcessor;
import dev.rollczi.litecommands.annotations.literal.LiteralArgumentProcessor;
import dev.rollczi.litecommands.annotations.meta.MarkMetaAnnotationResolver;
import dev.rollczi.litecommands.annotations.optional.OptionalArgArgumentProcessor;
import dev.rollczi.litecommands.annotations.permission.PermissionAnnotationResolver;
Expand Down Expand Up @@ -66,7 +69,9 @@ public static <SENDER> AnnotationProcessorService<SENDER> defaultService() {
// profile processors (they apply profiles to arguments)
.register(new FlagAnnotationProcessor<>())
.register(new VarargsArgumentProcessor<>())
.register(new ArgCollectionArgumentProcessor<>())
.register(new VarargsAnyArgumentProcessor<>(Arg.class))
.register(new VarargsAnyArgumentProcessor<>(OptionalArg.class))
.register(new LiteralArgumentProcessor<>())
.register(new JoinArgumentProcessor<>())
.register(new QuotedAnnotationProcessor<>())
.register(new NullableArgumentProcessor<>())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.rollczi.litecommands.annotations.literal;

import dev.rollczi.litecommands.annotations.requirement.RequirementDefinition;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.jetbrains.annotations.ApiStatus;

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@RequirementDefinition(type = RequirementDefinition.Type.ARGUMENT, nameProviders = { "value" })
@ApiStatus.Experimental
public @interface Literal {

String[] value();

boolean ignoreCase() default false;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.rollczi.litecommands.annotations.literal;

import dev.rollczi.litecommands.annotations.argument.profile.ProfileAnnotationProcessor;
import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.literal.LiteralProfile;
import java.lang.reflect.Parameter;

public class LiteralArgumentProcessor<SENDER> extends ProfileAnnotationProcessor<SENDER, Literal, LiteralProfile> {

public LiteralArgumentProcessor() {
super(Literal.class);
}

@Override
protected LiteralProfile createProfile(Parameter parameter, Literal annotation, Argument<?> argument) {
return new LiteralProfile(annotation.value(), annotation.ignoreCase());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;

/**
* Universal annotation processor for requirements such as {@link Argument}, {@link ContextRequirement} and {@link BindRequirement}.
Expand Down Expand Up @@ -63,13 +64,7 @@ private String getName(RequirementDefinition definition, Parameter parameter, An
for (String attributeName : definition.nameProviders()) {
try {
Method attributeMethod = annotation.annotationType().getDeclaredMethod(attributeName);
Object object = attributeMethod.invoke(annotation);

if (!(object instanceof String)) {
throw new LiteCommandsReflectException("Attribute " + attributeName + " in annotation " + annotation.annotationType().getSimpleName() + " must return a String");
}

String nameCandidate = (String) object;
String nameCandidate = getPrimaryName(annotation, attributeName, attributeMethod);

if (!nameCandidate.isEmpty()) {
return nameCandidate;
Expand All @@ -83,4 +78,24 @@ private String getName(RequirementDefinition definition, Parameter parameter, An
return parameter.getName();
}

private static @NotNull String getPrimaryName(Annotation annotation, String attributeName, Method attributeMethod) throws IllegalAccessException, InvocationTargetException {
Object object = attributeMethod.invoke(annotation);

if (object instanceof String[]) {
String[] array = (String[]) object;

if (array.length == 0) {
return "";
}

return array[0];
}

if (object instanceof String) {
return (String) object;
}

throw new LiteCommandsReflectException("Attribute " + attributeName + " in annotation " + annotation.annotationType().getSimpleName() + " must return a String");
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package dev.rollczi.litecommands.annotations.argument.collection;
package dev.rollczi.litecommands.annotations.varargs;

import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.argument.profile.ProfileAnnotationProcessor;
import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.argument.resolver.collector.VarargsProfile;
import dev.rollczi.litecommands.input.raw.RawCommand;
import dev.rollczi.litecommands.reflect.type.TypeToken;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.util.Collection;

public class ArgCollectionArgumentProcessor<SENDER> extends ProfileAnnotationProcessor<SENDER, Arg, VarargsProfile> {
public class VarargsAnyArgumentProcessor<SENDER, A extends Annotation> extends ProfileAnnotationProcessor<SENDER, A, VarargsProfile> {

public ArgCollectionArgumentProcessor() {
super(Arg.class);
public VarargsAnyArgumentProcessor(Class<A> annotationClass) {
super(annotationClass);
}

@Override
protected VarargsProfile createProfile(Parameter parameter, Arg annotation, Argument<?> argument) {
protected VarargsProfile createProfile(Parameter parameter, A annotation, Argument<?> argument) {
TypeToken<?> collectorType = TypeToken.ofParameter(parameter);

if (collectorType.isArray()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dev.rollczi.litecommands.annotations.argument.resolver.collector;import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.optional.OptionalArg;
import dev.rollczi.litecommands.unit.annotations.LiteTestSpec;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class OptionalCollectionTest extends LiteTestSpec {

@Command(name = "test")
static class OptionalCollectionTestCommand {
@Execute
String test(@OptionalArg List<String> names) {
if (names == null) {
return "null";
}

return String.join(", ", names);
}
}

@Test
@DisplayName("Should always return not null collection")
void testOptionalCollectionExecute() {
platform.execute("test")
.assertSuccess("");

platform.execute("test name1 name2 name3")
.assertSuccess("name1, name2, name3");
}

@Test
void testOptionalCollectionSuggest() {
platform.suggest("test ")
.assertSuggest("<names>");

platform.suggest("test name1 name2 ")
.assertSuggest("<names>");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dev.rollczi.litecommands.annotations.literal;import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.join.Join;
import dev.rollczi.litecommands.unit.annotations.LiteTestSpec;
import org.junit.jupiter.api.Test;

class LiteralTest extends LiteTestSpec {

@Command(name = "user")
static class LiteralTestCommand {
@Execute
String setGroup(@Arg String name, @Literal("group set") String literal, @Arg String group) {
return name + " " + literal + " " + group;
}

@Execute
String ban(@Arg String name, @Literal("ban") String ban, @Join String reason) {
return name + " " + ban + " " + reason;
}
@Execute
String getName(@Arg String name, @Literal("get name") String literal) {
return name + " " + literal;
}

@Execute
String getUuid(@Arg String name, @Literal("get uuid") String literal) {
return name + " " + literal;
}

}

@Test
void testLiteralExecute() {
platform.execute("user Rollczi group set Admin")
.assertSuccess("Rollczi group set Admin");

platform.execute("user Rollczi get name")
.assertSuccess("Rollczi get name");

platform.execute("user Rollczi get uuid")
.assertSuccess("Rollczi get uuid");

platform.execute("user Rollczi ban test reason")
.assertSuccess("Rollczi ban test reason");
}

@Test
void testLiteralSuggest() {
platform.suggest("user Rollczi ")
.assertSuggest("group set", "get name", "get uuid", "ban");

platform.suggest("user Rollczi group set ")
.assertSuggest("<group>");

platform.suggest("user Rollczi ban ")
.assertSuggest("<reason>");

platform.suggest("user Rollczi get name ")
.assertSuggest();

platform.suggest("user Rollczi get uuid ")
.assertSuggest();

platform.suggest("user Rollczi get ")
.assertSuggest("name", "uuid");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dev.rollczi.litecommands.argument.resolver.nullable.NullableProfile;
import dev.rollczi.litecommands.flag.FlagProfile;
import dev.rollczi.litecommands.join.JoinProfile;
import dev.rollczi.litecommands.literal.LiteralProfile;
import dev.rollczi.litecommands.quoted.QuotedProfile;
import org.jetbrains.annotations.ApiStatus;

Expand All @@ -17,6 +18,13 @@ public final class ProfileNamespaces {
private ProfileNamespaces() {
}

/**
* Literal profile allows
* parsing static values such as {@code /user Rollczi group Admin} where {@code group} is a literal.
* @since 3.8.0
*/
public static final ArgumentProfileNamespace<LiteralProfile> LITERAL = LiteralProfile.NAMESPACE;

/**
* Join profile allows joining multiple arguments into one.
* @since 3.7.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import dev.rollczi.litecommands.argument.profile.ArgumentProfileNamespace;
import dev.rollczi.litecommands.meta.MetaKey;
import dev.rollczi.litecommands.meta.MetaType;
import dev.rollczi.litecommands.priority.PriorityLevel;
import dev.rollczi.litecommands.reflect.type.TypeToken;

public class VarargsProfile implements ArgumentProfile<VarargsProfile> {
Expand Down Expand Up @@ -32,4 +33,8 @@ public ArgumentProfileNamespace<VarargsProfile> getNamespace() {
return NAMESPACE;
}

@Override
public PriorityLevel getPriority() {
return PriorityLevel.HIGH;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package dev.rollczi.litecommands.literal;

import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.argument.parser.ParseResult;
import dev.rollczi.litecommands.argument.profile.ProfileNamespaces;
import dev.rollczi.litecommands.argument.profile.ProfiledMultipleArgumentResolver;
import dev.rollczi.litecommands.input.raw.RawInput;
import dev.rollczi.litecommands.invalidusage.InvalidUsage;
import dev.rollczi.litecommands.invocation.Invocation;
import dev.rollczi.litecommands.range.Range;
import dev.rollczi.litecommands.shared.FailedReason;
import dev.rollczi.litecommands.suggestion.SuggestionContext;
import dev.rollczi.litecommands.suggestion.SuggestionResult;
import java.util.ArrayList;
import java.util.List;

public class LiteralArgumentResolver<SENDER> extends ProfiledMultipleArgumentResolver<SENDER, String, LiteralProfile> {

public LiteralArgumentResolver() {
super(ProfileNamespaces.LITERAL);
}

@Override
public ParseResult<String> parse(Invocation<SENDER> invocation, Argument<String> argument, RawInput rawInput, LiteralProfile literalProfile) {
return parse(new ArrayList<>(), rawInput, literalProfile);
}

private ParseResult<String> parse(List<String> before, RawInput rawInput, LiteralProfile literalProfile) {
int max = literalProfile.getExpectedRange().getMax();

if (before.size() >= max) {
return ParseResult.failure(FailedReason.of(InvalidUsage.Cause.INVALID_ARGUMENT));
}

if (!rawInput.hasNext()) {
return ParseResult.failure(FailedReason.of(InvalidUsage.Cause.MISSING_ARGUMENT));
}

String partOfLiteral = rawInput.next();
String fullLiteral = before.isEmpty() ? partOfLiteral : String.join(" ", before) + " " + partOfLiteral;

if (literalProfile.getLiterals().contains(fullLiteral)) {
return ParseResult.success(fullLiteral);
}

before.add(partOfLiteral);
return parse(before, rawInput, literalProfile);
}

@Override
public Range getRange(Argument<String> argument, LiteralProfile literalProfile) {
return literalProfile.getExpectedRange();
}

@Override
protected SuggestionResult suggest(Invocation<SENDER> invocation, Argument<String> argument, SuggestionContext context, LiteralProfile literalProfile) {
return SuggestionResult.of(literalProfile.getLiterals());
}

}
Loading

0 comments on commit e8edda5

Please sign in to comment.