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 extends IParameterInfo> 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 extends IParameterInfo> parameters) {
+ this(StandardParameterParserFactory.create(), parameters);
+ }
+
+ public ArgumentsParser(final IParameterParserFactory parameterParserFactory, final List extends IParameterInfo> 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