2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.sdkmanager;
19 import com.android.sdklib.ISdkLog;
21 import java.util.HashMap;
22 import java.util.Map.Entry;
25 * Parses the command-line and stores flags needed or requested.
27 * This is a base class. To be useful you want to:
30 * <li>pass an action array to the constructor.
31 * <li>define flags for your actions.
34 * To use, call {@link #parseArgs(String[])} and then
35 * call {@link #getValue(String, String, String)}.
37 class CommandLineProcessor {
40 * Steps needed to add a new action:
41 * - Each action is defined as a "verb object" followed by parameters.
42 * - Either reuse a VERB_ constant or define a new one.
43 * - Either reuse an OBJECT_ constant or define a new one.
44 * - Add a new entry to mAction with a one-line help summary.
45 * - In the constructor, add a define() call for each parameter (either mandatory
46 * or optional) for the given action.
49 /** Internal verb name for internally hidden flags. */
50 public final static String GLOBAL_FLAG_VERB = "@@internal@@";
52 /** String to use when the verb doesn't need any object. */
53 public final static String NO_VERB_OBJECT = "";
55 /** The global help flag. */
56 public static final String KEY_HELP = "help";
57 /** The global verbose flag. */
58 public static final String KEY_VERBOSE = "verbose";
59 /** The global silent flag. */
60 public static final String KEY_SILENT = "silent";
62 /** Verb requested by the user. Null if none specified, which will be an error. */
63 private String mVerbRequested;
64 /** Direct object requested by the user. Can be null. */
65 private String mDirectObjectRequested;
70 * This list serves two purposes: first it is used to know which verb/object
71 * actions are acceptable on the command-line; second it provides a summary
72 * for each action that is printed in the help.
74 * Each entry is a string array with:
77 * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
79 * <li> an alternate form for the object (e.g. plural).
82 private final String[][] mActions;
84 private static final int ACTION_VERB_INDEX = 0;
85 private static final int ACTION_OBJECT_INDEX = 1;
86 private static final int ACTION_DESC_INDEX = 2;
87 private static final int ACTION_ALT_OBJECT_INDEX = 3;
90 * The map of all defined arguments.
92 * The key is a string "verb/directObject/longName".
94 private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
96 private final ISdkLog mLog;
99 * Constructs a new command-line processor.
101 * @param logger An SDK logger object. Must not be null.
102 * @param actions The list of actions recognized on the command-line.
103 * See the javadoc of {@link #mActions} for more details.
107 public CommandLineProcessor(ISdkLog logger, String[][] actions) {
111 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
112 "Verbose mode: errors, warnings and informational messages are printed.",
114 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
115 "Silent mode: only errors are printed out.",
117 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
118 "Help on a specific command.",
123 * Indicates if this command-line can work when no verb is specified.
124 * The default is false, which generates an error when no verb/object is specified.
125 * Derived implementations can set this to true if they can deal with a lack
128 public boolean acceptLackOfVerb() {
134 // Helpers to get flags values
136 /** Helper that returns true if --verbose was requested. */
137 public boolean isVerbose() {
138 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
141 /** Helper that returns true if --silent was requested. */
142 public boolean isSilent() {
143 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
146 /** Helper that returns true if --help was requested. */
147 public boolean isHelpRequested() {
148 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
151 /** Returns the verb name from the command-line. Can be null. */
152 public String getVerb() {
153 return mVerbRequested;
156 /** Returns the direct object name from the command-line. Can be null. */
157 public String getDirectObject() {
158 return mDirectObjectRequested;
164 * Raw access to parsed parameter values.
166 * The default is to scan all parameters. Parameters that have been explicitly set on the
167 * command line are returned first. Otherwise one with a non-null value is returned.
169 * Both a verb and a direct object filter can be specified. When they are non-null they limit
170 * the scope of the search.
172 * If nothing has been found, return the last default value seen matching the filter.
174 * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
175 * verbs that match the direct object condition will be examined and the first
176 * value set will be used.
177 * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
178 * all possible direct objects that match the verb condition will be examined and
179 * the first value set will be used.
180 * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
181 * @return The current value object stored in the parameter, which depends on the argument mode.
183 public Object getValue(String verb, String directObject, String longFlagName) {
185 if (verb != null && directObject != null) {
186 String key = verb + "/" + directObject + "/" + longFlagName;
187 Arg arg = mArguments.get(key);
188 return arg.getCurrentValue();
191 Object lastDefault = null;
192 for (Arg arg : mArguments.values()) {
193 if (arg.getLongArg().equals(longFlagName)) {
194 if (verb == null || arg.getVerb().equals(verb)) {
195 if (directObject == null || arg.getDirectObject().equals(directObject)) {
196 if (arg.isInCommandLine()) {
197 return arg.getCurrentValue();
199 if (arg.getCurrentValue() != null) {
200 lastDefault = arg.getCurrentValue();
211 * Internal setter for raw parameter value.
212 * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
213 * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
214 * @param longFlagName The long flag name for the given action.
215 * @param value The new current value object stored in the parameter, which depends on the
218 protected void setValue(String verb, String directObject, String longFlagName, Object value) {
219 String key = verb + "/" + directObject + "/" + longFlagName;
220 Arg arg = mArguments.get(key);
221 arg.setCurrentValue(value);
225 * Parses the command-line arguments.
227 * This method will exit and not return if a parsing error arise.
229 * @param args The arguments typically received by a main method.
231 public void parseArgs(String[] args) {
232 String needsHelp = null;
234 String directObject = null;
238 for (int i = 0; i < n; i++) {
241 if (a.startsWith("--")) {
242 arg = findLongArg(verb, directObject, a.substring(2));
243 } else if (a.startsWith("-")) {
244 arg = findShortArg(verb, directObject, a.substring(1));
247 // No matching argument name found
249 // Does it looks like a dashed parameter?
250 if (a.startsWith("-")) {
251 if (verb == null || directObject == null) {
252 // It looks like a dashed parameter and we don't have a a verb/object
253 // set yet, the parameter was just given too early.
255 needsHelp = String.format(
256 "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
260 // It looks like a dashed parameter and but it is unknown by this
261 // verb-object combination
263 needsHelp = String.format(
264 "Flag '%1$s' is not valid for '%2$s %3$s'.",
265 a, verb, directObject);
271 // Fill verb first. Find it.
272 for (String[] actionDesc : mActions) {
273 if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
279 // Error if it was not a valid verb
281 needsHelp = String.format(
282 "Expected verb after global parameters but found '%1$s' instead.",
287 } else if (directObject == null) {
288 // Then fill the direct object. Find it.
289 for (String[] actionDesc : mActions) {
290 if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
291 if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
294 } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
295 actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
296 // if the alternate form exist and is used, we internally
297 // only memorize the default direct object form.
298 directObject = actionDesc[ACTION_OBJECT_INDEX];
304 // Error if it was not a valid object for that verb
305 if (directObject == null) {
306 needsHelp = String.format(
307 "Expected verb after global parameters but found '%1$s' instead.",
313 } else if (arg != null) {
314 // This argument was present on the command line
315 arg.setInCommandLine(true);
319 if (arg.getMode().needsExtra()) {
321 needsHelp = String.format("Missing argument for flag %1$s.", a);
325 error = arg.getMode().process(arg, args[i]);
327 error = arg.getMode().process(arg, null);
329 // If we just toggled help, we want to exit now without printing any error.
330 // We do this test here only when a Boolean flag is toggled since booleans
331 // are the only flags that don't take parameters and help is a boolean.
332 if (isHelpRequested()) {
333 printHelpAndExit(null);
334 // The call above should terminate however in unit tests we override
335 // it so we still need to return here.
341 needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
347 if (needsHelp == null) {
348 if (verb == null && !acceptLackOfVerb()) {
349 needsHelp = "Missing verb name.";
350 } else if (verb != null) {
351 if (directObject == null) {
352 // Make sure this verb has an optional direct object
353 for (String[] actionDesc : mActions) {
354 if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
355 actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
356 directObject = NO_VERB_OBJECT;
361 if (directObject == null) {
362 needsHelp = String.format("Missing object name for verb '%1$s'.", verb);
367 // Validate that all mandatory arguments are non-null for this action
368 String missing = null;
369 boolean plural = false;
370 for (Entry<String, Arg> entry : mArguments.entrySet()) {
371 Arg arg = entry.getValue();
372 if (arg.getVerb().equals(verb) &&
373 arg.getDirectObject().equals(directObject)) {
374 if (arg.isMandatory() && arg.getCurrentValue() == null) {
375 if (missing == null) {
376 missing = "--" + arg.getLongArg();
378 missing += ", --" + arg.getLongArg();
385 if (missing != null) {
386 needsHelp = String.format(
387 "The %1$s %2$s must be defined for action '%3$s %4$s'",
388 plural ? "parameters" : "parameter",
394 mVerbRequested = verb;
395 mDirectObjectRequested = directObject;
399 if (needsHelp != null) {
400 printHelpAndExitForAction(verb, directObject, needsHelp);
406 * Finds an {@link Arg} given an action name and a long flag name.
407 * @return The {@link Arg} found or null.
409 protected Arg findLongArg(String verb, String directObject, String longName) {
411 verb = GLOBAL_FLAG_VERB;
413 if (directObject == null) {
414 directObject = NO_VERB_OBJECT;
416 String key = verb + "/" + directObject + "/" + longName;
417 return mArguments.get(key);
421 * Finds an {@link Arg} given an action name and a short flag name.
422 * @return The {@link Arg} found or null.
424 protected Arg findShortArg(String verb, String directObject, String shortName) {
426 verb = GLOBAL_FLAG_VERB;
428 if (directObject == null) {
429 directObject = NO_VERB_OBJECT;
432 for (Entry<String, Arg> entry : mArguments.entrySet()) {
433 Arg arg = entry.getValue();
434 if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
435 if (shortName.equals(arg.getShortArg())) {
445 * Prints the help/usage and exits.
447 * @param errorFormat Optional error message to print prior to usage using String.format
448 * @param args Arguments for String.format
450 public void printHelpAndExit(String errorFormat, Object... args) {
451 printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
455 * Prints the help/usage and exits.
457 * @param verb If null, displays help for all verbs. If not null, display help only
458 * for that specific verb. In all cases also displays general usage and action list.
459 * @param directObject If null, displays help for all verb objects.
460 * If not null, displays help only for that specific action
461 * In all cases also display general usage and action list.
462 * @param errorFormat Optional error message to print prior to usage using String.format
463 * @param args Arguments for String.format
465 public void printHelpAndExitForAction(String verb, String directObject,
466 String errorFormat, Object... args) {
467 if (errorFormat != null) {
468 stderr(errorFormat, args);
472 * usage should fit in 80 columns
473 * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
477 " android [global options] action [action options]\n" +
480 listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
482 if (verb == null || directObject == null) {
483 stdout("\nValid actions are composed of a verb and an optional direct object:");
484 for (String[] action : mActions) {
486 stdout("- %1$6s %2$-12s: %3$s",
487 action[ACTION_VERB_INDEX],
488 action[ACTION_OBJECT_INDEX],
489 action[ACTION_DESC_INDEX]);
493 for (String[] action : mActions) {
494 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
495 if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
496 stdout("\nAction \"%1$s %2$s\":",
497 action[ACTION_VERB_INDEX],
498 action[ACTION_OBJECT_INDEX]);
499 stdout(" %1$s", action[ACTION_DESC_INDEX]);
501 listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
510 * Internal helper to print all the option flags for a given action name.
512 protected void listOptions(String verb, String directObject) {
514 for (Entry<String, Arg> entry : mArguments.entrySet()) {
515 Arg arg = entry.getValue();
516 if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
519 String required = "";
520 if (arg.isMandatory()) {
521 required = " [required]";
524 if (arg.getDefaultValue() instanceof String[]) {
525 for (String v : (String[]) arg.getDefaultValue()) {
526 if (value.length() > 0) {
531 } else if (arg.getDefaultValue() != null) {
532 Object v = arg.getDefaultValue();
533 if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
534 value = v.toString();
537 if (value.length() > 0) {
538 value = " [Default: " + value + "]";
542 stdout(" -%1$s %2$-10s %3$s%4$s%5$s",
544 "--" + arg.getLongArg(),
545 arg.getDescription(),
552 if (numOptions == 0) {
553 stdout(" No options");
560 * The mode of an argument specifies the type of variable it represents,
561 * whether an extra parameter is required after the flag and how to parse it.
564 /** Argument value is a Boolean. Default value is a Boolean. */
567 public boolean needsExtra() {
571 public String process(Arg arg, String extra) {
572 // Toggle the current value
573 arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
578 /** Argument value is an Integer. Default value is an Integer. */
581 public boolean needsExtra() {
585 public String process(Arg arg, String extra) {
587 arg.setCurrentValue(Integer.parseInt(extra));
589 } catch (NumberFormatException e) {
590 return String.format("Failed to parse '%1$s' as an integer: %2%s",
591 extra, e.getMessage());
596 /** Argument value is a String. Default value is a String[]. */
599 public boolean needsExtra() {
603 public String process(Arg arg, String extra) {
604 StringBuilder desc = new StringBuilder();
605 String[] values = (String[]) arg.getDefaultValue();
606 for (String value : values) {
607 if (value.equals(extra)) {
608 arg.setCurrentValue(extra);
612 if (desc.length() != 0) {
618 return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
622 /** Argument value is a String. Default value is a null. */
625 public boolean needsExtra() {
629 public String process(Arg arg, String extra) {
630 arg.setCurrentValue(extra);
636 * Returns true if this mode requires an extra parameter.
638 public abstract boolean needsExtra();
641 * Processes the flag for this argument.
643 * @param arg The argument being processed.
644 * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
645 * @return An error string or null if there's no error.
647 public abstract String process(Arg arg, String extra);
651 * An argument accepted by the command-line, also called "a flag".
652 * Arguments must have a short version (one letter), a long version name and a description.
653 * They can have a default value, or it can be null.
654 * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
655 * or a String array (in which case the first item is the current by default.)
658 /** Verb for that argument. Never null. */
659 private final String mVerb;
660 /** Direct Object for that argument. Never null, but can be empty string. */
661 private final String mDirectObject;
662 /** The 1-letter short name of the argument, e.g. -v. */
663 private final String mShortName;
664 /** The long name of the argument, e.g. --verbose. */
665 private final String mLongName;
666 /** A description. Never null. */
667 private final String mDescription;
668 /** A default value. Can be null. */
669 private final Object mDefaultValue;
670 /** The argument mode (type + process method). Never null. */
671 private final Mode mMode;
672 /** True if this argument is mandatory for this verb/directobject. */
673 private final boolean mMandatory;
674 /** Current value. Initially set to the default value. */
675 private Object mCurrentValue;
676 /** True if the argument has been used on the command line. */
677 private boolean mInCommandLine;
680 * Creates a new argument flag description.
682 * @param mode The {@link Mode} for the argument.
683 * @param mandatory True if this argument is mandatory for this action.
684 * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
685 * @param shortName The one-letter short argument name. Cannot be empty nor null.
686 * @param longName The long argument name. Cannot be empty nor null.
687 * @param description The description. Cannot be null.
688 * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
690 public Arg(Mode mode,
697 Object defaultValue) {
699 mMandatory = mandatory;
701 mDirectObject = directObject;
702 mShortName = shortName;
703 mLongName = longName;
704 mDescription = description;
705 mDefaultValue = defaultValue;
706 mInCommandLine = false;
707 if (defaultValue instanceof String[]) {
708 mCurrentValue = ((String[])defaultValue)[0];
710 mCurrentValue = mDefaultValue;
714 /** Return true if this argument is mandatory for this verb/directobject. */
715 public boolean isMandatory() {
719 /** Returns the 1-letter short name of the argument, e.g. -v. */
720 public String getShortArg() {
724 /** Returns the long name of the argument, e.g. --verbose. */
725 public String getLongArg() {
729 /** Returns the description. Never null. */
730 public String getDescription() {
734 /** Returns the verb for that argument. Never null. */
735 public String getVerb() {
739 /** Returns the direct Object for that argument. Never null, but can be empty string. */
740 public String getDirectObject() {
741 return mDirectObject;
744 /** Returns the default value. Can be null. */
745 public Object getDefaultValue() {
746 return mDefaultValue;
749 /** Returns the current value. Initially set to the default value. Can be null. */
750 public Object getCurrentValue() {
751 return mCurrentValue;
754 /** Sets the current value. Can be null. */
755 public void setCurrentValue(Object currentValue) {
756 mCurrentValue = currentValue;
759 /** Returns the argument mode (type + process method). Never null. */
760 public Mode getMode() {
764 /** Returns true if the argument has been used on the command line. */
765 public boolean isInCommandLine() {
766 return mInCommandLine;
769 /** Sets if the argument has been used on the command line. */
770 public void setInCommandLine(boolean inCommandLine) {
771 mInCommandLine = inCommandLine;
776 * Internal helper to define a new argument for a give action.
778 * @param mode The {@link Mode} for the argument.
779 * @param verb The verb name. Can be #INTERNAL_VERB.
780 * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
781 * @param shortName The one-letter short argument name. Cannot be empty nor null.
782 * @param longName The long argument name. Cannot be empty nor null.
783 * @param description The description. Cannot be null.
784 * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
786 protected void define(Mode mode,
790 String shortName, String longName,
791 String description, Object defaultValue) {
792 assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
794 if (directObject == null) {
795 directObject = NO_VERB_OBJECT;
798 String key = verb + "/" + directObject + "/" + longName;
799 mArguments.put(key, new Arg(mode, mandatory,
800 verb, directObject, shortName, longName, description, defaultValue));
804 * Exits in case of error.
805 * This is protected so that it can be overridden in unit tests.
807 protected void exit() {
812 * Prints a line to stdout.
813 * This is protected so that it can be overridden in unit tests.
815 * @param format The string to be formatted. Cannot be null.
816 * @param args Format arguments.
818 protected void stdout(String format, Object...args) {
819 mLog.printf(format + "\n", args);
823 * Prints a line to stderr.
824 * This is protected so that it can be overridden in unit tests.
826 * @param format The string to be formatted. Cannot be null.
827 * @param args Format arguments.
829 protected void stderr(String format, Object...args) {
830 mLog.error(null, format, args);