Skip to content

Commit

Permalink
Merge pull request #9 from DarviL82/dev
Browse files Browse the repository at this point in the history
Version 0.0.3
  • Loading branch information
darvil82 authored Oct 23, 2023
2 parents 63edf15 + 3c49edc commit d99a902
Show file tree
Hide file tree
Showing 58 changed files with 546 additions and 193 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ The package is currently only available on GitHub Packages.
implementation("darvil:lanat")
```

Note that you may need to explicitly specify the version of the package you want to use. (e.g. `darvil:lanat:0.0.1`)
Note that you may need to explicitly specify the version of the package you want to use. (e.g. `darvil:lanat:x.x.x`)

This information is available at the [GitHub Packages documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry#using-a-published-package).

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = "darvil"
version = "0.0.2"
version = "0.0.3"
description = "Command line argument parser"

dependencies {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/lanat/Argument.java
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public void parseValues(short tokenIndex, @NotNull String... values) {
*/
public @Nullable TInner finishParsing() {
final TInner finalValue = this.argType.getFinalValue();
final TInner defaultValue = this.defaultValue == null ? this.argType.getInitialValue() : this.defaultValue;
final TInner defaultValue = UtlMisc.nonNullOrElse(this.defaultValue, this.argType.getInitialValue());

/* no, | is not a typo. We don't want the OR operator to short-circuit, we want all of them to be evaluated
* because the methods have side effects (they add errors to the parser) */
Expand Down Expand Up @@ -597,6 +597,7 @@ void invokeCallbacks(@Nullable Object okValue) {
*/
@Override
public boolean equals(@NotNull Object obj) {
if (obj == this) return true;
if (obj instanceof Argument<?, ?> arg)
return UtlMisc.equalsByNamesAndParentCmd(this, arg);
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/lanat/ArgumentAdder.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ void addArgument(@NotNull ArgumentBuilder<T, TInner> argument) {
@NotNull List<@NotNull Argument<?, ?>> getArguments();

/**
* Checks that all the arguments in this container have unique names.
* Checks that all the arguments in this container are unique.
* @throws ArgumentAlreadyExistsException if there are two arguments with the same name
*/
default void checkUniqueArguments() {
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/lanat/ArgumentBuilder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lanat;

import lanat.argumentTypes.DummyArgumentType;
import lanat.exceptions.ArgumentTypeInferException;
import lanat.utils.UtlReflection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -47,16 +48,17 @@ public class ArgumentBuilder<Type extends ArgumentType<TInner>, TInner> {
if (annotation.argType() != DummyArgumentType.class)
return UtlReflection.instantiate(annotation.argType());

// try to infer the type from the field type
var argTypeMap = ArgumentType.getTypeInfer(field.getType());

// if the type was not found, return null
return argTypeMap == null ? null : UtlReflection.instantiate(argTypeMap);
// try to infer the type from the field type. If it can't be inferred, return null
try {
return ArgumentTypeInfer.get(field.getType());
} catch (ArgumentTypeInferException e) {
return null;
}
}

/**
* Builds an {@link Argument} from the specified field annotated with {@link Argument.Define}.
* Note that this doesn't set the argument type.
* Note that this doesn't set the argument type. Use {@link #setArgTypeFromField(Field)} for that.
*
* @param field the field that will be used to build the argument
* @param <Type> the {@link ArgumentType} subclass that will parse the value passed to the argument
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/lanat/ArgumentGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public class ArgumentGroup
* each one added to this group is because at parsing, we might need to know which arguments were used in this
* group.
* <br><br>
* Sure, we could just use {@link Command#arguments}, but that would mean that we would have to iterate through all
* the arguments in there for filtering ours, which is probably worse.
* Sure, we could just use {@link Command#getArguments()}, but that would mean that we would have
* to iterate through all the arguments in there for filtering ours, which is probably worse.
*/
private final @NotNull List<@NotNull Argument<?, ?>> arguments = new ArrayList<>();

Expand Down Expand Up @@ -274,6 +274,7 @@ public ArgumentGroup getParent() {

@Override
public boolean equals(@NotNull Object obj) {
if (obj == this) return true;
if (obj instanceof ArgumentGroup group)
return this.parentCommand == group.parentCommand && this.name.equals(group.name);
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/lanat/ArgumentGroupAdder.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public interface ArgumentGroupAdder extends NamedWithDescription {
@NotNull List<@NotNull ArgumentGroup> getGroups();

/**
* Checks that all the argument groups in this container have unique names.
* Checks that all the argument groups in this container are unique.
* @throws ArgumentGroupAlreadyExistsException if there are two argument groups with the same name
*/
default void checkUniqueGroups() {
Expand Down
62 changes: 56 additions & 6 deletions src/main/java/lanat/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import lanat.exceptions.IncompatibleCommandTemplateType;
import lanat.parsing.TokenType;
import lanat.parsing.errors.ErrorHandler;
import lanat.utils.UtlMisc;
import lanat.utils.UtlReflection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
Expand Down Expand Up @@ -131,7 +133,11 @@ public static ArgumentParser from(@NotNull Class<? extends CommandTemplate> temp
*/
public static <T extends CommandTemplate>
@NotNull T parseFromInto(@NotNull Class<T> templateClass, @NotNull CLInput input) {
return ArgumentParser.parseFromInto(templateClass, input, opts -> opts.printErrors().exitIfErrors());
return ArgumentParser.parseFromInto(
templateClass,
input,
opts -> opts.printErrors().exitIfErrors().printHelpIfNoInput().exitIfNoInput()
);
}

/**
Expand Down Expand Up @@ -191,7 +197,7 @@ public static ArgumentParser from(@NotNull Class<? extends CommandTemplate> temp
return new ParsedArgumentsRoot(
this,
this.getParser().getParsedArgumentsHashMap(),
this.subCommands.stream().map(Command::getParsedArguments).toList(),
this.getCommands().stream().map(Command::getParsedArguments).toList(),
this.getForwardValue()
);
}
Expand Down Expand Up @@ -239,7 +245,9 @@ public void setVersion(@NotNull String version) {
*/
public void addVersionArgument() {
this.addArgument(Argument.createOfBoolType("version")
.onOk(t -> System.out.println("Version: " + this.getVersion()))
.onOk(t ->
System.out.println("Version: " + UtlMisc.nonNullOrElse(this.getVersion(), "unknown"))
)
.withDescription("Shows the version of this program.")
.allowsUnique()
);
Expand Down Expand Up @@ -366,7 +374,7 @@ private static <T extends CommandTemplate> T into(
// get the name of the argument from the annotation or field name
final String argName = annotation.names().length == 0 ? f.getName() : annotation.names()[0];

final @NotNull Optional<Object> parsedValue = parsedArgs.get(argName);
final @NotNull Optional<?> parsedValue = parsedArgs.get(argName);

try {
// if the field has a value already set and the parsed value is empty, skip it (keep the old value)
Expand All @@ -379,7 +387,7 @@ private static <T extends CommandTemplate> T into(
instance,
f.getType().isAssignableFrom(Optional.class)
? parsedValue
: parsedValue.orElse(null)
: AfterParseOptions.into$getNewFieldValue(f, parsedValue)
);
} catch (IllegalArgumentException e) {
if (parsedValue.isEmpty())
Expand All @@ -390,7 +398,7 @@ private static <T extends CommandTemplate> T into(

throw new IncompatibleCommandTemplateType(
"Field '" + f.getName() + "' of type '" + f.getType().getSimpleName() + "' is not "
+ "compatible with the type (" + parsedValue.getClass().getSimpleName() + ") of the "
+ "compatible with the type (" + parsedValue.get().getClass().getSimpleName() + ") of the "
+ "parsed argument '" + argName + "'"
);

Expand Down Expand Up @@ -456,5 +464,47 @@ private static <T extends CommandTemplate> T into(
throw new RuntimeException(e);
}
}

/**
* {@link #into(Class)} helper method. Returns the new value for the given field based on the parsed value.
* If the parsed value is {@code null}, this method will return {@code null} as well.
* If both the field and the parsed value are arrays, this method will return a new array with the same type.
* @param commandAccesorField The field to get the new value for.
* @param parsedValue The parsed value to get the new value from.
* @return The new value for the given field based on the parsed value. This will be {@code null} if the parsed
* value is {@code null}.
*/
private static Object into$getNewFieldValue(
@NotNull Field commandAccesorField,
@NotNull Optional<?> parsedValue
) {
if (parsedValue.isEmpty())
return null;

final Object value = parsedValue.get();

if (!(commandAccesorField.getType().isArray() && value.getClass().isArray()))
return value;


// handle array types
final var fieldType = commandAccesorField.getType().getComponentType();
final var originalArray = (Object[])value; // to get rid of warnings

try {
// create a new array of the same type as the field.
var newArray = (Object[])Array.newInstance(fieldType, Array.getLength(originalArray));

// copy the values from the original array to the new array
System.arraycopy(originalArray, 0, newArray, 0, originalArray.length);

return newArray;
} catch (ClassCastException e) {
throw new IncompatibleCommandTemplateType(
"Field '" + commandAccesorField.getName() + "' of type '" + commandAccesorField.getType().getSimpleName()
+ "' is not compatible with the type (" + fieldType.arrayType() + ") of the parsed argument"
);
}
}
}
}
41 changes: 1 addition & 40 deletions src/main/java/lanat/ArgumentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.Consumer;

/**
Expand Down Expand Up @@ -78,9 +77,6 @@ public abstract class ArgumentType<T>
private @Nullable ArgumentType<?> parentArgType;
private final @NotNull ArrayList<@NotNull ArgumentType<?>> subTypes = new ArrayList<>();

/** Mapping of types to their corresponding argument types. Used for inferring. */
private static final HashMap<Class<?>, Class<? extends ArgumentType<?>>> INFER_ARGUMENT_TYPES_MAP = new HashMap<>();


/**
* Constructs a new argument type with the specified initial value.
Expand All @@ -92,7 +88,7 @@ public ArgumentType(@NotNull T initialValue) {
}

/**
* Constructs a new argument type with no initial value.
* Constructs a new argument type.
*/
public ArgumentType() {
if (this.getRequiredUsageCount().start() == 0) {
Expand Down Expand Up @@ -298,39 +294,4 @@ public void resetState() {
public @Nullable ArgumentType<?> getParent() {
return this.parentArgType;
}


/**
* Registers an argument type to be inferred for the specified type/s.
* @param type The argument type to infer.
* @param infer The types to infer the argument type for.
*/
public static void registerTypeInfer(@NotNull Class<? extends ArgumentType<?>> type, @NotNull Class<?>... infer) {
for (Class<?> clazz : infer) {
ArgumentType.INFER_ARGUMENT_TYPES_MAP.put(clazz, type);
}
}

/**
* Returns the argument type that should be inferred for the specified type.
* @param clazz The type to infer the argument type for.
* @return The argument type that should be inferred for the specified type. Returns {@code null} if no
* valid argument type was found.
*/
public static Class<? extends ArgumentType<?>> getTypeInfer(@NotNull Class<?> clazz) {
return ArgumentType.INFER_ARGUMENT_TYPES_MAP.get(clazz);
}

// add some default argument types.
static {
// we need to also specify the primitives... wish there was a better way to do this.
ArgumentType.registerTypeInfer(StringArgumentType.class, String.class);
ArgumentType.registerTypeInfer(IntegerArgumentType.class, int.class, Integer.class);
ArgumentType.registerTypeInfer(BooleanArgumentType.class, boolean.class, Boolean.class);
ArgumentType.registerTypeInfer(FloatArgumentType.class, float.class, Float.class);
ArgumentType.registerTypeInfer(DoubleArgumentType.class, double.class, Double.class);
ArgumentType.registerTypeInfer(LongArgumentType.class, long.class, Long.class);
ArgumentType.registerTypeInfer(ShortArgumentType.class, short.class, Short.class);
ArgumentType.registerTypeInfer(ByteArgumentType.class, byte.class, Byte.class);
}
}
98 changes: 98 additions & 0 deletions src/main/java/lanat/ArgumentTypeInfer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package lanat;

import lanat.argumentTypes.*;
import lanat.exceptions.ArgumentTypeInferException;
import lanat.utils.Range;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.HashMap;
import java.util.function.Supplier;

public class ArgumentTypeInfer {
/**
* Mapping of types to their corresponding argument types. Used for inferring.
* Argument types are stored as suppliers so that we have no shared references.
* */
private static final HashMap<Class<?>, Supplier<? extends ArgumentType<?>>> INFER_ARGUMENT_TYPES_MAP = new HashMap<>();
public static final Range DEFAULT_TYPE_RANGE = Range.AT_LEAST_ONE;

/**
* Registers an argument type to be inferred for the specified type/s.
* @param type The argument type to infer.
* @param infer The types to infer the argument type for.
*/
public static void register(@NotNull Supplier<? extends ArgumentType<?>> type, @NotNull Class<?>... infer) {
if (infer.length == 0)
throw new IllegalArgumentException("Must specify at least one type to infer the argument type for.");

for (Class<?> clazz : infer) {
if (clazz.isArray() && clazz.getComponentType().isPrimitive())
throw new IllegalArgumentException("Cannot infer argument type for primitive array type: " + clazz.getName());

if (ArgumentTypeInfer.INFER_ARGUMENT_TYPES_MAP.containsKey(clazz))
throw new IllegalArgumentException("Argument type already registered for type: " + clazz.getName());

ArgumentTypeInfer.INFER_ARGUMENT_TYPES_MAP.put(clazz, type);
}
}

/**
* Returns a new argument type instance for the specified type.
* @param clazz The type to infer the argument type for.
* @return The argument type that should be inferred for the specified type.
* @throws ArgumentTypeInferException If no argument type is found for the specified type.
*/
public static ArgumentType<?> get(@NotNull Class<?> clazz) {
var result = ArgumentTypeInfer.INFER_ARGUMENT_TYPES_MAP.get(clazz);

if (result == null)
throw new ArgumentTypeInferException(clazz);

return result.get();
}

/**
* Registers a numeric argument type with the specified tuple type as well.
* Note that for arrays, only the non-primitive types are inferred.
* @param type The type of the numeric argument type.
* @param array The default value of the numeric argument type.
* @param inferPrimitive The <strong>non-array</strong> types to infer the argument type for.
* @param <Ti> The type of the numeric type.
* @param <T> The type of the tuple argument type.
*/
private static <Ti extends Number, T extends NumberArgumentType<Ti>>
void registerNumericWithTuple(
@NotNull Supplier<T> type,
@NotNull Ti[] array,
@NotNull Class<?> inferPrimitive,
@NotNull Class<?> infer
) {
assert !infer.isPrimitive() && inferPrimitive.isPrimitive()
: "Infer must be a non-primitive type and inferPrimitive must be a primitive type.";

// register both the primitive and non-primitive types
ArgumentTypeInfer.register(type, inferPrimitive, infer);

// register the array type (only the non-primitive type)
ArgumentTypeInfer.register(() -> new MultipleNumbersArgumentType<>(DEFAULT_TYPE_RANGE, array), infer.arrayType());
}

// add some default argument types.
static {
register(StringArgumentType::new, String.class);
register(() -> new MultipleStringsArgumentType(DEFAULT_TYPE_RANGE), String[].class);

register(BooleanArgumentType::new, boolean.class, Boolean.class);

register(() -> new FileArgumentType(false), File.class);

// we need to specify the primitives as well... wish there was a better way to do this.
registerNumericWithTuple(IntegerArgumentType::new, new Integer[] {}, int.class, Integer.class);
registerNumericWithTuple(FloatArgumentType::new, new Float[] {}, float.class, Float.class);
registerNumericWithTuple(DoubleArgumentType::new, new Double[] {}, double.class, Double.class);
registerNumericWithTuple(LongArgumentType::new, new Long[] {}, long.class, Long.class);
registerNumericWithTuple(ShortArgumentType::new, new Short[] {}, short.class, Short.class);
registerNumericWithTuple(ByteArgumentType::new, new Byte[] {}, byte.class, Byte.class);
}
}
Loading

0 comments on commit d99a902

Please sign in to comment.