diff --git a/gb-argparse/.gitignore b/gb-argparse/.gitignore new file mode 100644 index 0000000..1eb68f9 --- /dev/null +++ b/gb-argparse/.gitignore @@ -0,0 +1,5 @@ +/target/ +/.settings/ +/.project +/.classpath +/.factorypath diff --git a/gb-argparse/pom.xml b/gb-argparse/pom.xml new file mode 100644 index 0000000..1552762 --- /dev/null +++ b/gb-argparse/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + gb-argparse + + + com.g2forge.gearbox + gb-project + 0.0.10-SNAPSHOT + ../gb-project/pom.xml + + + Gearbox ArgParse + A simple command line argument parsing library. + + + + com.g2forge.habitat + ha-metadata + ${habitat.version} + + + \ No newline at end of file diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelp.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelp.java new file mode 100644 index 0000000..98a2b3f --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelp.java @@ -0,0 +1,15 @@ +package com.g2forge.gearbox.argparse; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +public @interface ArgumentHelp { + public String value(); +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelpException.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelpException.java new file mode 100644 index 0000000..1afee58 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentHelpException.java @@ -0,0 +1,9 @@ +package com.g2forge.gearbox.argparse; + +public class ArgumentHelpException extends RuntimeException { + public ArgumentHelpException(String message) { + super(message); + } + + private static final long serialVersionUID = 8436426521191021803L; +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentParser.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentParser.java new file mode 100644 index 0000000..adbd19f --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/ArgumentParser.java @@ -0,0 +1,243 @@ +package com.g2forge.gearbox.argparse; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import com.g2forge.alexandria.java.core.helpers.HCollection; +import com.g2forge.alexandria.java.core.helpers.HStream; +import com.g2forge.alexandria.java.fluent.optional.IOptional; +import com.g2forge.alexandria.java.function.IFunction1; +import com.g2forge.alexandria.java.text.HString; +import com.g2forge.habitat.metadata.value.predicate.IPredicate; +import com.g2forge.habitat.metadata.value.subject.ISubject; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ArgumentParser { + @Data + @Builder(toBuilder = true) + @RequiredArgsConstructor + protected static class ParameterParserInfo { + protected final int index; + + protected final IParameterParser parser; + } + + protected static final Set STANDARD_HELP_ARGUMENTS = HCollection.asSet("/h", "/?", "-h", "-help", "--help"); + + public enum HelpArguments { + STANDARD { + @Override + public boolean isHelp(List arguments) { + if (arguments.size() == 1) return STANDARD_HELP_ARGUMENTS.contains(arguments.get(0)); + return false; + } + }, + EMPTY { + @Override + public boolean isHelp(List arguments) { + return arguments.isEmpty(); + } + }; + + public abstract boolean isHelp(List arguments); + } + + protected final Class type; + + protected final Set help; + + public ArgumentParser(Class type) { + this(type, EnumSet.of(HelpArguments.STANDARD)); + } + + @Getter(lazy = true, value = AccessLevel.PROTECTED) + private final Constructor constructor = findConstructor(); + + public T parse(List arguments) { + final IArgumentsParser argumentsParser = getArgumentsParser(); + + final boolean help = getHelp().stream().filter(helpArguments -> helpArguments.isHelp(arguments)).findAny().isPresent(); + if (help) throw new ArgumentHelpException(argumentsParser.generateHelp()); + + final Object[] parsed = argumentsParser.apply(arguments); + return create(parsed); + } + + @Getter(lazy = true, value = AccessLevel.PROTECTED) + private final IArgumentsParser argumentsParser = computeArgumentsParser(); + + protected ArgumentsParser computeArgumentsParser() { + final Parameter[] parameterActuals = getConstructor().getParameters(); + final List parameterInfos = new ArrayList<>(); + for (int i = 0; i < parameterActuals.length; i++) { + parameterInfos.add(new IParameterInfo.ParameterInfoAdapter(i, parameterActuals[i])); + } + final ArgumentsParser argumentsParser = new ArgumentsParser(parameterInfos); + return argumentsParser; + } + + private T create(final Object[] parsed) { + final Constructor constructor = getConstructor(); + try { + return constructor.newInstance(parsed); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private Constructor findConstructor() { + final Constructor[] constructors = type.getDeclaredConstructors(); + if (constructors.length != 1) throw new IllegalArgumentException(String.format("Argument type %1$s has %2$d constructors, only single constructor types are supported (for now).", type, constructors.length)); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Constructor constructor = (Constructor) constructors[0]; + return constructor; + } + + public static T parse(Class type, List arguments) { + return new ArgumentParser<>(type).parse(arguments); + } + + protected interface IArgumentsParser extends IFunction1, Object[]> { + public String generateHelp(); + } + + protected static class ArgumentsParser implements IArgumentsParser { + protected final List parameters; + + /** An in-order list of the positional parameters. May have different size than the orignal parameters as some may be named. */ + protected final List positional; + + /** A map from their names to the named parameters. May have different size than the original parameters as some may be positional. */ + protected final Map named; + + /** A list of parsers, one for each input parameter. */ + protected final List parsers; + + public ArgumentsParser(final List parameters) { + this(StandardParameterParserFactory.create(), parameters); + } + + public ArgumentsParser(final IParameterParserFactory parameterParserFactory, final List parameters) { + // Parse the parameter model from the constructor + this.parameters = parameters; + positional = new ArrayList<>(); + named = new HashMap<>(); + parsers = new ArrayList<>(); + for (int i = 0; i < parameters.size(); i++) { + final IParameterInfo parameter = parameters.get(i); + + final IParameterParser parameterTypeParser = parameterParserFactory.apply(parameter); + parsers.add(parameterTypeParser); + + final ParameterParserInfo info = new ParameterParserInfo(i, parameterTypeParser); + final ISubject subject = parameter.getSubject(); + final NamedParameter annotation = subject.get(NamedParameter.class); + if (annotation != null) named.put(annotation.value(), info); + else positional.add(info); + } + } + + public Object[] apply(List arguments) { + final Object[] parsed = new Object[parameters.size()]; + final boolean[] set = new boolean[parameters.size()]; + int p = 0; + // Parse the arguments + for (final ListIterator argumentIterator = arguments.listIterator(); argumentIterator.hasNext();) { + final int argumentIndex = argumentIterator.nextIndex(); + final String argument = argumentIterator.next(); + try { + boolean foundNamed = false; + for (Map.Entry entry : named.entrySet()) { + if (argument.startsWith(entry.getKey())) { + final ParameterParserInfo info = entry.getValue(); + final int parameterIndex = info.getIndex(); + parsed[parameterIndex] = info.getParser().parse(parameters.get(parameterIndex), argumentIterator); + set[parameterIndex] = true; + foundNamed = true; + break; + } + } + if (!foundNamed) { + argumentIterator.previous(); + final ParameterParserInfo info = positional.get(p++); + final int index = info.getIndex(); + parsed[index] = info.getParser().parse(parameters.get(index), argumentIterator); + set[index] = true; + } + } catch (Throwable throwable) { + throw new UnparseableArgumentException(argumentIndex, argument, throwable); + } + } + + // Fill in any unparsed parameters with defaults + for (IParameterInfo parameter : parameters) { + if (!set[parameter.getIndex()]) { + final IOptional defaultValue = parsers.get(parameter.getIndex()).getDefault(parameter); + if (defaultValue.isEmpty()) throw new UnspecifiedParameterException(parameter); + else { + parsed[parameter.getIndex()] = defaultValue.get(); + set[parameter.getIndex()] = true; + } + } + } + return parsed; + } + + @Override + public String generateHelp() { + final StringBuilder retVal = new StringBuilder(); + final Map positionalHelp = new LinkedHashMap<>(); + for (ParameterParserInfo info : positional) { + final IParameterInfo parameter = parameters.get(info.getIndex()); + if (!retVal.isEmpty()) retVal.append(' '); + retVal.append('<').append(parameter.getName()).append('>'); + + final IPredicate predicate = parameter.getSubject().bind(ArgumentHelp.class); + if (predicate.isPresent()) positionalHelp.put(parameter.getName(), predicate.get0().value()); + } + final boolean hasNamed = named.isEmpty(); + if (!hasNamed && !retVal.isEmpty()) retVal.append(" [...]"); + + if (!positionalHelp.isEmpty() || !hasNamed) { + if (!retVal.isEmpty()) retVal.append("\n"); + final int padded = HStream.concat(positionalHelp.keySet().stream(), named.keySet().stream()).mapToInt(String::length).max().getAsInt(); + + if (!positionalHelp.isEmpty()) { + for (Map.Entry entry : positionalHelp.entrySet()) { + if (!retVal.isEmpty()) retVal.append('\n'); + retVal.append(entry.getKey()).append(' ').append(entry.getValue()); + } + } + + if (!hasNamed) { + for (Map.Entry entry : named.entrySet()) { + if (!retVal.isEmpty()) retVal.append('\n'); + final IParameterInfo parameter = parameters.get(entry.getValue().getIndex()); + retVal.append(HString.pad(entry.getKey(), " ", padded)); + final ArgumentHelp argumentHelp = parameter.getSubject().get(ArgumentHelp.class); + if (argumentHelp != null) retVal.append(' ').append(argumentHelp.value()); + } + } + } + + return retVal.toString(); + } + } +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/BasicParameterParser.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/BasicParameterParser.java new file mode 100644 index 0000000..228227f --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/BasicParameterParser.java @@ -0,0 +1,47 @@ +package com.g2forge.gearbox.argparse; + +import java.nio.file.Paths; +import java.util.ListIterator; + +import com.g2forge.alexandria.java.fluent.optional.IOptional; +import com.g2forge.alexandria.java.fluent.optional.NullableOptional; + +public enum BasicParameterParser implements IParameterParser { + BOOLEAN { + @Override + public IOptional getDefault(IParameterInfo parameter) { + if (parameter.getSubject().bind(NamedParameter.class).isPresent()) return NullableOptional.of(false); + return NullableOptional.empty(); + } + + @Override + public Object parse(IParameterInfo parameter, ListIterator argumentIterator) { + if (parameter.getSubject().bind(NamedParameter.class).isPresent()) return true; + else return Boolean.valueOf(argumentIterator.next()); + } + }, + PATH { + @Override + public IOptional getDefault(IParameterInfo parameter) { + if (parameter.getSubject().bind(NamedParameter.class).isPresent()) return NullableOptional.of(null); + return NullableOptional.empty(); + } + + @Override + public Object parse(IParameterInfo parameter, ListIterator argumentIterator) { + return Paths.get(argumentIterator.next()); + } + }, + STRING { + @Override + public IOptional getDefault(IParameterInfo parameter) { + if (parameter.getSubject().bind(NamedParameter.class).isPresent()) return NullableOptional.of(null); + return NullableOptional.empty(); + } + + @Override + public Object parse(IParameterInfo parameter, ListIterator argumentIterator) { + return argumentIterator.next(); + } + }; +} \ No newline at end of file diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IArgumentsType.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IArgumentsType.java new file mode 100644 index 0000000..c2863a8 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IArgumentsType.java @@ -0,0 +1,6 @@ +package com.g2forge.gearbox.argparse; + +/** + * An optional marker interface for all types meant to be parsed as command line arguments. + */ +public interface IArgumentsType {} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterInfo.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterInfo.java new file mode 100644 index 0000000..4bfbdb5 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterInfo.java @@ -0,0 +1,60 @@ +package com.g2forge.gearbox.argparse; + +import java.lang.reflect.Parameter; + +import com.g2forge.alexandria.java.adt.name.IStringNamed; +import com.g2forge.habitat.metadata.Metadata; +import com.g2forge.habitat.metadata.value.subject.ISubject; + +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +public interface IParameterInfo extends IStringNamed { + public int getIndex(); + + public ISubject getSubject(); + + public Class getType(); + + @Data + @Builder(toBuilder = true) + @RequiredArgsConstructor + public static class ParameterInfo implements IParameterInfo { + protected final int index; + + protected final Class type; + + protected final String name; + + protected final ISubject subject; + + public ParameterInfo(IParameterInfo that) { + this(that.getIndex(), that.getType(), that.getName(), that.getSubject()); + } + } + + @Data + @Builder(toBuilder = true) + @RequiredArgsConstructor + public static class ParameterInfoAdapter implements IParameterInfo { + protected final int index; + + protected final Parameter parameter; + + @Override + public ISubject getSubject() { + return Metadata.getStandard().of(parameter, null); + } + + @Override + public Class getType() { + return getParameter().getType(); + } + + @Override + public String getName() { + return getParameter().getName(); + } + } +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParser.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParser.java new file mode 100644 index 0000000..a0493fd --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParser.java @@ -0,0 +1,11 @@ +package com.g2forge.gearbox.argparse; + +import java.util.ListIterator; + +import com.g2forge.alexandria.java.fluent.optional.IOptional; + +public interface IParameterParser { + public IOptional getDefault(IParameterInfo parameter); + + public Object parse(IParameterInfo parameter, ListIterator argumentIterator); +} \ No newline at end of file diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParserFactory.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParserFactory.java new file mode 100644 index 0000000..76988c9 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/IParameterParserFactory.java @@ -0,0 +1,5 @@ +package com.g2forge.gearbox.argparse; + +import com.g2forge.alexandria.java.function.IFunction1; + +public interface IParameterParserFactory extends IFunction1 {} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/NamedParameter.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/NamedParameter.java new file mode 100644 index 0000000..4b02956 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/NamedParameter.java @@ -0,0 +1,17 @@ +package com.g2forge.gearbox.argparse; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({ FIELD, METHOD, PARAMETER }) +public @interface NamedParameter { + public String value(); +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/StandardParameterParserFactory.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/StandardParameterParserFactory.java new file mode 100644 index 0000000..2f51ce6 --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/StandardParameterParserFactory.java @@ -0,0 +1,57 @@ +package com.g2forge.gearbox.argparse; + +import java.lang.reflect.Array; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +import com.g2forge.alexandria.java.core.helpers.HCollection; +import com.g2forge.alexandria.java.core.marker.ISingleton; +import com.g2forge.alexandria.java.fluent.optional.IOptional; +import com.g2forge.alexandria.java.fluent.optional.NullableOptional; + +public class StandardParameterParserFactory implements IParameterParserFactory, ISingleton { + protected StandardParameterParserFactory() {} + + protected static final StandardParameterParserFactory INSTANCE = new StandardParameterParserFactory(); + + public static StandardParameterParserFactory create() { + return INSTANCE; + } + + @Override + public IParameterParser apply(IParameterInfo parameter) { + final Class type = parameter.getType(); + return computeParser(type); + } + + private IParameterParser computeParser(final Class type) { + if (Path.class.equals(type)) return BasicParameterParser.PATH; + if (String.class.equals(type)) return BasicParameterParser.STRING; + if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) return BasicParameterParser.BOOLEAN; + if (type.isArray()) { + final Class componentType = type.getComponentType(); + final IParameterParser componentParser = computeParser(componentType); + return new IParameterParser() { + @Override + public IOptional getDefault(IParameterInfo parameter) { + if (parameter.getSubject().bind(NamedParameter.class).isPresent()) return NullableOptional.of(null); + return NullableOptional.empty(); + } + + @Override + public Object parse(IParameterInfo parameter, ListIterator argumentIterator) { + final List arguments = HCollection.asList(argumentIterator.next().split(",+")); + final IParameterInfo componentParameter = new IParameterInfo.ParameterInfo(parameter).toBuilder().type(componentType).build(); + final List values = new ArrayList<>(); + for (final ListIterator iterator = arguments.listIterator(); iterator.hasNext();) { + values.add(componentParser.parse(componentParameter, iterator)); + } + return values.toArray(size -> (Object[]) Array.newInstance(componentType, size)); + } + }; + } + return null; + } +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnparseableArgumentException.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnparseableArgumentException.java new file mode 100644 index 0000000..5e692bd --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnparseableArgumentException.java @@ -0,0 +1,15 @@ +package com.g2forge.gearbox.argparse; + +public class UnparseableArgumentException extends IllegalArgumentException { + public UnparseableArgumentException(int index, String argument, Throwable cause) { + super(String.format("Failed to parse argument #%1$d (\"%2$s\")!", index, argument), cause); + this.index = index; + this.argument = argument; + } + + private static final long serialVersionUID = 4965745309281531552L; + + protected final int index; + + protected final String argument; +} diff --git a/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnspecifiedParameterException.java b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnspecifiedParameterException.java new file mode 100644 index 0000000..d3b727f --- /dev/null +++ b/gb-argparse/src/main/java/com/g2forge/gearbox/argparse/UnspecifiedParameterException.java @@ -0,0 +1,12 @@ +package com.g2forge.gearbox.argparse; + +public class UnspecifiedParameterException extends IllegalArgumentException { + private static final long serialVersionUID = 5080026838657436944L; + + public UnspecifiedParameterException(IParameterInfo parameter) { + super(String.format("Parameter #%1$d (%2$s) was not specified!", parameter.getIndex(), parameter.getName())); + this.parameter = parameter; + } + + protected final IParameterInfo parameter; +} diff --git a/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParser.java b/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParser.java new file mode 100644 index 0000000..6342433 --- /dev/null +++ b/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParser.java @@ -0,0 +1,115 @@ +package com.g2forge.gearbox.argparse; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Test; + +import com.g2forge.alexandria.java.core.helpers.HCollection; +import com.g2forge.alexandria.test.HAssert; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +public class TestArgumentParser { + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class Flag { + @NamedParameter("--flag") + protected final boolean flag; + } + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class Mixed { + @NamedParameter("--flag") + @ArgumentHelp("An optional flag") + protected final boolean flag; + + @ArgumentHelp("A path") + protected final Path path; + } + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class None {} + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class Ordered { + protected final String string; + } + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class Array { + protected final String[] strings; + } + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + protected static class Unparseable { + protected final Unparseable unparseable; + } + + @Test + public void unparseable() { + HAssert.assertThrows(UnparseableArgumentException.class, () -> ArgumentParser.parse(Unparseable.class, HCollection.asList("argument"))); + } + + @Test + public void array() { + final Array actual = ArgumentParser.parse(Array.class, HCollection.asList("A,B")); + HAssert.assertEquals(new Array(new String[] { "A", "B" }), actual); + } + + @Test + public void flagFalse() { + final Flag actual = ArgumentParser.parse(Flag.class, HCollection.asList()); + HAssert.assertEquals(new Flag(false), actual); + } + + @Test + public void flagTrue() { + final Flag actual = ArgumentParser.parse(Flag.class, HCollection.asList("--flag")); + HAssert.assertEquals(new Flag(true), actual); + } + + @Test + public void missing() { + HAssert.assertException(UnspecifiedParameterException.class, "Parameter #0 (string) was not specified!", () -> ArgumentParser.parse(Ordered.class, HCollection.asList())); + } + + @Test + public void mixed1() { + final String expected = "path"; + final Mixed actual = ArgumentParser.parse(Mixed.class, HCollection.asList("--flag", expected)); + HAssert.assertEquals(new Mixed(true, Paths.get(expected)), actual); + } + + @Test + public void mixed2() { + final String expected = "path"; + final Mixed actual = ArgumentParser.parse(Mixed.class, HCollection.asList(expected, "--flag")); + HAssert.assertEquals(new Mixed(true, Paths.get(expected)), actual); + } + + @Test + public void none() { + HAssert.assertInstanceOf(None.class, ArgumentParser.parse(None.class, HCollection.asList())); + } + + @Test + public void ordered() { + final String expected = "value"; + final Ordered actual = ArgumentParser.parse(Ordered.class, HCollection.asList(expected)); + HAssert.assertEquals(new Ordered(expected), actual); + } +} diff --git a/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParserHelp.java b/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParserHelp.java new file mode 100644 index 0000000..554a2d1 --- /dev/null +++ b/gb-argparse/src/test/java/com/g2forge/gearbox/argparse/TestArgumentParserHelp.java @@ -0,0 +1,45 @@ +package com.g2forge.gearbox.argparse; + +import org.junit.Test; + +import com.g2forge.alexandria.java.core.helpers.HCollection; +import com.g2forge.alexandria.java.function.IThrowRunnable; +import com.g2forge.alexandria.test.HAssert; +import com.g2forge.alexandria.test.HMatchers; + +public class TestArgumentParserHelp { + @Test + public void unparseable() { + assertHelp("", TestArgumentParser.Unparseable.class); + } + + private void assertHelp(final String help, final Class type) { + final IThrowRunnable runnable = () -> ArgumentParser.parse(type, HCollection.asList("--help")); + HAssert.assertThat(runnable, HMatchers.isThrowable(ArgumentHelpException.class, HMatchers.equalTo(help))); + } + + @Test + public void array() { + assertHelp("", TestArgumentParser.Array.class); + } + + @Test + public void flag() { + assertHelp("--flag", TestArgumentParser.Flag.class); + } + + @Test + public void mixed() { + assertHelp(" [...]\n\npath A path\n--flag An optional flag", TestArgumentParser.Mixed.class); + } + + @Test + public void none() { + assertHelp("", TestArgumentParser.None.class); + } + + @Test + public void ordered() { + assertHelp("", TestArgumentParser.Ordered.class); + } +} diff --git a/lombok.config b/lombok.config index 815b786..10b285b 100644 --- a/lombok.config +++ b/lombok.config @@ -1,2 +1,4 @@ lombok.anyConstructor.addConstructorProperties = true -config.stopBubbling = true \ No newline at end of file +config.stopBubbling = true +lombok.copyableAnnotations += com.g2forge.gearbox.argparse.NamedParameter +lombok.copyableAnnotations += com.g2forge.gearbox.argparse.ArgumentHelp \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8d450b3..39b59db 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 gearbox @@ -12,10 +14,12 @@ Gearbox - A library of clients and frameworks for building them, including REST and command line. + A library of clients and frameworks for building them, + including REST and command line. gb-project + gb-argparse gb-command gb-command-test gb-ssh