From 5843f30f42b11cc6a6b4ca25a57bfe04bfe7d517 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 20 Dec 2011 22:41:55 -0800 Subject: SDK: Extend CommandLineParser to support string arrays. This allows the parser to process an input such as: $ cmd verb --arg1 value1 --arg2 value2 value3 value4 or $ cmd verb --arg1 value1 --arg2 value2 -- -value3 --value4 Change-Id: I7ea6c019c2bb4ee5003bdba1f0774f3c062322c2 --- .../com/android/sdklib/util/CommandLineParser.java | 117 ++++++++++++++++----- .../android/sdklib/util/CommandLineParserTest.java | 60 ++++++++++- 2 files changed, 150 insertions(+), 27 deletions(-) (limited to 'sdkmanager/libs') diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java index ae17813..1fa69b2 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java @@ -18,7 +18,9 @@ package com.android.sdklib.util; import com.android.sdklib.ISdkLog; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map.Entry; /** @@ -326,28 +328,46 @@ public class CommandLineParser { arg.setInCommandLine(true); // Process keyword - String error = null; + Object error = null; if (arg.getMode().needsExtra()) { - if (++i >= n) { + if (i+1 >= n) { errorMsg = String.format("Missing argument for flag %1$s.", a); return; } - String b = args[i]; - Arg dummyArg = null; - if (b.startsWith("--")) { //$NON-NLS-1$ - dummyArg = findLongArg(verb, directObject, b.substring(2)); - } else if (b.startsWith("-")) { //$NON-NLS-1$ - dummyArg = findShortArg(verb, directObject, b.substring(1)); - } - if (dummyArg != null) { - errorMsg = String.format( - "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.", - a, b); - return; - } + while (i+1 < n) { + String b = args[i+1]; + + if (arg.getMode() != Mode.STRING_ARRAY) { + // We never accept something that looks like a valid argument + // unless we see -- first + Arg dummyArg = null; + if (b.startsWith("--")) { //$NON-NLS-1$ + dummyArg = findLongArg(verb, directObject, b.substring(2)); + } else if (b.startsWith("-")) { //$NON-NLS-1$ + dummyArg = findShortArg(verb, directObject, b.substring(1)); + } + if (dummyArg != null) { + errorMsg = String.format( + "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.", + a, b); + return; + } + } - error = arg.getMode().process(arg, b); + error = arg.getMode().process(arg, b); + if (error == Accept.CONTINUE) { + i++; + } else if (error == Accept.ACCEPT_AND_STOP) { + i++; + break; + } else if (error == Accept.REJECT_AND_STOP) { + break; + } else if (error instanceof String) { + // We stop because of an error + break; + } + } } else { error = arg.getMode().process(arg, null); @@ -361,7 +381,7 @@ public class CommandLineParser { } } - if (error != null) { + if (error instanceof String) { errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error); return; } @@ -610,6 +630,11 @@ public class CommandLineParser { //---- + private static enum Accept { + CONTINUE, + ACCEPT_AND_STOP, + REJECT_AND_STOP, + } /** * The mode of an argument specifies the type of variable it represents, @@ -623,10 +648,10 @@ public class CommandLineParser { return false; } @Override - public String process(Arg arg, String extra) { + public Object process(Arg arg, String extra) { // Toggle the current value arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue()); - return null; + return Accept.ACCEPT_AND_STOP; } }, @@ -637,7 +662,7 @@ public class CommandLineParser { return true; } @Override - public String process(Arg arg, String extra) { + public Object process(Arg arg, String extra) { try { arg.setCurrentValue(Integer.parseInt(extra)); return null; @@ -655,13 +680,13 @@ public class CommandLineParser { return true; } @Override - public String process(Arg arg, String extra) { + public Object process(Arg arg, String extra) { StringBuilder desc = new StringBuilder(); String[] values = (String[]) arg.getDefaultValue(); for (String value : values) { if (value.equals(extra)) { arg.setCurrentValue(extra); - return null; + return Accept.ACCEPT_AND_STOP; } if (desc.length() != 0) { @@ -681,9 +706,43 @@ public class CommandLineParser { return true; } @Override - public String process(Arg arg, String extra) { + public Object process(Arg arg, String extra) { arg.setCurrentValue(extra); - return null; + return Accept.ACCEPT_AND_STOP; + } + }, + + /** Argument value is a String Array. Default value is a null. */ + STRING_ARRAY { + @Override + public boolean needsExtra() { + return true; + } + @Override + public Object process(Arg arg, String extra) { + // For simplification, a string array doesn't accept something that + // starts with a dash unless a pure -- was seen before. + if (extra != null) { + Object v = arg.getCurrentValue(); + if (v == null) { + ArrayList a = new ArrayList(); + arg.setCurrentValue(a); + v = a; + } + if (v instanceof List) { + @SuppressWarnings("unchecked") List a = (List) v; + + if (extra.equals("--") || + !extra.startsWith("-") || + (extra.startsWith("-") && a.contains("--"))) { + a.add(extra); + return Accept.CONTINUE; + } else if (a.isEmpty()) { + return "No values provided"; + } + } + } + return Accept.REJECT_AND_STOP; } }; @@ -697,9 +756,15 @@ public class CommandLineParser { * * @param arg The argument being processed. * @param extra The extra parameter. Null if {@link #needsExtra()} returned false. - * @return An error string or null if there's no error. + * @return {@link Accept#CONTINUE} if this argument can use multiple values and + * wishes to receive more. + * Or {@link Accept#ACCEPT_AND_STOP} if this was the last value accepted by the argument. + * Or {@link Accept#REJECT_AND_STOP} if this was value was reject and the argument + * stops accepting new values with no error. + * Or a string in case of error. + * Never returns null. */ - public abstract String process(Arg arg, String extra); + public abstract Object process(Arg arg, String extra); } /** diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java index 6f0af81..99e0ba4 100644 --- a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java @@ -18,7 +18,9 @@ package com.android.sdklib.util; import com.android.sdklib.ISdkLog; import com.android.sdklib.StdSdkLog; -import com.android.sdklib.util.CommandLineParser; + +import java.util.Arrays; +import java.util.List; import junit.framework.TestCase; @@ -42,11 +44,19 @@ public class CommandLineParserTest extends TestCase { new String[][] { { "verb1", "action1", "Some action" }, { "verb1", "action2", "Another action" }, + { "verb2", NO_VERB_OBJECT, "Action with string array" }, }); define(Mode.STRING, false /*mandatory*/, "verb1", "action1", "1", "first", "non-mandatory flag", null); define(Mode.STRING, true /*mandatory*/, "verb1", "action1", "2", "second", "mandatory flag", null); + + define(Mode.STRING, true /*mandatory*/, + "verb2", NO_VERB_OBJECT, "1", "first", "1st mandatory flag", null); + define(Mode.STRING_ARRAY, true /*mandatory*/, + "verb2", NO_VERB_OBJECT, "2", "second", "2nd mandatory flag", null); + define(Mode.STRING, true /*mandatory*/, + "verb2", NO_VERB_OBJECT, "3", "third", "3rd mandatory flag", null); } @Override @@ -185,4 +195,52 @@ public class CommandLineParserTest extends TestCase { assertEquals(null, c.getValue("verb1", "action1", "first")); assertEquals(null, c.getValue("verb1", "action1", "second")); } + + public void testStringArray() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + c.parseArgs(new String[] { "verb2", + "-1", "value1", + "-2", "value2_a", "value2_b", "value2_c", "value2_d", + "-3", "value3" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals("value1", c.getValue("verb2", null, "first")); + assertTrue(c.getValue("verb2", null, "second") instanceof List); + assertEquals("[value2_a, value2_b, value2_c, value2_d]", + Arrays.toString(((List) c.getValue("verb2", null, "second")).toArray())); + assertEquals("value3", c.getValue("verb2", null, "third")); + } + + public void testStringArray_DashDash() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + // Use -- to tell argument -2 it can absorb any argument, including dashed ones. + // Logically -2 must be the last argument and -1/-3 must be placed before it. + c.parseArgs(new String[] { "verb2", + "-1", "value1", + "-3", "value3", + "-2", "value2_a", "--", "-value2_b", "--value2_c", "value2_d" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals("value1", c.getValue("verb2", null, "first")); + assertTrue(c.getValue("verb2", null, "second") instanceof List); + assertEquals("[value2_a, --, -value2_b, --value2_c, value2_d]", + Arrays.toString(((List) c.getValue("verb2", null, "second")).toArray())); + assertEquals("value3", c.getValue("verb2", null, "third")); + } + + public void testStringArray_EmptyStringArray() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + c.parseArgs(new String[] { "verb2", + "-1", "value1", + "-2", + "-3", "value3" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertEquals("Invalid usage for flag -2: No values provided.", c.getStdErr().trim()); + } } -- cgit v1.1