1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 17 package com.android.sdklib.util; 18 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.utils.ILogger; 22 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Map.Entry; 27 28 /** 29 * Parses the command-line and stores flags needed or requested. 30 * <p/> 31 * This is a base class. To be useful you want to: 32 * <ul> 33 * <li>override it. 34 * <li>pass an action array to the constructor. 35 * <li>define flags for your actions. 36 * </ul> 37 * <p/> 38 * To use, call {@link #parseArgs(String[])} and then 39 * call {@link #getValue(String, String, String)}. 40 */ 41 public class CommandLineParser { 42 43 /* 44 * Steps needed to add a new action: 45 * - Each action is defined as a "verb object" followed by parameters. 46 * - Either reuse a VERB_ constant or define a new one. 47 * - Either reuse an OBJECT_ constant or define a new one. 48 * - Add a new entry to mAction with a one-line help summary. 49 * - In the constructor, add a define() call for each parameter (either mandatory 50 * or optional) for the given action. 51 */ 52 53 /** Internal verb name for internally hidden flags. */ 54 public final static String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$ 55 56 /** String to use when the verb doesn't need any object. */ 57 public final static String NO_VERB_OBJECT = ""; //$NON-NLS-1$ 58 59 /** The global help flag. */ 60 public static final String KEY_HELP = "help"; 61 /** The global verbose flag. */ 62 public static final String KEY_VERBOSE = "verbose"; 63 /** The global silent flag. */ 64 public static final String KEY_SILENT = "silent"; 65 66 /** Verb requested by the user. Null if none specified, which will be an error. */ 67 private String mVerbRequested; 68 /** Direct object requested by the user. Can be null. */ 69 private String mDirectObjectRequested; 70 71 /** 72 * Action definitions. 73 * <p/> 74 * This list serves two purposes: first it is used to know which verb/object 75 * actions are acceptable on the command-line; second it provides a summary 76 * for each action that is printed in the help. 77 * <p/> 78 * Each entry is a string array with: 79 * <ul> 80 * <li> the verb. 81 * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object). 82 * <li> a description. 83 * <li> an alternate form for the object (e.g. plural). 84 * </ul> 85 */ 86 private final String[][] mActions; 87 88 private static final int ACTION_VERB_INDEX = 0; 89 private static final int ACTION_OBJECT_INDEX = 1; 90 private static final int ACTION_DESC_INDEX = 2; 91 private static final int ACTION_ALT_OBJECT_INDEX = 3; 92 93 /** 94 * The map of all defined arguments. 95 * <p/> 96 * The key is a string "verb/directObject/longName". 97 */ 98 private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>(); 99 /** Logger */ 100 private final ILogger mLog; 101 102 /** 103 * Constructs a new command-line processor. 104 * 105 * @param logger An SDK logger object. Must not be null. 106 * @param actions The list of actions recognized on the command-line. 107 * See the javadoc of {@link #mActions} for more details. 108 * 109 * @see #mActions 110 */ CommandLineParser(ILogger logger, String[][] actions)111 public CommandLineParser(ILogger logger, String[][] actions) { 112 mLog = logger; 113 mActions = actions; 114 115 /* 116 * usage should fit in 80 columns, including the space to print the options: 117 * " -v --verbose 7890123456789012345678901234567890123456789012345678901234567890" 118 */ 119 120 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE, 121 "Verbose mode, shows errors, warnings and all messages.", 122 false); 123 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT, 124 "Silent mode, shows errors only.", 125 false); 126 define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP, 127 "Help on a specific command.", 128 false); 129 } 130 131 /** 132 * Indicates if this command-line can work when no verb is specified. 133 * The default is false, which generates an error when no verb/object is specified. 134 * Derived implementations can set this to true if they can deal with a lack 135 * of verb/action. 136 */ acceptLackOfVerb()137 public boolean acceptLackOfVerb() { 138 return false; 139 } 140 141 142 //------------------ 143 // Helpers to get flags values 144 145 /** Helper that returns true if --verbose was requested. */ isVerbose()146 public boolean isVerbose() { 147 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue(); 148 } 149 150 /** Helper that returns true if --silent was requested. */ isSilent()151 public boolean isSilent() { 152 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue(); 153 } 154 155 /** Helper that returns true if --help was requested. */ isHelpRequested()156 public boolean isHelpRequested() { 157 return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue(); 158 } 159 160 /** Returns the verb name from the command-line. Can be null. */ getVerb()161 public String getVerb() { 162 return mVerbRequested; 163 } 164 165 /** Returns the direct object name from the command-line. Can be null. */ getDirectObject()166 public String getDirectObject() { 167 return mDirectObjectRequested; 168 } 169 170 //------------------ 171 172 /** 173 * Raw access to parsed parameter values. 174 * <p/> 175 * The default is to scan all parameters. Parameters that have been explicitly set on the 176 * command line are returned first. Otherwise one with a non-null value is returned. 177 * <p/> 178 * Both a verb and a direct object filter can be specified. When they are non-null they limit 179 * the scope of the search. 180 * <p/> 181 * If nothing has been found, return the last default value seen matching the filter. 182 * 183 * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible 184 * verbs that match the direct object condition will be examined and the first 185 * value set will be used. 186 * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null, 187 * all possible direct objects that match the verb condition will be examined and 188 * the first value set will be used. 189 * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null. 190 * @return The current value object stored in the parameter, which depends on the argument mode. 191 */ getValue(String verb, String directObject, String longFlagName)192 public Object getValue(String verb, String directObject, String longFlagName) { 193 194 if (verb != null && directObject != null) { 195 String key = verb + '/' + directObject + '/' + longFlagName; 196 Arg arg = mArguments.get(key); 197 return arg.getCurrentValue(); 198 } 199 200 Object lastDefault = null; 201 for (Arg arg : mArguments.values()) { 202 if (arg.getLongArg().equals(longFlagName)) { 203 if (verb == null || arg.getVerb().equals(verb)) { 204 if (directObject == null || arg.getDirectObject().equals(directObject)) { 205 if (arg.isInCommandLine()) { 206 return arg.getCurrentValue(); 207 } 208 if (arg.getCurrentValue() != null) { 209 lastDefault = arg.getCurrentValue(); 210 } 211 } 212 } 213 } 214 } 215 216 return lastDefault; 217 } 218 219 /** 220 * Internal setter for raw parameter value. 221 * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. 222 * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. 223 * @param longFlagName The long flag name for the given action. 224 * @param value The new current value object stored in the parameter, which depends on the 225 * argument mode. 226 */ setValue(String verb, String directObject, String longFlagName, Object value)227 protected void setValue(String verb, String directObject, String longFlagName, Object value) { 228 String key = verb + '/' + directObject + '/' + longFlagName; 229 Arg arg = mArguments.get(key); 230 arg.setCurrentValue(value); 231 } 232 233 /** 234 * Parses the command-line arguments. 235 * <p/> 236 * This method will exit and not return if a parsing error arise. 237 * 238 * @param args The arguments typically received by a main method. 239 */ parseArgs(String[] args)240 public void parseArgs(String[] args) { 241 String errorMsg = null; 242 String verb = null; 243 String directObject = null; 244 245 try { 246 int n = args.length; 247 for (int i = 0; i < n; i++) { 248 Arg arg = null; 249 String a = args[i]; 250 if (a.startsWith("--")) { //$NON-NLS-1$ 251 arg = findLongArg(verb, directObject, a.substring(2)); 252 } else if (a.startsWith("-")) { //$NON-NLS-1$ 253 arg = findShortArg(verb, directObject, a.substring(1)); 254 } 255 256 // No matching argument name found 257 if (arg == null) { 258 // Does it looks like a dashed parameter? 259 if (a.startsWith("-")) { //$NON-NLS-1$ 260 if (verb == null || directObject == null) { 261 // It looks like a dashed parameter and we don't have a a verb/object 262 // set yet, the parameter was just given too early. 263 264 errorMsg = String.format( 265 "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?", 266 a); 267 return; 268 } else { 269 // It looks like a dashed parameter but it is unknown by this 270 // verb-object combination 271 272 errorMsg = String.format( 273 "Flag '%1$s' is not valid for '%2$s %3$s'.", 274 a, verb, directObject); 275 return; 276 } 277 } 278 279 if (verb == null) { 280 // Fill verb first. Find it. 281 for (String[] actionDesc : mActions) { 282 if (actionDesc[ACTION_VERB_INDEX].equals(a)) { 283 verb = a; 284 break; 285 } 286 } 287 288 // Error if it was not a valid verb 289 if (verb == null) { 290 errorMsg = String.format( 291 "Expected verb after global parameters but found '%1$s' instead.", 292 a); 293 return; 294 } 295 296 } else if (directObject == null) { 297 // Then fill the direct object. Find it. 298 for (String[] actionDesc : mActions) { 299 if (actionDesc[ACTION_VERB_INDEX].equals(verb)) { 300 if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) { 301 directObject = a; 302 break; 303 } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX && 304 actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) { 305 // if the alternate form exist and is used, we internally 306 // only memorize the default direct object form. 307 directObject = actionDesc[ACTION_OBJECT_INDEX]; 308 break; 309 } 310 } 311 } 312 313 // Error if it was not a valid object for that verb 314 if (directObject == null) { 315 errorMsg = String.format( 316 "Expected verb after global parameters but found '%1$s' instead.", 317 a); 318 return; 319 320 } 321 } else { 322 // The argument is not a dashed parameter and we already 323 // have a verb/object. Must be some extra unknown argument. 324 errorMsg = String.format( 325 "Argument '%1$s' is not recognized.", 326 a); 327 } 328 } else if (arg != null) { 329 // This argument was present on the command line 330 arg.setInCommandLine(true); 331 332 // Process keyword 333 Object error = null; 334 if (arg.getMode().needsExtra()) { 335 if (i+1 >= n) { 336 errorMsg = String.format("Missing argument for flag %1$s.", a); 337 return; 338 } 339 340 while (i+1 < n) { 341 String b = args[i+1]; 342 343 if (arg.getMode() != Mode.STRING_ARRAY) { 344 // We never accept something that looks like a valid argument 345 // unless we see -- first 346 Arg dummyArg = null; 347 if (b.startsWith("--")) { //$NON-NLS-1$ 348 dummyArg = findLongArg(verb, directObject, b.substring(2)); 349 } else if (b.startsWith("-")) { //$NON-NLS-1$ 350 dummyArg = findShortArg(verb, directObject, b.substring(1)); 351 } 352 if (dummyArg != null) { 353 errorMsg = String.format( 354 "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.", 355 a, b); 356 return; 357 } 358 } 359 360 error = arg.getMode().process(arg, b); 361 if (error == Accept.CONTINUE) { 362 i++; 363 } else if (error == Accept.ACCEPT_AND_STOP) { 364 i++; 365 break; 366 } else if (error == Accept.REJECT_AND_STOP) { 367 break; 368 } else if (error instanceof String) { 369 // We stop because of an error 370 break; 371 } 372 } 373 } else { 374 error = arg.getMode().process(arg, null); 375 376 if (isHelpRequested()) { 377 // The --help flag was requested. We'll continue the usual processing 378 // so that we can find the optional verb/object words. Those will be 379 // used to print specific help. 380 // Setting a non-null error message triggers printing the help, however 381 // there is no specific error to print. 382 errorMsg = ""; //$NON-NLS-1$ 383 } 384 } 385 386 if (error instanceof String) { 387 errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error); 388 return; 389 } 390 } 391 } 392 393 if (errorMsg == null) { 394 if (verb == null && !acceptLackOfVerb()) { 395 errorMsg = "Missing verb name."; 396 } else if (verb != null) { 397 if (directObject == null) { 398 // Make sure this verb has an optional direct object 399 for (String[] actionDesc : mActions) { 400 if (actionDesc[ACTION_VERB_INDEX].equals(verb) && 401 actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) { 402 directObject = NO_VERB_OBJECT; 403 break; 404 } 405 } 406 407 if (directObject == null) { 408 errorMsg = String.format("Missing object name for verb '%1$s'.", verb); 409 return; 410 } 411 } 412 413 // Validate that all mandatory arguments are non-null for this action 414 String missing = null; 415 boolean plural = false; 416 for (Entry<String, Arg> entry : mArguments.entrySet()) { 417 Arg arg = entry.getValue(); 418 if (arg.getVerb().equals(verb) && 419 arg.getDirectObject().equals(directObject)) { 420 if (arg.isMandatory() && arg.getCurrentValue() == null) { 421 if (missing == null) { 422 missing = "--" + arg.getLongArg(); //$NON-NLS-1$ 423 } else { 424 missing += ", --" + arg.getLongArg(); //$NON-NLS-1$ 425 plural = true; 426 } 427 } 428 } 429 } 430 431 if (missing != null) { 432 errorMsg = String.format( 433 "The %1$s %2$s must be defined for action '%3$s %4$s'", 434 plural ? "parameters" : "parameter", 435 missing, 436 verb, 437 directObject); 438 } 439 440 mVerbRequested = verb; 441 mDirectObjectRequested = directObject; 442 } 443 } 444 } finally { 445 if (errorMsg != null) { 446 printHelpAndExitForAction(verb, directObject, errorMsg); 447 } 448 } 449 } 450 451 /** 452 * Finds an {@link Arg} given an action name and a long flag name. 453 * @return The {@link Arg} found or null. 454 */ findLongArg(String verb, String directObject, String longName)455 protected Arg findLongArg(String verb, String directObject, String longName) { 456 if (verb == null) { 457 verb = GLOBAL_FLAG_VERB; 458 } 459 if (directObject == null) { 460 directObject = NO_VERB_OBJECT; 461 } 462 String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$ 463 return mArguments.get(key); 464 } 465 466 /** 467 * Finds an {@link Arg} given an action name and a short flag name. 468 * @return The {@link Arg} found or null. 469 */ findShortArg(String verb, String directObject, String shortName)470 protected Arg findShortArg(String verb, String directObject, String shortName) { 471 if (verb == null) { 472 verb = GLOBAL_FLAG_VERB; 473 } 474 if (directObject == null) { 475 directObject = NO_VERB_OBJECT; 476 } 477 478 for (Entry<String, Arg> entry : mArguments.entrySet()) { 479 Arg arg = entry.getValue(); 480 if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { 481 if (shortName.equals(arg.getShortArg())) { 482 return arg; 483 } 484 } 485 } 486 487 return null; 488 } 489 490 /** 491 * Prints the help/usage and exits. 492 * 493 * @param errorFormat Optional error message to print prior to usage using String.format 494 * @param args Arguments for String.format 495 */ printHelpAndExit(String errorFormat, Object... args)496 public void printHelpAndExit(String errorFormat, Object... args) { 497 printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args); 498 } 499 500 /** 501 * Prints the help/usage and exits. 502 * 503 * @param verb If null, displays help for all verbs. If not null, display help only 504 * for that specific verb. In all cases also displays general usage and action list. 505 * @param directObject If null, displays help for all verb objects. 506 * If not null, displays help only for that specific action 507 * In all cases also display general usage and action list. 508 * @param errorFormat Optional error message to print prior to usage using String.format 509 * @param args Arguments for String.format 510 */ printHelpAndExitForAction(String verb, String directObject, String errorFormat, Object... args)511 public void printHelpAndExitForAction(String verb, String directObject, 512 String errorFormat, Object... args) { 513 if (errorFormat != null && errorFormat.length() > 0) { 514 stderr(errorFormat, args); 515 } 516 517 /* 518 * usage should fit in 80 columns 519 * 12345678901234567890123456789012345678901234567890123456789012345678901234567890 520 */ 521 stdout("\n" + 522 "Usage:\n" + 523 " android [global options] %s [action options]\n" + 524 "\n" + 525 "Global options:", 526 verb == null ? "action" : 527 verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$ 528 listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT); 529 530 if (verb == null || directObject == null) { 531 stdout("\nValid actions are composed of a verb and an optional direct object:"); 532 for (String[] action : mActions) { 533 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) { 534 stdout("- %1$6s %2$-13s: %3$s", 535 action[ACTION_VERB_INDEX], 536 action[ACTION_OBJECT_INDEX], 537 action[ACTION_DESC_INDEX]); 538 } 539 } 540 } 541 542 // Only print details if a verb/object is requested 543 if (verb != null) { 544 for (String[] action : mActions) { 545 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) { 546 if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) { 547 stdout("\nAction \"%1$s %2$s\":", 548 action[ACTION_VERB_INDEX], 549 action[ACTION_OBJECT_INDEX]); 550 stdout(" %1$s", action[ACTION_DESC_INDEX]); 551 stdout("Options:"); 552 listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]); 553 } 554 } 555 } 556 } 557 558 exit(); 559 } 560 561 /** 562 * Internal helper to print all the option flags for a given action name. 563 */ listOptions(String verb, String directObject)564 protected void listOptions(String verb, String directObject) { 565 int numOptions = 0; 566 int longArgLen = 8; 567 568 for (Entry<String, Arg> entry : mArguments.entrySet()) { 569 Arg arg = entry.getValue(); 570 if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { 571 int n = arg.getLongArg().length(); 572 if (n > longArgLen) { 573 longArgLen = n; 574 } 575 } 576 } 577 578 for (Entry<String, Arg> entry : mArguments.entrySet()) { 579 Arg arg = entry.getValue(); 580 if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { 581 582 String value = ""; //$NON-NLS-1$ 583 String required = ""; //$NON-NLS-1$ 584 if (arg.isMandatory()) { 585 required = " [required]"; 586 587 } else { 588 if (arg.getDefaultValue() instanceof String[]) { 589 for (String v : (String[]) arg.getDefaultValue()) { 590 if (value.length() > 0) { 591 value += ", "; 592 } 593 value += v; 594 } 595 } else if (arg.getDefaultValue() != null) { 596 Object v = arg.getDefaultValue(); 597 if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) { 598 value = v.toString(); 599 } 600 } 601 if (value.length() > 0) { 602 value = " [Default: " + value + "]"; 603 } 604 } 605 606 // Java doesn't support * for printf variable width, so we'll insert the long arg 607 // width "manually" in the printf format string. 608 String longArgWidth = Integer.toString(longArgLen + 2); 609 610 // Print a line in the form " -1_letter_arg --long_arg description" 611 // where either the 1-letter arg or the long arg are optional. 612 String output = String.format( 613 " %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$ 614 arg.getShortArg().length() > 0 ? 615 "-" + arg.getShortArg() : //$NON-NLS-1$ 616 "", //$NON-NLS-1$ 617 arg.getLongArg().length() > 0 ? 618 "--" + arg.getLongArg() : //$NON-NLS-1$ 619 "", //$NON-NLS-1$ 620 arg.getDescription(), 621 value, 622 required); 623 stdout(output); 624 numOptions++; 625 } 626 } 627 628 if (numOptions == 0) { 629 stdout(" No options"); 630 } 631 } 632 633 //---- 634 635 private static enum Accept { 636 CONTINUE, 637 ACCEPT_AND_STOP, 638 REJECT_AND_STOP, 639 } 640 641 /** 642 * The mode of an argument specifies the type of variable it represents, 643 * whether an extra parameter is required after the flag and how to parse it. 644 */ 645 public static enum Mode { 646 /** Argument value is a Boolean. Default value is a Boolean. */ 647 BOOLEAN { 648 @Override needsExtra()649 public boolean needsExtra() { 650 return false; 651 } 652 @Override process(Arg arg, String extra)653 public Object process(Arg arg, String extra) { 654 // Toggle the current value 655 arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue()); 656 return Accept.ACCEPT_AND_STOP; 657 } 658 }, 659 660 /** Argument value is an Integer. Default value is an Integer. */ 661 INTEGER { 662 @Override needsExtra()663 public boolean needsExtra() { 664 return true; 665 } 666 @Override process(Arg arg, String extra)667 public Object process(Arg arg, String extra) { 668 try { 669 arg.setCurrentValue(Integer.parseInt(extra)); 670 return null; 671 } catch (NumberFormatException e) { 672 return String.format("Failed to parse '%1$s' as an integer: %2$s", extra, 673 e.getMessage()); 674 } 675 } 676 }, 677 678 /** Argument value is a String. Default value is a String[]. */ 679 ENUM { 680 @Override needsExtra()681 public boolean needsExtra() { 682 return true; 683 } 684 @Override process(Arg arg, String extra)685 public Object process(Arg arg, String extra) { 686 StringBuilder desc = new StringBuilder(); 687 String[] values = (String[]) arg.getDefaultValue(); 688 for (String value : values) { 689 if (value.equals(extra)) { 690 arg.setCurrentValue(extra); 691 return Accept.ACCEPT_AND_STOP; 692 } 693 694 if (desc.length() != 0) { 695 desc.append(", "); 696 } 697 desc.append(value); 698 } 699 700 return String.format("'%1$s' is not one of %2$s", extra, desc.toString()); 701 } 702 }, 703 704 /** Argument value is a String. Default value is a null. */ 705 STRING { 706 @Override needsExtra()707 public boolean needsExtra() { 708 return true; 709 } 710 @Override process(Arg arg, String extra)711 public Object process(Arg arg, String extra) { 712 arg.setCurrentValue(extra); 713 return Accept.ACCEPT_AND_STOP; 714 } 715 }, 716 717 /** Argument value is a {@link List}<String>. Default value is an empty list. */ 718 STRING_ARRAY { 719 @Override needsExtra()720 public boolean needsExtra() { 721 return true; 722 } 723 @Override process(Arg arg, String extra)724 public Object process(Arg arg, String extra) { 725 // For simplification, a string array doesn't accept something that 726 // starts with a dash unless a pure -- was seen before. 727 if (extra != null) { 728 Object v = arg.getCurrentValue(); 729 if (v == null) { 730 ArrayList<String> a = new ArrayList<String>(); 731 arg.setCurrentValue(a); 732 v = a; 733 } 734 if (v instanceof List<?>) { 735 @SuppressWarnings("unchecked") List<String> a = (List<String>) v; 736 737 if (extra.equals("--") || 738 !extra.startsWith("-") || 739 (extra.startsWith("-") && a.contains("--"))) { 740 a.add(extra); 741 return Accept.CONTINUE; 742 } else if (a.isEmpty()) { 743 return "No values provided"; 744 } 745 } 746 } 747 return Accept.REJECT_AND_STOP; 748 } 749 }; 750 751 /** 752 * Returns true if this mode requires an extra parameter. 753 */ needsExtra()754 public abstract boolean needsExtra(); 755 756 /** 757 * Processes the flag for this argument. 758 * 759 * @param arg The argument being processed. 760 * @param extra The extra parameter. Null if {@link #needsExtra()} returned false. 761 * @return {@link Accept#CONTINUE} if this argument can use multiple values and 762 * wishes to receive more. 763 * Or {@link Accept#ACCEPT_AND_STOP} if this was the last value accepted by the argument. 764 * Or {@link Accept#REJECT_AND_STOP} if this was value was reject and the argument 765 * stops accepting new values with no error. 766 * Or a string in case of error. 767 * Never returns null. 768 */ process(Arg arg, String extra)769 public abstract Object process(Arg arg, String extra); 770 } 771 772 /** 773 * An argument accepted by the command-line, also called "a flag". 774 * Arguments must have a short version (one letter), a long version name and a description. 775 * They can have a default value, or it can be null. 776 * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String 777 * or a String array (in which case the first item is the current by default.) 778 */ 779 static class Arg { 780 /** Verb for that argument. Never null. */ 781 private final String mVerb; 782 /** Direct Object for that argument. Never null, but can be empty string. */ 783 private final String mDirectObject; 784 /** The 1-letter short name of the argument, e.g. -v. */ 785 private final String mShortName; 786 /** The long name of the argument, e.g. --verbose. */ 787 private final String mLongName; 788 /** A description. Never null. */ 789 private final String mDescription; 790 /** A default value. Can be null. */ 791 private final Object mDefaultValue; 792 /** The argument mode (type + process method). Never null. */ 793 private final Mode mMode; 794 /** True if this argument is mandatory for this verb/directobject. */ 795 private final boolean mMandatory; 796 /** Current value. Initially set to the default value. */ 797 private Object mCurrentValue; 798 /** True if the argument has been used on the command line. */ 799 private boolean mInCommandLine; 800 801 /** 802 * Creates a new argument flag description. 803 * 804 * @param mode The {@link Mode} for the argument. 805 * @param mandatory True if this argument is mandatory for this action. 806 * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}. 807 * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}. 808 * @param shortName The one-letter short argument name. Can be empty but not null. 809 * @param longName The long argument name. Can be empty but not null. 810 * @param description The description. Cannot be null. 811 * @param defaultValue The default value (or values), which depends on the selected 812 * {@link Mode}. Can be null. 813 */ Arg(Mode mode, boolean mandatory, @NonNull String verb, @NonNull String directObject, @NonNull String shortName, @NonNull String longName, @NonNull String description, @Nullable Object defaultValue)814 public Arg(Mode mode, 815 boolean mandatory, 816 @NonNull String verb, 817 @NonNull String directObject, 818 @NonNull String shortName, 819 @NonNull String longName, 820 @NonNull String description, 821 @Nullable Object defaultValue) { 822 mMode = mode; 823 mMandatory = mandatory; 824 mVerb = verb; 825 mDirectObject = directObject; 826 mShortName = shortName; 827 mLongName = longName; 828 mDescription = description; 829 mDefaultValue = defaultValue; 830 mInCommandLine = false; 831 if (defaultValue instanceof String[]) { 832 mCurrentValue = ((String[])defaultValue)[0]; 833 } else { 834 mCurrentValue = mDefaultValue; 835 } 836 } 837 838 /** Return true if this argument is mandatory for this verb/directobject. */ isMandatory()839 public boolean isMandatory() { 840 return mMandatory; 841 } 842 843 /** Returns the 1-letter short name of the argument, e.g. -v. */ getShortArg()844 public String getShortArg() { 845 return mShortName; 846 } 847 848 /** Returns the long name of the argument, e.g. --verbose. */ getLongArg()849 public String getLongArg() { 850 return mLongName; 851 } 852 853 /** Returns the description. Never null. */ getDescription()854 public String getDescription() { 855 return mDescription; 856 } 857 858 /** Returns the verb for that argument. Never null. */ getVerb()859 public String getVerb() { 860 return mVerb; 861 } 862 863 /** Returns the direct Object for that argument. Never null, but can be empty string. */ getDirectObject()864 public String getDirectObject() { 865 return mDirectObject; 866 } 867 868 /** Returns the default value. Can be null. */ getDefaultValue()869 public Object getDefaultValue() { 870 return mDefaultValue; 871 } 872 873 /** Returns the current value. Initially set to the default value. Can be null. */ getCurrentValue()874 public Object getCurrentValue() { 875 return mCurrentValue; 876 } 877 878 /** Sets the current value. Can be null. */ setCurrentValue(Object currentValue)879 public void setCurrentValue(Object currentValue) { 880 mCurrentValue = currentValue; 881 } 882 883 /** Returns the argument mode (type + process method). Never null. */ getMode()884 public Mode getMode() { 885 return mMode; 886 } 887 888 /** Returns true if the argument has been used on the command line. */ isInCommandLine()889 public boolean isInCommandLine() { 890 return mInCommandLine; 891 } 892 893 /** Sets if the argument has been used on the command line. */ setInCommandLine(boolean inCommandLine)894 public void setInCommandLine(boolean inCommandLine) { 895 mInCommandLine = inCommandLine; 896 } 897 } 898 899 /** 900 * Internal helper to define a new argument for a give action. 901 * 902 * @param mode The {@link Mode} for the argument. 903 * @param mandatory The argument is required (never if {@link Mode#BOOLEAN}) 904 * @param verb The verb name. Never null. Can be {@link CommandLineParser#GLOBAL_FLAG_VERB}. 905 * @param directObject The action name. Can be {@link CommandLineParser#NO_VERB_OBJECT}. 906 * @param shortName The one-letter short argument name. Can be empty but not null. 907 * @param longName The long argument name. Can be empty but not null. 908 * @param description The description. Cannot be null. 909 * @param defaultValue The default value (or values), which depends on the selected 910 * {@link Mode}. 911 */ define(Mode mode, boolean mandatory, @NonNull String verb, @NonNull String directObject, @NonNull String shortName, @NonNull String longName, @NonNull String description, @Nullable Object defaultValue)912 protected void define(Mode mode, 913 boolean mandatory, 914 @NonNull String verb, 915 @NonNull String directObject, 916 @NonNull String shortName, 917 @NonNull String longName, 918 @NonNull String description, 919 @Nullable Object defaultValue) { 920 assert verb != null; 921 assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory 922 923 // We should always have at least a short or long name, ideally both but never none. 924 assert shortName != null; 925 assert longName != null; 926 assert shortName.length() > 0 || longName.length() > 0; 927 928 if (directObject == null) { 929 directObject = NO_VERB_OBJECT; 930 } 931 932 String key = verb + '/' + directObject + '/' + longName; 933 mArguments.put(key, new Arg(mode, mandatory, 934 verb, directObject, shortName, longName, description, defaultValue)); 935 } 936 937 /** 938 * Exits in case of error. 939 * This is protected so that it can be overridden in unit tests. 940 */ exit()941 protected void exit() { 942 System.exit(1); 943 } 944 945 /** 946 * Prints a line to stdout. 947 * This is protected so that it can be overridden in unit tests. 948 * 949 * @param format The string to be formatted. Cannot be null. 950 * @param args Format arguments. 951 */ stdout(String format, Object...args)952 protected void stdout(String format, Object...args) { 953 String output = String.format(format, args); 954 output = LineUtil.reflowLine(output); 955 mLog.info("%s\n", output); //$NON-NLS-1$ 956 } 957 958 /** 959 * Prints a line to stderr. 960 * This is protected so that it can be overridden in unit tests. 961 * 962 * @param format The string to be formatted. Cannot be null. 963 * @param args Format arguments. 964 */ stderr(String format, Object...args)965 protected void stderr(String format, Object...args) { 966 mLog.error(null, format, args); 967 } 968 } 969