diff options
author | Raphael <raphael@google.com> | 2011-12-20 21:21:39 -0800 |
---|---|---|
committer | Raphael <raphael@google.com> | 2012-01-05 11:54:36 -0800 |
commit | 09c6e56052a136ce9f82876d3199a527d9cba678 (patch) | |
tree | 738bde9dc0d5aab1390afc1cb5533fdfa5083423 /sdkmanager/libs | |
parent | c000db8d112f4143249fa79a24c6ddb2ffc122f3 (diff) | |
download | sdk-09c6e56052a136ce9f82876d3199a527d9cba678.zip sdk-09c6e56052a136ce9f82876d3199a527d9cba678.tar.gz sdk-09c6e56052a136ce9f82876d3199a527d9cba678.tar.bz2 |
Move CommandLineProcessor from sdkmanager to sdklib as CommandLineParser
Change-Id: I4413efea2887436d167ce44db6f0cd711bdf8fdd
Diffstat (limited to 'sdkmanager/libs')
-rw-r--r-- | sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java | 895 | ||||
-rw-r--r-- | sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java | 188 |
2 files changed, 1083 insertions, 0 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java new file mode 100644 index 0000000..ae17813 --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/util/CommandLineParser.java @@ -0,0 +1,895 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package com.android.sdklib.util; + +import com.android.sdklib.ISdkLog; + +import java.util.HashMap; +import java.util.Map.Entry; + +/** + * Parses the command-line and stores flags needed or requested. + * <p/> + * This is a base class. To be useful you want to: + * <ul> + * <li>override it. + * <li>pass an action array to the constructor. + * <li>define flags for your actions. + * </ul> + * <p/> + * To use, call {@link #parseArgs(String[])} and then + * call {@link #getValue(String, String, String)}. + */ +public class CommandLineParser { + + /* + * Steps needed to add a new action: + * - Each action is defined as a "verb object" followed by parameters. + * - Either reuse a VERB_ constant or define a new one. + * - Either reuse an OBJECT_ constant or define a new one. + * - Add a new entry to mAction with a one-line help summary. + * - In the constructor, add a define() call for each parameter (either mandatory + * or optional) for the given action. + */ + + /** Internal verb name for internally hidden flags. */ + public final static String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$ + + /** String to use when the verb doesn't need any object. */ + public final static String NO_VERB_OBJECT = ""; //$NON-NLS-1$ + + /** The global help flag. */ + public static final String KEY_HELP = "help"; + /** The global verbose flag. */ + public static final String KEY_VERBOSE = "verbose"; + /** The global silent flag. */ + public static final String KEY_SILENT = "silent"; + + /** Verb requested by the user. Null if none specified, which will be an error. */ + private String mVerbRequested; + /** Direct object requested by the user. Can be null. */ + private String mDirectObjectRequested; + + /** + * Action definitions. + * <p/> + * This list serves two purposes: first it is used to know which verb/object + * actions are acceptable on the command-line; second it provides a summary + * for each action that is printed in the help. + * <p/> + * Each entry is a string array with: + * <ul> + * <li> the verb. + * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object). + * <li> a description. + * <li> an alternate form for the object (e.g. plural). + * </ul> + */ + private final String[][] mActions; + + private static final int ACTION_VERB_INDEX = 0; + private static final int ACTION_OBJECT_INDEX = 1; + private static final int ACTION_DESC_INDEX = 2; + private static final int ACTION_ALT_OBJECT_INDEX = 3; + + /** + * The map of all defined arguments. + * <p/> + * The key is a string "verb/directObject/longName". + */ + private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>(); + /** Logger */ + private final ISdkLog mLog; + + /** + * Constructs a new command-line processor. + * + * @param logger An SDK logger object. Must not be null. + * @param actions The list of actions recognized on the command-line. + * See the javadoc of {@link #mActions} for more details. + * + * @see #mActions + */ + public CommandLineParser(ISdkLog logger, String[][] actions) { + mLog = logger; + mActions = actions; + + /* + * usage should fit in 80 columns, including the space to print the options: + * " -v --verbose 7890123456789012345678901234567890123456789012345678901234567890" + */ + + define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE, + "Verbose mode, shows errors, warnings and all messages.", + false); + define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT, + "Silent mode, shows errors only.", + false); + define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP, + "Help on a specific command.", + false); + } + + /** + * Indicates if this command-line can work when no verb is specified. + * The default is false, which generates an error when no verb/object is specified. + * Derived implementations can set this to true if they can deal with a lack + * of verb/action. + */ + public boolean acceptLackOfVerb() { + return false; + } + + + //------------------ + // Helpers to get flags values + + /** Helper that returns true if --verbose was requested. */ + public boolean isVerbose() { + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue(); + } + + /** Helper that returns true if --silent was requested. */ + public boolean isSilent() { + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue(); + } + + /** Helper that returns true if --help was requested. */ + public boolean isHelpRequested() { + return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue(); + } + + /** Returns the verb name from the command-line. Can be null. */ + public String getVerb() { + return mVerbRequested; + } + + /** Returns the direct object name from the command-line. Can be null. */ + public String getDirectObject() { + return mDirectObjectRequested; + } + + //------------------ + + /** + * Raw access to parsed parameter values. + * <p/> + * The default is to scan all parameters. Parameters that have been explicitly set on the + * command line are returned first. Otherwise one with a non-null value is returned. + * <p/> + * Both a verb and a direct object filter can be specified. When they are non-null they limit + * the scope of the search. + * <p/> + * If nothing has been found, return the last default value seen matching the filter. + * + * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible + * verbs that match the direct object condition will be examined and the first + * value set will be used. + * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null, + * all possible direct objects that match the verb condition will be examined and + * the first value set will be used. + * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null. + * @return The current value object stored in the parameter, which depends on the argument mode. + */ + public Object getValue(String verb, String directObject, String longFlagName) { + + if (verb != null && directObject != null) { + String key = verb + '/' + directObject + '/' + longFlagName; + Arg arg = mArguments.get(key); + return arg.getCurrentValue(); + } + + Object lastDefault = null; + for (Arg arg : mArguments.values()) { + if (arg.getLongArg().equals(longFlagName)) { + if (verb == null || arg.getVerb().equals(verb)) { + if (directObject == null || arg.getDirectObject().equals(directObject)) { + if (arg.isInCommandLine()) { + return arg.getCurrentValue(); + } + if (arg.getCurrentValue() != null) { + lastDefault = arg.getCurrentValue(); + } + } + } + } + } + + return lastDefault; + } + + /** + * Internal setter for raw parameter value. + * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. + * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. + * @param longFlagName The long flag name for the given action. + * @param value The new current value object stored in the parameter, which depends on the + * argument mode. + */ + protected void setValue(String verb, String directObject, String longFlagName, Object value) { + String key = verb + '/' + directObject + '/' + longFlagName; + Arg arg = mArguments.get(key); + arg.setCurrentValue(value); + } + + /** + * Parses the command-line arguments. + * <p/> + * This method will exit and not return if a parsing error arise. + * + * @param args The arguments typically received by a main method. + */ + public void parseArgs(String[] args) { + String errorMsg = null; + String verb = null; + String directObject = null; + + try { + int n = args.length; + for (int i = 0; i < n; i++) { + Arg arg = null; + String a = args[i]; + if (a.startsWith("--")) { //$NON-NLS-1$ + arg = findLongArg(verb, directObject, a.substring(2)); + } else if (a.startsWith("-")) { //$NON-NLS-1$ + arg = findShortArg(verb, directObject, a.substring(1)); + } + + // No matching argument name found + if (arg == null) { + // Does it looks like a dashed parameter? + if (a.startsWith("-")) { //$NON-NLS-1$ + if (verb == null || directObject == null) { + // It looks like a dashed parameter and we don't have a a verb/object + // set yet, the parameter was just given too early. + + errorMsg = String.format( + "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?", + a); + return; + } else { + // It looks like a dashed parameter but it is unknown by this + // verb-object combination + + errorMsg = String.format( + "Flag '%1$s' is not valid for '%2$s %3$s'.", + a, verb, directObject); + return; + } + } + + if (verb == null) { + // Fill verb first. Find it. + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(a)) { + verb = a; + break; + } + } + + // Error if it was not a valid verb + if (verb == null) { + errorMsg = String.format( + "Expected verb after global parameters but found '%1$s' instead.", + a); + return; + } + + } else if (directObject == null) { + // Then fill the direct object. Find it. + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(verb)) { + if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) { + directObject = a; + break; + } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX && + actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) { + // if the alternate form exist and is used, we internally + // only memorize the default direct object form. + directObject = actionDesc[ACTION_OBJECT_INDEX]; + break; + } + } + } + + // Error if it was not a valid object for that verb + if (directObject == null) { + errorMsg = String.format( + "Expected verb after global parameters but found '%1$s' instead.", + a); + return; + + } + } else { + // The argument is not a dashed parameter and we already + // have a verb/object. Must be some extra unknown argument. + errorMsg = String.format( + "Argument '%1$s' is not recognized.", + a); + } + } else if (arg != null) { + // This argument was present on the command line + arg.setInCommandLine(true); + + // Process keyword + String error = null; + if (arg.getMode().needsExtra()) { + if (++i >= 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; + } + + error = arg.getMode().process(arg, b); + } else { + error = arg.getMode().process(arg, null); + + if (isHelpRequested()) { + // The --help flag was requested. We'll continue the usual processing + // so that we can find the optional verb/object words. Those will be + // used to print specific help. + // Setting a non-null error message triggers printing the help, however + // there is no specific error to print. + errorMsg = ""; //$NON-NLS-1$ + } + } + + if (error != null) { + errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error); + return; + } + } + } + + if (errorMsg == null) { + if (verb == null && !acceptLackOfVerb()) { + errorMsg = "Missing verb name."; + } else if (verb != null) { + if (directObject == null) { + // Make sure this verb has an optional direct object + for (String[] actionDesc : mActions) { + if (actionDesc[ACTION_VERB_INDEX].equals(verb) && + actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) { + directObject = NO_VERB_OBJECT; + break; + } + } + + if (directObject == null) { + errorMsg = String.format("Missing object name for verb '%1$s'.", verb); + return; + } + } + + // Validate that all mandatory arguments are non-null for this action + String missing = null; + boolean plural = false; + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getVerb().equals(verb) && + arg.getDirectObject().equals(directObject)) { + if (arg.isMandatory() && arg.getCurrentValue() == null) { + if (missing == null) { + missing = "--" + arg.getLongArg(); //$NON-NLS-1$ + } else { + missing += ", --" + arg.getLongArg(); //$NON-NLS-1$ + plural = true; + } + } + } + } + + if (missing != null) { + errorMsg = String.format( + "The %1$s %2$s must be defined for action '%3$s %4$s'", + plural ? "parameters" : "parameter", + missing, + verb, + directObject); + } + + mVerbRequested = verb; + mDirectObjectRequested = directObject; + } + } + } finally { + if (errorMsg != null) { + printHelpAndExitForAction(verb, directObject, errorMsg); + } + } + } + + /** + * Finds an {@link Arg} given an action name and a long flag name. + * @return The {@link Arg} found or null. + */ + protected Arg findLongArg(String verb, String directObject, String longName) { + if (verb == null) { + verb = GLOBAL_FLAG_VERB; + } + if (directObject == null) { + directObject = NO_VERB_OBJECT; + } + String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$ + return mArguments.get(key); + } + + /** + * Finds an {@link Arg} given an action name and a short flag name. + * @return The {@link Arg} found or null. + */ + protected Arg findShortArg(String verb, String directObject, String shortName) { + if (verb == null) { + verb = GLOBAL_FLAG_VERB; + } + if (directObject == null) { + directObject = NO_VERB_OBJECT; + } + + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { + if (shortName.equals(arg.getShortArg())) { + return arg; + } + } + } + + return null; + } + + /** + * Prints the help/usage and exits. + * + * @param errorFormat Optional error message to print prior to usage using String.format + * @param args Arguments for String.format + */ + public void printHelpAndExit(String errorFormat, Object... args) { + printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args); + } + + /** + * Prints the help/usage and exits. + * + * @param verb If null, displays help for all verbs. If not null, display help only + * for that specific verb. In all cases also displays general usage and action list. + * @param directObject If null, displays help for all verb objects. + * If not null, displays help only for that specific action + * In all cases also display general usage and action list. + * @param errorFormat Optional error message to print prior to usage using String.format + * @param args Arguments for String.format + */ + public void printHelpAndExitForAction(String verb, String directObject, + String errorFormat, Object... args) { + if (errorFormat != null && errorFormat.length() > 0) { + stderr(errorFormat, args); + } + + /* + * usage should fit in 80 columns + * 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + */ + stdout("\n" + + "Usage:\n" + + " android [global options] %s [action options]\n" + + "\n" + + "Global options:", + verb == null ? "action" : + verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$ + listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT); + + if (verb == null || directObject == null) { + stdout("\nValid actions are composed of a verb and an optional direct object:"); + for (String[] action : mActions) { + if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) { + stdout("- %1$6s %2$-13s: %3$s", + action[ACTION_VERB_INDEX], + action[ACTION_OBJECT_INDEX], + action[ACTION_DESC_INDEX]); + } + } + } + + // Only print details if a verb/object is requested + if (verb != null) { + for (String[] action : mActions) { + if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) { + if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) { + stdout("\nAction \"%1$s %2$s\":", + action[ACTION_VERB_INDEX], + action[ACTION_OBJECT_INDEX]); + stdout(" %1$s", action[ACTION_DESC_INDEX]); + stdout("Options:"); + listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]); + } + } + } + } + + exit(); + } + + /** + * Internal helper to print all the option flags for a given action name. + */ + protected void listOptions(String verb, String directObject) { + int numOptions = 0; + int longArgLen = 8; + + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { + int n = arg.getLongArg().length(); + if (n > longArgLen) { + longArgLen = n; + } + } + } + + for (Entry<String, Arg> entry : mArguments.entrySet()) { + Arg arg = entry.getValue(); + if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { + + String value = ""; //$NON-NLS-1$ + String required = ""; //$NON-NLS-1$ + if (arg.isMandatory()) { + required = " [required]"; + + } else { + if (arg.getDefaultValue() instanceof String[]) { + for (String v : (String[]) arg.getDefaultValue()) { + if (value.length() > 0) { + value += ", "; + } + value += v; + } + } else if (arg.getDefaultValue() != null) { + Object v = arg.getDefaultValue(); + if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) { + value = v.toString(); + } + } + if (value.length() > 0) { + value = " [Default: " + value + "]"; + } + } + + // Java doesn't support * for printf variable width, so we'll insert the long arg + // width "manually" in the printf format string. + String longArgWidth = Integer.toString(longArgLen + 2); + + // Print a line in the form " -1_letter_arg --long_arg description" + // where either the 1-letter arg or the long arg are optional. + String output = String.format( + " %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$ + arg.getShortArg().length() > 0 ? + "-" + arg.getShortArg() : //$NON-NLS-1$ + "", //$NON-NLS-1$ + arg.getLongArg().length() > 0 ? + "--" + arg.getLongArg() : //$NON-NLS-1$ + "", //$NON-NLS-1$ + arg.getDescription(), + value, + required); + stdout(output); + numOptions++; + } + } + + if (numOptions == 0) { + stdout(" No options"); + } + } + + //---- + + + /** + * The mode of an argument specifies the type of variable it represents, + * whether an extra parameter is required after the flag and how to parse it. + */ + public static enum Mode { + /** Argument value is a Boolean. Default value is a Boolean. */ + BOOLEAN { + @Override + public boolean needsExtra() { + return false; + } + @Override + public String process(Arg arg, String extra) { + // Toggle the current value + arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue()); + return null; + } + }, + + /** Argument value is an Integer. Default value is an Integer. */ + INTEGER { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String process(Arg arg, String extra) { + try { + arg.setCurrentValue(Integer.parseInt(extra)); + return null; + } catch (NumberFormatException e) { + return String.format("Failed to parse '%1$s' as an integer: %2$s", extra, + e.getMessage()); + } + } + }, + + /** Argument value is a String. Default value is a String[]. */ + ENUM { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String 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; + } + + if (desc.length() != 0) { + desc.append(", "); + } + desc.append(value); + } + + return String.format("'%1$s' is not one of %2$s", extra, desc.toString()); + } + }, + + /** Argument value is a String. Default value is a null. */ + STRING { + @Override + public boolean needsExtra() { + return true; + } + @Override + public String process(Arg arg, String extra) { + arg.setCurrentValue(extra); + return null; + } + }; + + /** + * Returns true if this mode requires an extra parameter. + */ + public abstract boolean needsExtra(); + + /** + * Processes the flag for this argument. + * + * @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. + */ + public abstract String process(Arg arg, String extra); + } + + /** + * An argument accepted by the command-line, also called "a flag". + * Arguments must have a short version (one letter), a long version name and a description. + * They can have a default value, or it can be null. + * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String + * or a String array (in which case the first item is the current by default.) + */ + static class Arg { + /** Verb for that argument. Never null. */ + private final String mVerb; + /** Direct Object for that argument. Never null, but can be empty string. */ + private final String mDirectObject; + /** The 1-letter short name of the argument, e.g. -v. */ + private final String mShortName; + /** The long name of the argument, e.g. --verbose. */ + private final String mLongName; + /** A description. Never null. */ + private final String mDescription; + /** A default value. Can be null. */ + private final Object mDefaultValue; + /** The argument mode (type + process method). Never null. */ + private final Mode mMode; + /** True if this argument is mandatory for this verb/directobject. */ + private final boolean mMandatory; + /** Current value. Initially set to the default value. */ + private Object mCurrentValue; + /** True if the argument has been used on the command line. */ + private boolean mInCommandLine; + + /** + * Creates a new argument flag description. + * + * @param mode The {@link Mode} for the argument. + * @param mandatory True if this argument is mandatory for this action. + * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG. + * @param shortName The one-letter short argument name. Can be empty but not null. + * @param longName The long argument name. Can be empty but not null. + * @param description The description. Cannot be null. + * @param defaultValue The default value (or values), which depends on the selected {@link Mode}. + */ + public Arg(Mode mode, + boolean mandatory, + String verb, + String directObject, + String shortName, + String longName, + String description, + Object defaultValue) { + mMode = mode; + mMandatory = mandatory; + mVerb = verb; + mDirectObject = directObject; + mShortName = shortName; + mLongName = longName; + mDescription = description; + mDefaultValue = defaultValue; + mInCommandLine = false; + if (defaultValue instanceof String[]) { + mCurrentValue = ((String[])defaultValue)[0]; + } else { + mCurrentValue = mDefaultValue; + } + } + + /** Return true if this argument is mandatory for this verb/directobject. */ + public boolean isMandatory() { + return mMandatory; + } + + /** Returns the 1-letter short name of the argument, e.g. -v. */ + public String getShortArg() { + return mShortName; + } + + /** Returns the long name of the argument, e.g. --verbose. */ + public String getLongArg() { + return mLongName; + } + + /** Returns the description. Never null. */ + public String getDescription() { + return mDescription; + } + + /** Returns the verb for that argument. Never null. */ + public String getVerb() { + return mVerb; + } + + /** Returns the direct Object for that argument. Never null, but can be empty string. */ + public String getDirectObject() { + return mDirectObject; + } + + /** Returns the default value. Can be null. */ + public Object getDefaultValue() { + return mDefaultValue; + } + + /** Returns the current value. Initially set to the default value. Can be null. */ + public Object getCurrentValue() { + return mCurrentValue; + } + + /** Sets the current value. Can be null. */ + public void setCurrentValue(Object currentValue) { + mCurrentValue = currentValue; + } + + /** Returns the argument mode (type + process method). Never null. */ + public Mode getMode() { + return mMode; + } + + /** Returns true if the argument has been used on the command line. */ + public boolean isInCommandLine() { + return mInCommandLine; + } + + /** Sets if the argument has been used on the command line. */ + public void setInCommandLine(boolean inCommandLine) { + mInCommandLine = inCommandLine; + } + } + + /** + * Internal helper to define a new argument for a give action. + * + * @param mode The {@link Mode} for the argument. + * @param mandatory The argument is required (never if {@link Mode#BOOLEAN}) + * @param verb The verb name. Can be #INTERNAL_VERB. + * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG. + * @param shortName The one-letter short argument name. Can be empty but not null. + * @param longName The long argument name. Can be empty but not null. + * @param description The description. Cannot be null. + * @param defaultValue The default value (or values), which depends on the selected {@link Mode}. + */ + protected void define(Mode mode, + boolean mandatory, + String verb, + String directObject, + String shortName, String longName, + String description, Object defaultValue) { + assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory + + // We should always have at least a short or long name, ideally both but never none. + assert shortName != null; + assert longName != null; + assert shortName.length() > 0 || longName.length() > 0; + + if (directObject == null) { + directObject = NO_VERB_OBJECT; + } + + String key = verb + '/' + directObject + '/' + longName; + mArguments.put(key, new Arg(mode, mandatory, + verb, directObject, shortName, longName, description, defaultValue)); + } + + /** + * Exits in case of error. + * This is protected so that it can be overridden in unit tests. + */ + protected void exit() { + System.exit(1); + } + + /** + * Prints a line to stdout. + * This is protected so that it can be overridden in unit tests. + * + * @param format The string to be formatted. Cannot be null. + * @param args Format arguments. + */ + protected void stdout(String format, Object...args) { + String output = String.format(format, args); + output = LineUtil.reflowLine(output); + mLog.printf("%s\n", output); //$NON-NLS-1$ + } + + /** + * Prints a line to stderr. + * This is protected so that it can be overridden in unit tests. + * + * @param format The string to be formatted. Cannot be null. + * @param args Format arguments. + */ + protected void stderr(String format, Object...args) { + mLog.error(null, format, args); + } +} 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 new file mode 100644 index 0000000..6f0af81 --- /dev/null +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/util/CommandLineParserTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package com.android.sdklib.util; + +import com.android.sdklib.ISdkLog; +import com.android.sdklib.StdSdkLog; +import com.android.sdklib.util.CommandLineParser; + +import junit.framework.TestCase; + + +public class CommandLineParserTest extends TestCase { + + private StdSdkLog mLog; + + /** + * A mock version of the {@link CommandLineParser} class that does not + * exits and captures its stdout/stderr output. + */ + public static class MockCommandLineProcessor extends CommandLineParser { + private boolean mExitCalled; + private boolean mHelpCalled; + private String mStdOut = ""; + private String mStdErr = ""; + + public MockCommandLineProcessor(ISdkLog logger) { + super(logger, + new String[][] { + { "verb1", "action1", "Some action" }, + { "verb1", "action2", "Another action" }, + }); + 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); + } + + @Override + public void printHelpAndExitForAction(String verb, String directObject, + String errorFormat, Object... args) { + mHelpCalled = true; + super.printHelpAndExitForAction(verb, directObject, errorFormat, args); + } + + @Override + protected void exit() { + mExitCalled = true; + } + + @Override + protected void stdout(String format, Object... args) { + String s = String.format(format, args); + mStdOut += s + "\n"; + // don't call super to avoid printing stuff + } + + @Override + protected void stderr(String format, Object... args) { + String s = String.format(format, args); + mStdErr += s + "\n"; + // don't call super to avoid printing stuff + } + + public boolean wasHelpCalled() { + return mHelpCalled; + } + + public boolean wasExitCalled() { + return mExitCalled; + } + + public String getStdOut() { + return mStdOut; + } + + public String getStdErr() { + return mStdErr; + } + } + + @Override + protected void setUp() throws Exception { + mLog = new StdSdkLog(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testPrintHelpAndExit() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertTrue(c.getStdOut().equals("")); + assertTrue(c.getStdErr().equals("")); + c.printHelpAndExit(null); + assertTrue(c.getStdOut().indexOf("-v") != -1); + assertTrue(c.getStdOut().indexOf("--verbose") != -1); + assertTrue(c.getStdErr().equals("")); + assertTrue(c.wasExitCalled()); + + c = new MockCommandLineProcessor(mLog); + assertFalse(c.wasExitCalled()); + assertTrue(c.getStdOut().equals("")); + assertTrue(c.getStdErr().indexOf("Missing parameter") == -1); + + c.printHelpAndExit("Missing %s", "parameter"); + assertTrue(c.wasExitCalled()); + assertFalse(c.getStdOut().equals("")); + assertTrue(c.getStdErr().indexOf("Missing parameter") != -1); + } + + public void testVerbose() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + assertFalse(c.isVerbose()); + c.parseArgs(new String[] { "-v" }); + assertTrue(c.isVerbose()); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1); + + c = new MockCommandLineProcessor(mLog); + c.parseArgs(new String[] { "--verbose" }); + assertTrue(c.isVerbose()); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1); + } + + public void testHelp() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + c.parseArgs(new String[] { "-h" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1); + + c = new MockCommandLineProcessor(mLog); + c.parseArgs(new String[] { "--help" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1); + } + + public void testMandatory() { + MockCommandLineProcessor c = new MockCommandLineProcessor(mLog); + + c.parseArgs(new String[] { "verb1", "action1", "-1", "value1", "-2", "value2" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals("value1", c.getValue("verb1", "action1", "first")); + assertEquals("value2", c.getValue("verb1", "action1", "second")); + + c = new MockCommandLineProcessor(mLog); + c.parseArgs(new String[] { "verb1", "action1", "-2", "value2" }); + assertFalse(c.wasExitCalled()); + assertFalse(c.wasHelpCalled()); + assertEquals("", c.getStdErr()); + assertEquals(null, c.getValue("verb1", "action1", "first")); + assertEquals("value2", c.getValue("verb1", "action1", "second")); + + c = new MockCommandLineProcessor(mLog); + c.parseArgs(new String[] { "verb1", "action1" }); + assertTrue(c.wasExitCalled()); + assertTrue(c.wasHelpCalled()); + assertTrue(c.getStdErr().indexOf("must be defined") != -1); + assertEquals(null, c.getValue("verb1", "action1", "first")); + assertEquals(null, c.getValue("verb1", "action1", "second")); + } +} |