> getPredicateInfers() {
+ return Collections.unmodifiableList(ArgumentTypeInfer.PREDICATE_INFERS);
+ }
+
/**
* Removes the argument type inference for the specified type, including the primitive form.
* @param boxed The boxed type to unregister the argument type from.
@@ -112,9 +171,20 @@ public static void unregisterWithPrimitive(
* @throws ArgumentTypeInferException If no argument type is found for the specified type.
*/
public static @NotNull ArgumentType> get(@NotNull Class> clazz) {
- return Optional.ofNullable(ArgumentTypeInfer.INFER_ARGUMENT_TYPES_MAP.get(clazz))
- .map(Supplier::get)
- .orElseThrow(() -> new ArgumentTypeInferException(clazz));
+ var infer = Optional.ofNullable(ArgumentTypeInfer.INFER_ARGUMENT_TYPES_MAP.get(clazz))
+ .map(Supplier::get);
+
+ if (infer.isPresent())
+ return infer.get();
+
+ var predicateInfer = ArgumentTypeInfer.PREDICATE_INFERS.stream()
+ .filter(c -> c.matches(clazz))
+ .findFirst();
+
+ if (predicateInfer.isPresent())
+ return predicateInfer.get().apply(clazz);
+
+ throw new ArgumentTypeInferException(clazz);
}
@@ -163,6 +233,7 @@ void registerWithTuple(
registerWithPrimitive(BooleanArgumentType::new, Boolean.class, boolean.class);
register(() -> new FileArgumentType(false), File.class);
+ setDefaultPredicateInfers();
registerWithTuple(IntegerArgumentType::new, Integer.class, int.class);
registerWithTuple(FloatArgumentType::new, Float.class, float.class);
@@ -171,4 +242,9 @@ void registerWithTuple(
registerWithTuple(ShortArgumentType::new, Short.class, short.class);
registerWithTuple(ByteArgumentType::new, Byte.class, byte.class);
}
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private static void setDefaultPredicateInfers() {
+ register(Enum.class::isAssignableFrom, c -> new EnumArgumentType(c), "EnumArgumentType");
+ }
}
\ No newline at end of file
diff --git a/src/main/java/lanat/argumentTypes/EnumArgumentType.java b/src/main/java/lanat/argumentTypes/EnumArgumentType.java
index 6baebcd88..97c8164b3 100644
--- a/src/main/java/lanat/argumentTypes/EnumArgumentType.java
+++ b/src/main/java/lanat/argumentTypes/EnumArgumentType.java
@@ -2,6 +2,13 @@
import org.jetbrains.annotations.NotNull;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Optional;
+
/**
* An argument type that takes a valid enum value.
*
@@ -13,23 +20,56 @@
public class EnumArgumentType> extends SingleValueListArgumentType {
/**
* Creates a new enum argument type.
- * @param defaultValue The default value of the enum type. This is also used to infer the type of the enum.
+ * @param clazz The class of the enum type to use.
*/
- public EnumArgumentType(@NotNull T defaultValue) {
- super(defaultValue.getDeclaringClass().getEnumConstants(), defaultValue);
+ public EnumArgumentType(@NotNull Class clazz) {
+ super(clazz.getEnumConstants());
+ this.setDefault(clazz);
}
/**
- * Creates a new enum argument type.
+ * Sets the default value of the enum type by using the {@link Default} annotation.
* @param clazz The class of the enum type to use.
*/
- public EnumArgumentType(@NotNull Class clazz) {
- super(clazz.getEnumConstants());
- }
+ private void setDefault(@NotNull Class clazz) {
+ var defaultFields = Arrays.stream(clazz.getDeclaredFields())
+ .filter(f -> f.isAnnotationPresent(Default.class))
+ .toList();
+
+ if (defaultFields.isEmpty())
+ return;
+ if (defaultFields.size() > 1)
+ throw new IllegalArgumentException("Only one default value can be set.");
+
+ this.setInitialValue(
+ Arrays.stream(this.listValues)
+ .filter(v -> v.name().equals(defaultFields.get(0).getName()))
+ .findFirst()
+ .orElseThrow()
+ );
+ }
@Override
protected @NotNull String valueToString(@NotNull T value) {
- return value.name();
+ try {
+ return Optional.ofNullable(value.getClass().getField(value.name()).getAnnotation(WithName.class))
+ .map(WithName::value)
+ .orElseGet(value::name);
+ } catch (NoSuchFieldException e) {
+ return value.name();
+ }
}
+
+ /** An annotation that specifies the name the user will have to write to select this value. */
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface WithName {
+ String value();
+ }
+
+ /** An annotation that specifies the default value of the enum type. */
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Default { }
}
\ No newline at end of file
diff --git a/src/main/java/lanat/argumentTypes/SingleValueListArgumentType.java b/src/main/java/lanat/argumentTypes/SingleValueListArgumentType.java
index 86a732304..26cd6ee08 100644
--- a/src/main/java/lanat/argumentTypes/SingleValueListArgumentType.java
+++ b/src/main/java/lanat/argumentTypes/SingleValueListArgumentType.java
@@ -1,6 +1,7 @@
package lanat.argumentTypes;
import lanat.ArgumentType;
+import lanat.utils.UtlMisc;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import textFormatter.FormatOption;
@@ -47,14 +48,21 @@ protected SingleValueListArgumentType(@NotNull T @NotNull [] listValues) {
if (this.listValues.length == 0)
throw new IllegalArgumentException("The list of values cannot be empty.");
- return Stream.of(this.listValues)
+ var sanitized = Stream.of(this.listValues)
.map(this::valueToString)
.map(String::trim)
.peek(v -> {
+ if (v.isEmpty())
+ throw new IllegalArgumentException("Value cannot be empty.");
+
if (v.chars().anyMatch(Character::isWhitespace))
throw new IllegalArgumentException("Value cannot contain spaces: '" + v + "'.");
})
- .toArray(String[]::new);
+ .toList();
+
+ UtlMisc.requireUniqueElements(sanitized, e -> new IllegalArgumentException("Duplicate value: '" + e + "'."));
+
+ return sanitized.toArray(String[]::new);
}
/**
@@ -75,7 +83,7 @@ public T parseValues(@NotNull String @NotNull [] values) {
return this.listValues[i];
}
- this.addError("Invalid value: '" + values[0] + "'.");
+ this.addError("Value '" + values[0] + "' not matching any in " + this.getRepresentation());
return null;
}
@@ -105,9 +113,16 @@ public T parseValues(@NotNull String @NotNull [] values) {
@Override
public @Nullable String getDescription() {
+ var initialValue = this.getInitialValue();
+
return "Specify one of the following values: "
+ String.join(", ", Stream.of(this.listValuesStr).toList())
- + (this.getInitialValue() == null ? "" : (". Default is " + this.getInitialValue()))
+ + (
+ initialValue == null
+ ? ""
+ : (". Default is " + TextFormatter.of(this.valueToString(initialValue), SimpleColor.YELLOW)
+ .addFormat(FormatOption.BOLD))
+ )
+ ".";
}
}
\ No newline at end of file
diff --git a/src/main/java/lanat/helpRepresentation/HelpFormatter.java b/src/main/java/lanat/helpRepresentation/HelpFormatter.java
index 4e2bcac65..c15fede27 100644
--- a/src/main/java/lanat/helpRepresentation/HelpFormatter.java
+++ b/src/main/java/lanat/helpRepresentation/HelpFormatter.java
@@ -198,7 +198,7 @@ public final void removeLayoutItems(int... indices) {
final var buffer = new StringBuilder();
for (int i = 0; i < this.layout.size(); i++) {
- final var generatedContent = this.layout.get(i).generate(this, cmd);
+ final var generatedContent = this.layout.get(i).generate(cmd);
if (generatedContent == null)
continue;
diff --git a/src/main/java/lanat/helpRepresentation/LayoutItem.java b/src/main/java/lanat/helpRepresentation/LayoutItem.java
index cabc71ab6..d16711084 100644
--- a/src/main/java/lanat/helpRepresentation/LayoutItem.java
+++ b/src/main/java/lanat/helpRepresentation/LayoutItem.java
@@ -39,7 +39,7 @@ public static LayoutItem of(@NotNull Function<@NotNull Command, @Nullable String
/**
* Creates a new {@link LayoutItem} with the given {@link Supplier} that generates a {@link String}.
*
- * @param layoutGenerator the supplier that generates the content of the layout item
+ * @param layoutGenerator the typeSupplier that generates the content of the layout item
* @return the new LayoutItem
*/
public static LayoutItem of(@NotNull Supplier<@Nullable String> layoutGenerator) {
@@ -140,18 +140,16 @@ public LayoutItem withTitle(String title) {
/**
* Generates the content of the layout item. The reason this method requires a {@link HelpFormatter} is because it
* provides the indent size and the parent command.
- *
- * @param helpFormatter the help formatter that is generating the help message
* @return the content of the layout item
*/
- public @Nullable String generate(@NotNull HelpFormatter helpFormatter, @NotNull Command cmd) {
+ public @Nullable String generate(@NotNull Command cmd) {
final var content = this.generator.apply(cmd);
return (content == null || content.isEmpty()) ? null : (
System.lineSeparator().repeat(this.marginTop)
+ (this.title == null ? "" : this.title + System.lineSeparator().repeat(2))
// strip() is used here because trim() also removes \022 (escape character)
- + UtlString.indent(content.strip(), this.indentCount * helpFormatter.getIndentSize())
+ + UtlString.indent(content.strip(), this.indentCount * HelpFormatter.getIndentSize())
+ System.lineSeparator().repeat(this.marginBottom)
);
}
diff --git a/src/test/java/lanat/test/units/TestArgumentTypes.java b/src/test/java/lanat/test/units/TestArgumentTypes.java
index 5cf6b6291..b4f725fd1 100644
--- a/src/test/java/lanat/test/units/TestArgumentTypes.java
+++ b/src/test/java/lanat/test/units/TestArgumentTypes.java
@@ -15,7 +15,16 @@
public class TestArgumentTypes extends UnitTests {
private enum TestEnum {
- ONE, TWO, THREE
+ ONE,
+ @EnumArgumentType.Default
+ TWO,
+ THREE
+ }
+
+ private enum TestEnum2 {
+ ONE,
+ TWO,
+ THREE
}
@Override
@@ -31,8 +40,8 @@ protected TestingParser setParser() {
.defaultValue(new Integer[] { 10101 })
);
this.addArgument(Argument.create(new FileArgumentType(true), "file"));
- this.addArgument(Argument.create(new EnumArgumentType<>(TestEnum.TWO), "enum"));
- this.addArgument(Argument.create(new EnumArgumentType<>(TestEnum.class), "enum2"));
+ this.addArgument(Argument.create(new EnumArgumentType<>(TestEnum.class), "enum"));
+ this.addArgument(Argument.create(new EnumArgumentType<>(TestEnum2.class), "enum2"));
this.addArgument(Argument.create(new OptListArgumentType(List.of("foo", "bar", "qux"), "qux"), "optlist"));
this.addArgument(Argument.create(new OptListArgumentType("foo", "bar", "qux"), "optlist2"));
this.addArgument(Argument.create(new KeyValuesArgumentType<>(new IntegerArgumentType()), "key-value"));
@@ -107,8 +116,8 @@ public void testEnum() {
assertEquals(TestEnum.TWO, this.parser.parseGetValues("").get("enum").orElse(null)); // default value
// test without default value
- assertEquals(TestEnum.ONE, this.parseArg("enum2", "ONE"));
- assertEquals(TestEnum.TWO, this.parseArg("enum2", "TWO"));
+ assertEquals(TestEnum2.ONE, this.parseArg("enum2", "ONE"));
+ assertEquals(TestEnum2.TWO, this.parseArg("enum2", "TWO"));
this.assertNotPresent("enum2");
}