Initial commit

This commit is contained in:
Isaac Parenteau
2018-07-11 14:44:12 -05:00
commit bbb3c08ba9
11 changed files with 358 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
.classpath
.idea
.project
.settings
npm-debug.log
phantomjs
tags
*.DS_Store
*/bin/
**/.classpath
**/.settings
**/*.swp
**/target
**/.tern-project
**/*.iml
**/git.properties
target/**

14
bitbucket-pipelines.yml Normal file
View File

@ -0,0 +1,14 @@
# This is a sample build configuration for Java (Maven).
# Check our guides at https://confluence.atlassian.com/x/zd-5Mw for more examples.
# Only use spaces to indent your .yml configuration.
# -----
# You can specify a custom docker image from Docker Hub as your build environment.
image: maven:3.3.9
pipelines:
default:
- step:
caches:
- maven
script: # Modify the commands below to build your repository.
- mvn -B verify # -B batch mode makes Maven less verbose

36
pom.xml Normal file
View File

@ -0,0 +1,36 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.locuswors</groupId>
<artifactId>argparser</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ArgParser</name>
<description>Library to parse command lined arguments into a class</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,167 @@
package net.locusworks.argparser;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.locusworks.argparser.annotations.Parameter;
import net.locusworks.argparser.interfaces.IParameterConverter;
public class ArgParser {
private Class<?> argClass;
private List<Field> fields;
private String programName;
public ArgParser() { }
public static ArgParser newBuilder() {
return new ArgParser();
}
public ArgParser withArgClass(Class<?> argClass) {
this.argClass = argClass;
this.fields = Stream.of(argClass.getDeclaredFields())
.filter(f -> f.isAnnotationPresent(Parameter.class))
.map(f -> {
f.setAccessible(true);
return f;
})
.collect(Collectors.toList());
if (fields == null || fields.isEmpty()) throw new IllegalArgumentException("No fields with @Parameter defined");
fields.sort(new Comparator<Field>() {
@Override
public int compare(Field o1, Field o2) {
Integer o1Order = o1.getDeclaredAnnotation(Parameter.class).order();
Integer o2Order = o2.getDeclaredAnnotation(Parameter.class).order();
return o1Order.compareTo(o2Order);
}
});
boolean positional = true;
for (Field f : fields) {
Parameter param = f.getDeclaredAnnotation(Parameter.class);
positional &= param.positional();
if (!positional && param.positional()) throw new IllegalArgumentException("All positional values have to be first in list");
}
return this;
}
public ArgParser setProgramName(String name) {
this.programName = name;
return this;
}
public String help() {
return "";
}
public <T> T parse(String[] args) {
try {
return parseItem(args);
} catch (Exception ex) {
System.err.println("An exception occured: " + ex.getMessage());
System.err.println(help());
}
return null;
}
@SuppressWarnings("unchecked")
private <T> T parseItem(String[] args) throws InstantiationException, IllegalAccessException {
if (argClass == null) throw new IllegalArgumentException("Argument class was not specified");
Object obj = argClass.newInstance();
this.fields = Stream.of(obj.getClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(Parameter.class))
.map(f -> {
f.setAccessible(true);
return f;
})
.collect(Collectors.toList());
List<String> argList = new ArrayList<>(Arrays.asList(args));
List<Field> positional = new ArrayList<>();
for(Iterator<Field> iter = fields.iterator(); iter.hasNext();) {
Field f = iter.next();
Parameter param = f.getDeclaredAnnotation(Parameter.class);
if (param.positional()) {
positional.add(f);
iter.remove();
}
}
for(Iterator<String> iter = argList.iterator(); iter.hasNext() && !positional.isEmpty();) {
String value = iter.next();
Field f = positional.remove(0);
Parameter param = f.getDeclaredAnnotation(Parameter.class);
IParameterConverter<?> converter = param.converter().newInstance();
f.set(obj, converter.convert(value));
iter.remove();
}
Map<Field, List<String>> paramMap = new LinkedHashMap<>();
List<String> params = null;
for(Iterator<String> iter = argList.iterator(); iter.hasNext();) {
String value = iter.next();
Field f = findField(value);
if (f != null) {
if (!paramMap.containsKey(f)) {
paramMap.put(f, new ArrayList<>());
}
params = paramMap.get(f);
} else {
params.add(value);
}
iter.remove();
}
for(Entry<Field, List<String>> entry : paramMap.entrySet()) {
Field f = entry.getKey();
List<String> values = entry.getValue();
Parameter param = f.getDeclaredAnnotation(Parameter.class);
if (param.variableArity() && !f.getType().isAssignableFrom(List.class))
throw new IllegalArgumentException(String.format("Field %s is not of type list for the params", f.getName()));
IParameterConverter<?> converter = param.converter().newInstance();
List<Object> objs = values.stream().map(s -> converter.convert(s)).collect(Collectors.toList());
if (param.variableArity()) {
f.set(obj, getGenericList(f.getType(), objs));
} else {
f.set(obj, objs.get(0));
}
}
return (T)obj;
}
private Field findField(String value) {
for (Field f : fields) {
Parameter param = f.getDeclaredAnnotation(Parameter.class);
for (String s : Arrays.asList(param.names())) {
if (s.equals(value)) return f;
}
}
return null;
}
@SuppressWarnings("unchecked")
private <Type> List<Type> getGenericList(Class<Type> type, List<Object> items) {
List<Type> l = new ArrayList<>();
for (Object o : items) {
l.add((Type)o);
}
return l;
}
}

View File

@ -0,0 +1,24 @@
package net.locusworks.argparser.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.locusworks.argparser.converters.NoConverter;
import net.locusworks.argparser.interfaces.IParameterConverter;
@Target(value= {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Parameter {
String[] names();
String description() default "";
boolean positional() default false;
int order() default Integer.MAX_VALUE;
int arityCount() default 0;
boolean variableArity() default false;
boolean required() default false;
Class<? extends IParameterConverter<?>> converter() default NoConverter.class;
SubParameter[] subParameters() default {};
}

View File

@ -0,0 +1,13 @@
package net.locusworks.argparser.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SubParameter {
String parameter();
Class<?> subClass();
}

View File

@ -0,0 +1,14 @@
package net.locusworks.argparser.converters;
import java.io.File;
import net.locusworks.argparser.interfaces.IParameterConverter;
public class FileConverter implements IParameterConverter<File> {
public File convert(String value) {
File file = new File(value);
return file;
}
}

View File

@ -0,0 +1,12 @@
package net.locusworks.argparser.converters;
import net.locusworks.argparser.interfaces.IParameterConverter;
public class IntegerConverter implements IParameterConverter<Integer> {
@Override
public Integer convert(String value) {
return Integer.parseInt(value);
}
}

View File

@ -0,0 +1,11 @@
package net.locusworks.argparser.converters;
import net.locusworks.argparser.interfaces.IParameterConverter;
public class NoConverter implements IParameterConverter<String> {
public String convert(String value) {
return value;
}
}

View File

@ -0,0 +1,7 @@
package net.locusworks.argparser.interfaces;
public interface IParameterConverter<T> {
T convert(String value);
}

View File

@ -0,0 +1,43 @@
package test.argparser;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import net.locusworks.argparser.ArgParser;
import net.locusworks.argparser.annotations.Parameter;
import net.locusworks.argparser.converters.IntegerConverter;
public class ArgParserTest {
public static class Args {
@Parameter(names="position2", positional=true, order=2)
public String position2;
@Parameter(names="position1", positional=true, order=1)
public String position1;
@Parameter(names="-list", variableArity=true)
public List<String> values;
@Parameter(names="-count", converter=IntegerConverter.class)
public int count;
}
@Test
public void testArgParser() {
String[] args = new String[] {"Hello", "World", "-list", "hell", "world", "-count", "1"};
Args arg = ArgParser.newBuilder().withArgClass(Args.class).parse(args);
assertTrue(arg.position2.equals(args[0]));
assertTrue(arg.position1.equals(args[1]));
assertTrue(arg.values.size() == 2);
assertTrue(arg.count == 1);
}
}