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