src/main/java/com/sampullara/cli/Args.java000644 001750 001750 00000057517 13113601762 020032 0ustar00m36m36000000 000000 /* * Copyright (c) 2005, Sam Pullara. All Rights Reserved. * You may modify and redistribute as long as this attribution remains. */ package com.sampullara.cli; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.PrintStream; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.Callable; public class Args { // POSIX compliant exit code some gray beard asked me to add public static final int EX_USAGE = 64; /** * A convenience method for parsing and automatically producing error messages. * * @param target Either an instance or a class * @param args The arguments you want to parse and populate * @return The list of arguments that were not consumed */ public static List parseOrExit(Object target, String[] args) { try { return parse(target, args); } catch (IllegalArgumentException e) { System.err.println(e.getMessage()); Args.usage(target); System.exit(EX_USAGE); throw e; } } public static List parse(Object target, String[] args) { return parse(target, args, true); } /** * Parse a set of arguments and populate the target with the appropriate values. * * @param target * Either an instance or a class * @param args * The arguments you want to parse and populate * @param failOnExtraFlags * Throw an IllegalArgumentException if extra flags are present * @return The list of arguments that were not consumed */ public static List parse(Object target, String[] args, boolean failOnExtraFlags) { List arguments = new ArrayList(); arguments.addAll(Arrays.asList(args)); Class clazz; if (target instanceof Class) { clazz = (Class) target; } else { clazz = target.getClass(); try { BeanInfo info = Introspector.getBeanInfo(clazz); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { processProperty(target, pd, arguments); } } catch (IntrospectionException e) { // If its not a JavaBean we ignore it } } // Check fields of 'target' class and its superclasses for (Class currentClazz = clazz; currentClazz != null; currentClazz = currentClazz.getSuperclass()) { for (Field field : currentClazz.getDeclaredFields()) { processField(target, field, arguments); } } if (failOnExtraFlags) { for (String argument : arguments) { if (argument.startsWith("-")) { throw new IllegalArgumentException("Invalid argument: " + argument); } } } return arguments; } private static void processField(Object target, Field field, List arguments) { Argument argument = field.getAnnotation(Argument.class); if (argument != null) { boolean set = false; for (Iterator i = arguments.iterator(); i.hasNext(); ) { String arg = i.next(); String prefix = argument.prefix(); String delimiter = argument.delimiter(); if (arg.startsWith(prefix)) { Object value; String name = getName(argument, field); String alias = getAlias(argument); arg = arg.substring(prefix.length()); Class type = field.getType(); if (arg.equals(name) || (alias != null && arg.equals(alias))) { i.remove(); value = consumeArgumentValue(type, argument, i); if (!set) { setField(type, field, target, value, delimiter); } else { addArgument(type, field, target, value, delimiter); } set = true; } if (set && !type.isArray()) break; } } if (!set && argument.required()) { String name = getName(argument, field); throw new IllegalArgumentException("You must set argument " + name); } } } private static void addArgument(Class type, Field field, Object target, Object value, String delimiter) { try { Object[] os = (Object[]) field.get(target); Object[] vs = (Object[]) getValue(type, value, delimiter); Object[] s = (Object[]) Array.newInstance(type.getComponentType(), os.length + vs.length); System.arraycopy(os, 0, s, 0, os.length); System.arraycopy(vs, 0, s, os.length, vs.length); field.set(target, s); } catch (IllegalAccessException iae) { throw new IllegalArgumentException("Could not set field " + field, iae); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Could not find constructor in class " + type.getName() + " that takes a string", e); } } private static void addPropertyArgument(Class type, PropertyDescriptor property, Object target, Object value, String delimiter) { try { Object[] os = (Object[]) property.getReadMethod().invoke(target); Object[] vs = (Object[]) getValue(type, value, delimiter); Object[] s = (Object[]) Array.newInstance(type.getComponentType(), os.length + vs.length); System.arraycopy(os, 0, s, 0, os.length); System.arraycopy(vs, 0, s, os.length, vs.length); property.getWriteMethod().invoke(target, (Object) s); } catch (IllegalAccessException iae) { throw new IllegalArgumentException("Could not set property " + property, iae); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Could not find constructor in class " + type.getName() + " that takes a string", e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Failed to validate argument " + value + " for " + property); } } private static void processProperty(Object target, PropertyDescriptor property, List arguments) { Method writeMethod = property.getWriteMethod(); if (writeMethod != null) { Argument argument = writeMethod.getAnnotation(Argument.class); if (argument != null) { boolean set = false; for (Iterator i = arguments.iterator(); i.hasNext(); ) { String arg = i.next(); String prefix = argument.prefix(); String delimiter = argument.delimiter(); if (arg.startsWith(prefix)) { Object value; String name = getName(argument, property); String alias = getAlias(argument); arg = arg.substring(prefix.length()); Class type = property.getPropertyType(); if (arg.equals(name) || (alias != null && arg.equals(alias))) { i.remove(); value = consumeArgumentValue(type, argument, i); if (!set) { setProperty(type, property, target, value, delimiter); } else { addPropertyArgument(type, property, target, value, delimiter); } set = true; } if (set && !type.isArray()) break; } } if (!set && argument.required()) { String name = getName(argument, property); throw new IllegalArgumentException("You must set argument " + name); } } } } /** * Generate usage information based on the target annotations. * * @param target An instance or class. */ public static void usage(Object target) { usage(System.err, target); } /** * Generate usage information based on the target annotations. * * @param errStream A {@link java.io.PrintStream} to print the usage information to. * @param target An instance or class. */ public static void usage(PrintStream errStream, Object target) { usage(errStream, target, (target instanceof Class)?(Class)target:target.getClass()); } /** * Generate usage information based on the target annotations. * * @param errStream A {@link java.io.PrintStream} to print the usage information to. * @param target An instance or class. * @param mainClass The class containing the main method */ public static void usage(PrintStream errStream, Object target, Class mainClass) { Class clazz; if (target instanceof Class) { clazz = (Class) target; } else { clazz = target.getClass(); } errStream.println("Usage: " + mainClass.getName()); for (Class currentClazz = clazz; currentClazz != null; currentClazz = currentClazz.getSuperclass()) { for (Field field : currentClazz.getDeclaredFields()) { fieldUsage(errStream, target, field); } } try { BeanInfo info = Introspector.getBeanInfo(clazz); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { propertyUsage(errStream, target, pd); } } catch (IntrospectionException e) { // If its not a JavaBean we ignore it } } private static void fieldUsage(PrintStream errStream, Object target, Field field) { Argument argument = field.getAnnotation(Argument.class); if (argument != null) { String name = getName(argument, field); String alias = getAlias(argument); String prefix = argument.prefix(); String delimiter = argument.delimiter(); String description = argument.description(); Class>> valuesProvider = argument.valuesProvider(); makeAccessible(field); try { Object defaultValue = field.get(target); Class type = field.getType(); propertyUsage(errStream, prefix, name, alias, type, delimiter, description, defaultValue, getPossibleValues(type, valuesProvider)); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Could not use this field " + field + " as an argument field", e); } } } private static void propertyUsage(PrintStream errStream, Object target, PropertyDescriptor field) { Method writeMethod = field.getWriteMethod(); if (writeMethod != null) { Argument argument = writeMethod.getAnnotation(Argument.class); if (argument != null) { String name = getName(argument, field); String alias = getAlias(argument); String prefix = argument.prefix(); String delimiter = argument.delimiter(); String description = argument.description(); Class>> valuesProvider = argument.valuesProvider(); try { Method readMethod = field.getReadMethod(); Object defaultValue; if (readMethod == null) { defaultValue = null; } else { defaultValue = readMethod.invoke(target, (Object[]) null); } Class type = field.getPropertyType(); propertyUsage(errStream, prefix, name, alias, type, delimiter, description, defaultValue, getPossibleValues(type, valuesProvider)); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Could not use this field " + field + " as an argument field", e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Could not get default value for " + field, e); } } } } private static void propertyUsage(PrintStream errStream, String prefix, String name, String alias, Class type, String delimiter, String description, Object defaultValue, List possibleValues) { StringBuilder sb = new StringBuilder(" "); sb.append(prefix); sb.append(name); if (alias != null) { sb.append(" ("); sb.append(prefix); sb.append(alias); sb.append(")"); } if (type == Boolean.TYPE || type == Boolean.class) { sb.append(" [flag] "); sb.append(description); } else { sb.append(" ["); if (type.isArray()) { String typeName = getTypeName(type.getComponentType()); sb.append(typeName); sb.append("["); sb.append(delimiter); sb.append("]"); } else { String typeName = getTypeName(type); sb.append(typeName); } sb.append("] "); sb.append(description); if (defaultValue != null) { sb.append(" ("); if (type.isArray()) { List list = new ArrayList(); int len = Array.getLength(defaultValue); for (int i = 0; i < len; i++) { list.add(Array.get(defaultValue, i)); } sb.append(list); } else { sb.append(defaultValue); } sb.append(")"); } } if (possibleValues != null && !possibleValues.isEmpty()) { sb.append(System.getProperty("line.separator")); sb.append(" Possible values are: "); boolean first = true; for (String possibleValue : possibleValues) { if (first) { first = false; } else { sb.append(", "); } sb.append(possibleValue); } } errStream.println(sb); } private static String getTypeName(Class type) { String typeName = type.getName(); int beginIndex = typeName.lastIndexOf("."); typeName = typeName.substring(beginIndex + 1); return typeName; } static String getName(Argument argument, PropertyDescriptor property) { String name = argument.value(); if (name.equals("")) { name = property.getName(); } return name; } private static Object consumeArgumentValue(Class type, Argument argument, Iterator i) { Object value; if (type == Boolean.TYPE || type == Boolean.class) { value = true; } else { if (i.hasNext()) { value = i.next(); i.remove(); } else { throw new IllegalArgumentException("Must have a value for non-boolean argument " + argument.value()); } } return value; } static void setProperty(Class type, PropertyDescriptor property, Object target, Object value, String delimiter) { try { value = getValue(type, value, delimiter); property.getWriteMethod().invoke(target, value); } catch (IllegalAccessException iae) { throw new IllegalArgumentException("Could not set property " + property, iae); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Could not find constructor in class " + type.getName() + " that takes a string", e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Failed to validate argument " + value + " for " + property); } } static String getAlias(Argument argument) { String alias = argument.alias(); if (alias.equals("")) { alias = null; } return alias; } static String getName(Argument argument, Field field) { String name = argument.value(); if (name.equals("")) { name = field.getName(); } return name; } static void setField(Class type, Field field, Object target, Object value, String delimiter) { makeAccessible(field); try { value = getValue(type, value, delimiter); field.set(target, value); } catch (IllegalAccessException iae) { throw new IllegalArgumentException("Could not set field " + field, iae); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Could not find constructor in class " + type.getName() + " that takes a string", e); } } private static Object getValue(Class type, Object value, String delimiter) throws NoSuchMethodException { if (type != String.class && type != Boolean.class && type != Boolean.TYPE) { String string = (String) value; if (type.isArray()) { String[] strings = string.split(delimiter); type = type.getComponentType(); if (type == String.class) { value = strings; } else { Object[] array = (Object[]) Array.newInstance(type, strings.length); for (int i = 0; i < array.length; i++) { array[i] = createValue(type, strings[i]); } value = array; } } else { value = createValue(type, string); } } return value; } private static Object createValue(Class type, String valueAsString) throws NoSuchMethodException { for (ValueCreator valueCreator : valueCreators) { Object createdValue = valueCreator.createValue(type, valueAsString); if (createdValue != null) { return createdValue; } } throw new IllegalArgumentException(String.format("cannot instanciate any %s object using %s value", type.toString(), valueAsString)); } private static void makeAccessible(AccessibleObject ao) { if (ao instanceof Member) { Member member = (Member) ao; if (!Modifier.isPublic(member.getModifiers())) { ao.setAccessible(true); } } } private static List getPossibleValues(Class type, Class>> provider) { if (type.isEnum()) { return getEnumPossibleValues(type); } return getPossibleValuesViaProvider(provider); } private static List getPossibleValuesViaProvider(Class>> provider) { try { return provider.newInstance().call(); } catch (InstantiationException e) { return Collections.emptyList(); } catch (IllegalAccessException e) { return Collections.emptyList(); } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") private static List getEnumPossibleValues(Class clazz) { return getEnumPossibleValuesImpl((Class)clazz); } private static > List getEnumPossibleValuesImpl(Class clazz) { List result = new ArrayList(); for (T value : clazz.getEnumConstants()) { result.add(value.name()); } return result; } public interface ValueCreator { /** * Creates a value object of the given type using the given string value representation; * * @param type the type to create an instance of * @param value the string represented value of the object to create * @return null if the object could not be created, the value otherwise */ Object createValue(Class type, String value); } /** * Creates a {@link ValueCreator} object able to create object assignable from given type, * using a static one arg method which name is the the given one taking a String object as parameter * * @param compatibleType the base assignable for which this object will try to invoke the given method * @param methodName the name of the one arg method taking a String as parameter that will be used to built a new value * @return null if the object could not be created, the value otherwise */ public static ValueCreator byStaticMethodInvocation(final Class compatibleType, final String methodName) { return new ValueCreator() { public Object createValue(Class type, String value) { if (compatibleType.isAssignableFrom(type)) { try { Method m = type.getMethod(methodName, String.class); return m.invoke(null, value); } catch (NoSuchMethodException e) { // ignore } catch (Exception e) { throw new IllegalArgumentException(String.format("could not invoke %s#%s to create an obejct from %s", type.toString(), methodName, value)); } } return null; } }; } /** * {@link ValueCreator} building object using a one arg constructor taking a {@link String} object as parameter */ public static final ValueCreator FROM_STRING_CONSTRUCTOR = new ValueCreator() { public Object createValue(Class type, String value) { Object v = null; try { Constructor init = type.getDeclaredConstructor(String.class); v = init.newInstance(value); } catch (NoSuchMethodException e) { // ignore } catch (Exception e) { throw new IllegalArgumentException("Failed to convert " + value + " to type " + type.getName(), e); } return v; } }; public static final ValueCreator ENUM_CREATOR = new ValueCreator() { @SuppressWarnings({"unchecked", "rawtypes"}) public Object createValue(Class type, String value) { if (Enum.class.isAssignableFrom(type)) { return Enum.valueOf(type, value); } return null; } }; private static final List DEFAULT_VALUE_CREATORS = Arrays.asList(Args.FROM_STRING_CONSTRUCTOR, Args.ENUM_CREATOR); private static List valueCreators = new ArrayList(DEFAULT_VALUE_CREATORS); /** * Allows external extension of the valiue creators. * * @param vc another value creator to take into account for trying to create values */ public static void registerValueCreator(ValueCreator vc) { valueCreators.add(vc); } /** * Cleanup of registered ValueCreators (mainly for tests) */ public static void resetValueCreators() { valueCreators.clear(); valueCreators.addAll(DEFAULT_VALUE_CREATORS); } } src/test/java/000700 001750 001750 00000000000 13113601762 012607 5ustar00m36m36000000 000000 src/test/java/com/sampullara/cli/UsingStaticMethodsTest.java000644 001750 001750 00000002446 13113601762 023601 0ustar00m36m36000000 000000 package com.sampullara.cli; import java.nio.charset.*; import junit.framework.*; public class UsingStaticMethodsTest extends TestCase { private static final String UTF_8_STR = "UTF-8"; @Override protected void setUp() throws Exception { Args.resetValueCreators(); } public void testCharsetCannotBeParsedByDefault() { CommandCLI cli = new CommandCLI(); String[] args = new String[] {"-charset", UTF_8_STR}; try { Args.parse(cli, args); fail("without specified ValueCreator Charset object could not be created"); } catch (IllegalArgumentException e) { // expected } } public void testCharsetCanBeParsedByRegisteringAValueCreator() { CommandCLI cli = new CommandCLI(); Args.registerValueCreator(Args.byStaticMethodInvocation(Charset.class, "forName")); String[] args = new String[] {"-charset", UTF_8_STR}; Args.parse(cli, args); assertNotNull("charset value not built", cli.getCharset()); assertTrue("built object is not a " + Charset.class + " class object", Charset.class.isAssignableFrom(cli.getCharset().getClass())); assertEquals("retrieved charset is not " + UTF_8_STR, UTF_8_STR, cli.getCharset().name()); } public static class CommandCLI { @Argument() private Charset charset; public Charset getCharset() { return charset; } } }src/test/java/com/sampullara/cli/ArgsTest.java000644 001750 001750 00000023732 13113601762 020715 0ustar00m36m36000000 000000 package com.sampullara.cli; import junit.framework.TestCase; import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.OutputStream; import java.io.PrintStream; import java.util.List; /** * User: sam * Date: Dec 27, 2005 * Time: 3:31:44 PM */ public class ArgsTest extends TestCase { public void testArgsParse() { TestCommand tc = new TestCommand(); Args.usage(tc); String[] args = {"-input", "inputfile", "-o", "outputfile", "extra1", "-someflag", "extra2", "-m", "10", "-values", "1:2:3", "-strings", "sam;dave;jolly"}; List extra = Args.parse(tc, args); assertEquals("inputfile", tc.inputFilename); assertEquals(new File("outputfile"), tc.outputFile); assertEquals(true, tc.someflag); assertEquals(10, tc.minimum.intValue()); assertEquals(3, tc.values.length); assertEquals(2, tc.values[1].intValue()); assertEquals("dave", tc.strings[1]); assertEquals(2, extra.size()); } public void testMultipleArgs() { TestCommand tc = new TestCommand(); Args.usage(tc); String[] args = {"-input", "inputfile", "-o", "outputfile", "extra1", "-someflag", "extra2", "-m", "10", "-values", "1", "-values", "2", "-values", "3", "-strings", "sam", "-strings", "dave", "-strings", "jolly"}; List extra = Args.parse(tc, args); assertEquals("inputfile", tc.inputFilename); assertEquals(new File("outputfile"), tc.outputFile); assertEquals(true, tc.someflag); assertEquals(10, tc.minimum.intValue()); assertEquals(3, tc.values.length); assertEquals(2, tc.values[1].intValue()); assertEquals("dave", tc.strings[1]); assertEquals(2, extra.size()); } public void testStaticFields() { Args.usage(TestCommand4.class); String[] args = {"-input", "inputfile", "-output", "outputfile"}; List extra = Args.parse(TestCommand4.class, args); assertEquals("inputfile", TestCommand4.input); assertEquals("outputfile", TestCommand4.output); } public void testBadArgsParse() { String[] args = {"-fred", "inputfile", "-output", "outputfile"}; try { List extra = Args.parse(TestCommand4.class, args); fail("Should have thrown an exception"); } catch (IllegalArgumentException iae) { assertEquals("Invalid argument: -fred", iae.getMessage()); } args = new String[] {"-input", "inputfile"}; try { List extra = Args.parse(TestCommand4.class, args); fail("Should have thrown an exception"); } catch (IllegalArgumentException iae) { assertEquals("You must set argument output", iae.getMessage()); } } public void testMethodArgsParse() { TestCommand2 tc = new TestCommand2(); Args.usage(tc); String[] args = {"-input", "inputfile", "-o", "outputfile", "extra1", "-someflag", "extra2", "-m", "10", "-values", "1:2:3"}; List extra = Args.parse(tc, args); assertEquals("inputfile", tc.inputFilename); assertEquals(new File("outputfile"), tc.outputFile); assertEquals(true, tc.someflag); assertEquals(10, tc.minimum.intValue()); assertEquals(3, tc.values.length); assertEquals(2, tc.values[1].intValue()); assertEquals(2, extra.size()); } public void testMixedArgsParse() { TestCommand3 tc = new TestCommand3(); Args.usage(tc); String[] args = {"-input", "inputfile", "-o", "outputfile", "extra1", "-someflag", "extra2", "-m", "10", "-values", "1:2:3"}; List extra = Args.parse(tc, args); assertEquals("inputfile", tc.inputFilename); assertEquals(new File("outputfile"), tc.outputFile); assertEquals(true, tc.someflag); assertEquals(10, tc.minimum.intValue()); assertEquals(3, tc.values.length); assertEquals(2, tc.values[1].intValue()); assertEquals(2, extra.size()); } public void testStaticCommand() { Args.usage(StaticTestCommand.class); String[] args = { "-num", "1", "extra" }; List extra = Args.parse(StaticTestCommand.class, args); assertEquals(1, (int) StaticTestCommand.num); assertEquals("extra", extra.get(0)); } public void testDerivedCommand() { String[] args = { "-help", "-verbose" }; TestCommand6 tc = new TestCommand6(); Args.parse(tc, args); assertTrue(tc.help); assertTrue(tc.verbose); } public void testUsageShowsParameterClassByDefault() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); Args.usage(ps, Example.class); ps.flush(); String usage = new String(baos.toByteArray()); assertTrue(usage.startsWith("Usage: " + Example.class.getName())); } public void testUsageShowsGivenMainClass() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); Args.usage(ps, Example.class, ArgsTest.class); ps.flush(); String usage = new String(baos.toByteArray()); assertTrue(usage.startsWith("Usage: " + ArgsTest.class.getName())); } public static class TestCommand { @Argument(value = "input", description = "This is the input file", required = true) private String inputFilename; @Argument(value = "output", alias = "o", description = "This is the output file", required = true) private File outputFile; @Argument(description = "This flag can optionally be set") private boolean someflag; @Argument(description = "Minimum", alias = "m") private Integer minimum; @Argument(description = "List of values", delimiter = ":") public void setValues(Integer[] values) { this.values = values; } public Integer[] getValues() { return values; } private Integer[] values; @Argument(description = "List of strings", delimiter = ";") private String[] strings; @Argument(description = "not required") private boolean notRequired; } public static class StaticTestCommand { @Argument private static Integer num; } public static class TestCommand2 { private String inputFilename; private File outputFile; private boolean someflag; private Integer minimum = 0; private Integer[] values = new Integer[] { 10 }; public String getInputFilename() { return inputFilename; } @Argument(value = "input", description = "This is the input file", required = true) public void setInputFilename(String inputFilename) { this.inputFilename = inputFilename; } public File getOutputFile() { return outputFile; } @Argument(value = "output", alias = "o", description = "This is the output file", required = true) public void setOutputFile(File outputFile) { this.outputFile = outputFile; } public boolean isSomeflag() { return someflag; } @Argument(description = "This flag can optionally be set") public void setSomeflag(boolean someflag) { this.someflag = someflag; } public Integer getMinimum() { return minimum; } @Argument(description = "Minimum", alias = "m") public void setMinimum(Integer minimum) { this.minimum = minimum; } public Integer[] getValues() { return values; } @Argument(description = "List of values", delimiter = ":") public void setValues(Integer[] values) { this.values = values; } } public static class TestCommand3 { private String inputFilename; @Argument(value = "output", alias = "o", description = "This is the output file", required = true) private File outputFile; private boolean someflag; private boolean someotherflag; private Integer minimum; private Integer[] values; public String getInputFilename() { return inputFilename; } @Argument(value = "input", description = "This is the input file", required = true) public void setInputFilename(String inputFilename) { this.inputFilename = inputFilename; } public File getOutputFile() { return outputFile; } public void setOutputFile(File outputFile) { this.outputFile = outputFile; } public boolean isSomeflag() { return someflag; } @Argument(description = "This flag can optionally be set") public void setSomeflag(boolean someflag) { this.someflag = someflag; } @Argument(description = "This flag can optionally be set") public void setSomeotherflag(boolean someotherflag) { this.someotherflag = someotherflag; } public Integer getMinimum() { return minimum; } @Argument(description = "Minimum", alias = "m") public void setMinimum(Integer minimum) { this.minimum = minimum; } public Integer[] getValues() { return values; } @Argument(description = "List of values", delimiter = ":") public void setValues(Integer[] values) { this.values = values; } } public static class TestCommand4 { @Argument() private static String input; @Argument(required = true) private static String output; } public static abstract class TestCommand5 { @Argument public boolean help; } public static class TestCommand6 extends TestCommand5 { @Argument public boolean verbose; } } src/test/java/com/sampullara/cli/Example.java000644 001750 001750 00000002160 13113601762 020544 0ustar00m36m36000000 000000 package com.sampullara.cli; import java.util.List; public class Example { @Argument private static Boolean hdfs = false; @Argument(alias = "r", description = "Regular expression to parse lines", required = true) private static String regex; @Argument(alias = "k", description = "Key column", required = true) private static String key; @Argument(alias = "p", description = "Key prefix") private static String prefix; @Argument(alias = "c", description = "Column groups", delimiter = ",") private static String[] columns; @Argument(alias = "n", description = "Column names", delimiter = ",") private static String[] names; @Argument(alias = "h", description = "Redis host") private static String host = "localhost"; @Argument(alias = "p", description = "Redis port") private static Integer port = 6379; public static void main(String[] args) { final List parse; try { parse = Args.parse(Example.class, args); } catch (IllegalArgumentException e) { Args.usage(Example.class); System.exit(1); return; } // Use all those parameters... } } src/test/java/com/sampullara/cli/PropertiesArgsTest.java000644 001750 001750 00000003366 13113601762 022773 0ustar00m36m36000000 000000 package com.sampullara.cli; import junit.framework.TestCase; import java.io.File; import java.util.Properties; /** * User: sam * Date: Dec 27, 2005 * Time: 3:31:44 PM */ public class PropertiesArgsTest extends TestCase { public void testArgsParseWithProperties() { TestCommand tc = new TestCommand(); Args.usage(tc); Properties p = new Properties(); p.put("input", "inputfile"); p.put("o", "outputfile"); p.put("someflag", "true"); p.put("m", "10"); p.put("values", "1:2:3"); p.put("strings", "sam;dave;jolly"); PropertiesArgs.parse(tc, p); assertEquals("inputfile", tc.inputFilename); assertEquals(new File("outputfile"), tc.outputFile); assertEquals(true, tc.someflag); assertEquals(10, tc.minimum.intValue()); assertEquals(3, tc.values.length); assertEquals(2, tc.values[1].intValue()); assertEquals("dave", tc.strings[1]); } public static class TestCommand { @Argument(value = "input", description = "This is the input file", required = true) private String inputFilename; @Argument(value = "output", alias = "o", description = "This is the output file", required = true) private File outputFile; @Argument(description = "This flag can optionally be set") private boolean someflag; @Argument(description = "Minimum", alias = "m") private Integer minimum; @Argument(description = "List of values", delimiter = ":") private Integer[] values; @Argument(description = "List of strings", delimiter = ";") private String[] strings; @Argument(description = "not required") private boolean notRequired; } }.travis.yml000644 001750 001750 00000000017 13113601762 012241 0ustar00m36m36000000 000000 language: java .gitignore000644 001750 001750 00000000062 13113601762 012120 0ustar00m36m36000000 000000 target/ .settings/ .classpath .project .idea *.imlsrc/test/java/com/sampullara/cli/000700 001750 001750 00000000000 13113601762 016275 5ustar00m36m36000000 000000 pom.xml000644 001750 001750 00000006243 13113601762 011454 0ustar00m36m36000000 000000 4.0.0 com.github.spullara.cli-parser cli-parser jar CLI Parser 1.1.3-SNAPSHOT http://code.google.com/p/cli-parser/ An annotation-based command line interface parser. spullara Sam Pullara spullara@yahoo.com http://javarants.com architect developer -8 The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo scm:git:git@github.com:spullara/cli-parser.git scm:git:git@github.com:spullara/cli-parser.git scm:git:git@github.com:spullara/cli-parser.git HEAD sonatype-nexus-staging https://oss.sonatype.org/service/local/staging/deploy/maven2 sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots junit junit 4.12 test org.apache.maven.plugins maven-compiler-plugin 2.5.1 1.5 1.5 org.apache.maven.plugins maven-release-plugin 2.5 release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin 1.4 sign-artifacts verify sign src/main/java/com/sampullara/cli/000700 001750 001750 00000000000 13113601762 016242 5ustar00m36m36000000 000000 src/test/java/com/sampullara/cli/EnumTest.java000644 001750 001750 00000003005 13113601762 020714 0ustar00m36m36000000 000000 package com.sampullara.cli; import junit.framework.TestCase; import java.io.*; public class EnumTest extends TestCase { public void testCanUseAnEnumValue() { CommandCLI cli = new CommandCLI(); String[] args = new String[] {"-command", "START"}; Args.parse(cli, args); assertNotNull("Commands enum value not built", cli.getCommand()); assertEquals("retrieved command value is not Commands.START", Commands.START, cli.getCommand()); } public void testUsageForEnum() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true, "UTF-8"); Args.usage(ps, new CommandCLI()); ps.close(); BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(baos.toByteArray()), "UTF-8")); String line; StringBuilder foundUsageInfo = new StringBuilder(1024); while ((line = br.readLine()) != null) { line = line.trim(); int index = line.indexOf("Possible values are:"); if (index != -1) { foundUsageInfo.append(line.substring(index + "Possible values are:".length()).trim()); foundUsageInfo.append("\n"); } } assertEquals("START, STOP, PAUSE", foundUsageInfo.toString().trim()); } public static class CommandCLI { @Argument() private Commands command; public Commands getCommand() { return command; } } public static enum Commands { START, STOP, PAUSE; } }src/main/java/com/sampullara/cli/PropertiesArgs.java000644 001750 001750 00000007047 13113601762 022100 0ustar00m36m36000000 000000 package com.sampullara.cli; import java.util.Properties; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.beans.IntrospectionException; /** * Created by IntelliJ IDEA. *

* User: samp * Date: Nov 11, 2007 * Time: 3:42:27 PM */ public class PropertiesArgs { /** * Parse properties instead of String arguments. Any additional arguments need to be passed some other way. * This is often used in a second pass when the property filename is passed on the command line. Because of * required properties you must be careful to set them all in the property file. * * @param target Either an instance or a class * @param arguments The properties that contain the arguments */ public static void parse(Object target, Properties arguments) { Class clazz; if (target instanceof Class) { clazz = (Class) target; } else { clazz = target.getClass(); } for (Field field : clazz.getDeclaredFields()) { processField(target, field, arguments); } try { BeanInfo info = Introspector.getBeanInfo(clazz); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { processProperty(target, pd, arguments); } } catch (IntrospectionException e) { // If its not a JavaBean we ignore it } } private static void processField(Object target, Field field, Properties arguments) { Argument argument = field.getAnnotation(Argument.class); if (argument != null) { String name = Args.getName(argument, field); String alias = Args.getAlias(argument); Class type = field.getType(); Object value = arguments.get(name); if (value == null && alias != null) { value = arguments.get(alias); } if (value != null) { if (type == Boolean.TYPE || type == Boolean.class) { value = true; } Args.setField(type, field, target, value, argument.delimiter()); } else { if (argument.required()) { throw new IllegalArgumentException("You must set argument " + name); } } } } private static void processProperty(Object target, PropertyDescriptor property, Properties arguments) { Method writeMethod = property.getWriteMethod(); if (writeMethod != null) { Argument argument = writeMethod.getAnnotation(Argument.class); if (argument != null) { String name = Args.getName(argument, property); String alias = Args.getAlias(argument); Object value = arguments.get(name); if (value == null && alias != null) { value = arguments.get(alias); } if (value != null) { Class type = property.getPropertyType(); if (type == Boolean.TYPE || type == Boolean.class) { value = true; } Args.setProperty(type, property, target, value, argument.delimiter()); } else { if (argument.required()) { throw new IllegalArgumentException("You must set argument " + name); } } } } } } src/main/java/com/sampullara/000700 001750 001750 00000000000 13113601762 015473 5ustar00m36m36000000 000000 src/test/java/com/sampullara/cli/ValuesProviderTest.java000644 001750 001750 00000002625 13113601762 022771 0ustar00m36m36000000 000000 package com.sampullara.cli; import org.junit.Test; import java.io.*; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; public class ValuesProviderTest { @Test public void testMain() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true, "UTF-8"); Args.usage(ps, new TestCLI()); ps.close(); BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(baos.toByteArray()), "UTF-8")); String line; StringBuilder foundUsageInfo = new StringBuilder(1024); while ((line = br.readLine()) != null) { line = line.trim(); int index = line.indexOf("Possible values are:"); if (index != -1) { foundUsageInfo.append(line.substring(index + "Possible values are:".length()).trim()); foundUsageInfo.append("\n"); } } assertEquals("v1, v2", foundUsageInfo.toString().trim()); } public static class TestCLI { @Argument(valuesProvider = ArgProvider.class) public String arg; } public static class ArgProvider implements Callable> { public List call() throws Exception { return Arrays.asList("v1", "v2"); } } } src/test/000700 001750 001750 00000000000 13113601762 011666 5ustar00m36m36000000 000000 src/test/java/com/000700 001750 001750 00000000000 13113601762 013365 5ustar00m36m36000000 000000 src/main/java/com/000700 001750 001750 00000000000 13113601762 013332 5ustar00m36m36000000 000000 src/main/java/com/sampullara/cli/Argument.java000644 001750 001750 00000002467 13113601762 020712 0ustar00m36m36000000 000000 /* * Copyright (c) 2005, Sam Pullara. All Rights Reserved. * You may modify and redistribute as long as this attribution remains. */ package com.sampullara.cli; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.concurrent.Callable; @Documented @Retention(RetentionPolicy.RUNTIME) public @interface Argument { /** * This is the actual command line argument itself */ String value() default ""; /** * If this is true, then the argument must be set or the parse will fail */ boolean required() default false; /** * This is the prefix expected for the argument */ String prefix() default "-"; /** * Each argument can have an alias */ String alias() default ""; /** * A description of the argument that will appear in the usage method */ String description() default ""; /** * A delimiter for arguments that are multi-valued. */ String delimiter() default ","; Class>> valuesProvider() default DummyCallable.class; class DummyCallable implements Callable> { public List call() throws Exception { return null; } } } src/test/java/com/sampullara/000700 001750 001750 00000000000 13113601762 015526 5ustar00m36m36000000 000000 src/main/000700 001750 001750 00000000000 13113601762 011633 5ustar00m36m36000000 000000 README.md000644 001750 001750 00000005674 13113601762 011425 0ustar00m36m36000000 000000 CLI Parser ========== **CLI Parser** is a tiny (10k jar), super easy to use library for parsing various kinds of command line arguments or property lists. Using annotations on your fields or JavaBean properties you can specify what configuration is available. Here is an example: public class Loader { @Argument private static Boolean hdfs = false; @Argument(alias = "r", description = "Regular expression to parse lines", required = true) private static String regex; @Argument(alias = "k", description = "Key column", required = true) private static String key; @Argument(alias = "p", description = "Key prefix") private static String prefix; @Argument(alias = "c", description = "Column groups", delimiter = ",") private static String[] columns; @Argument(alias = "n", description = "Column names", delimiter = ",") private static String[] names; @Argument(alias = "h", description = "Redis host") private static String host = "localhost"; @Argument(alias = "p", description = "Redis port") private static Integer port = 6379; public static void main(String[] args) throws IOException { // unparsed will contain all unparsed arguments to the command line List unparsed = Args.parseOrExit(Loader.class, args); // Loader's fields will be populated after this line or the program will exit with usage } } In this case we are configuring static fields, but you can also use the same system with instances. If you pass in a wrong command line argument you will get the usage message: Usage: com.sampullara.cli.Example -hdfs [flag] -regex (-r) [String] Regular expression to parse lines -key (-k) [String] Key column -prefix (-p) [String] Key prefix -columns (-c) [String[,]] Column groups -names (-n) [String[,]] Column names -host (-h) [String] Redis host (localhost) -port (-p) [Integer] Redis port (6379) That message will print out the names and aliases of the arguments, type, description and a default value for the parameter if there is one. You can add it to your code with: com.github.spullara.cli-parser cli-parser 1.1 Here is the license: Copyright 2012 Sam Pullara Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. src/000700 001750 001750 00000000000 13113601762 010707 5ustar00m36m36000000 000000 src/main/java/000700 001750 001750 00000000000 13113601762 012554 5ustar00m36m36000000 000000