1 // Copyright 2014 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.devtools.common.options; 16 17 import com.google.common.base.Function; 18 import com.google.common.base.Functions; 19 import com.google.common.base.Joiner; 20 import com.google.common.base.Preconditions; 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.collect.ListMultimap; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Maps; 25 import com.google.common.escape.Escaper; 26 import java.lang.reflect.Field; 27 import java.nio.file.FileSystem; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.List; 34 import java.util.Map; 35 36 /** 37 * A parser for options. Typical use case in a main method: 38 * 39 * <pre> 40 * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class); 41 * parser.parseAndExitUponError(args); 42 * FooOptions foo = parser.getOptions(FooOptions.class); 43 * BarOptions bar = parser.getOptions(BarOptions.class); 44 * List<String> otherArguments = parser.getResidue(); 45 * </pre> 46 * 47 * <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase, 48 * that contain fields annotated with @Option(...). 49 * 50 * <p>Alternatively, rather than calling {@link #parseAndExitUponError(OptionPriority, String, 51 * String[])}, client code may call {@link #parse(OptionPriority,String,List)}, and handle parser 52 * exceptions usage messages themselves. 53 * 54 * <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz' 55 * and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz' 56 * in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch 57 * options between void/boolean/tristate and everything else without breaking backwards 58 * compatibility. 59 * 60 * @see Options a simpler class which you can use if you only have one options specification class 61 */ 62 public class OptionsParser implements OptionsProvider { 63 64 /** 65 * A cache for the parsed options data. Both keys and values are immutable, so 66 * this is always safe. Only access this field through the {@link 67 * #getOptionsData} method for thread-safety! The cache is very unlikely to 68 * grow to a significant amount of memory, because there's only a fixed set of 69 * options classes on the classpath. 70 */ 71 private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData = 72 Maps.newHashMap(); 73 74 /** 75 * Returns {@link OpaqueOptionsData} suitable for passing along to 76 * {@link #newOptionsParser(OpaqueOptionsData optionsData)}. 77 * 78 * This is useful when you want to do the work of analyzing the given {@code optionsClasses} 79 * exactly once, but you want to parse lots of different lists of strings (and thus need to 80 * construct lots of different {@link OptionsParser} instances). 81 */ getOptionsData( ImmutableList<Class<? extends OptionsBase>> optionsClasses)82 public static OpaqueOptionsData getOptionsData( 83 ImmutableList<Class<? extends OptionsBase>> optionsClasses) { 84 return getOptionsDataInternal(optionsClasses); 85 } 86 getOptionsDataInternal( ImmutableList<Class<? extends OptionsBase>> optionsClasses)87 private static synchronized OptionsData getOptionsDataInternal( 88 ImmutableList<Class<? extends OptionsBase>> optionsClasses) { 89 OptionsData result = optionsData.get(optionsClasses); 90 if (result == null) { 91 result = OptionsData.from(optionsClasses); 92 optionsData.put(optionsClasses, result); 93 } 94 return result; 95 } 96 97 /** 98 * Returns all the annotated fields for the given class, including inherited 99 * ones. 100 */ getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass)101 static Collection<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) { 102 OptionsData data = getOptionsDataInternal( 103 ImmutableList.<Class<? extends OptionsBase>>of(optionsClass)); 104 return data.getFieldsForClass(optionsClass); 105 } 106 107 /** 108 * @see #newOptionsParser(Iterable) 109 */ newOptionsParser(Class<? extends OptionsBase> class1)110 public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1) { 111 return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1)); 112 } 113 114 /** 115 * @see #newOptionsParser(Iterable) 116 */ newOptionsParser(Class<? extends OptionsBase> class1, Class<? extends OptionsBase> class2)117 public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1, 118 Class<? extends OptionsBase> class2) { 119 return newOptionsParser(ImmutableList.of(class1, class2)); 120 } 121 122 /** 123 * Create a new {@link OptionsParser}. 124 */ newOptionsParser( Iterable<? extends Class<? extends OptionsBase>> optionsClasses)125 public static OptionsParser newOptionsParser( 126 Iterable<? extends Class<? extends OptionsBase>> optionsClasses) { 127 return newOptionsParser( 128 getOptionsDataInternal(ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsClasses))); 129 } 130 131 /** 132 * Create a new {@link OptionsParser}, using {@link OpaqueOptionsData} previously returned from 133 * {@link #getOptionsData}. 134 */ newOptionsParser(OpaqueOptionsData optionsData)135 public static OptionsParser newOptionsParser(OpaqueOptionsData optionsData) { 136 return new OptionsParser((OptionsData) optionsData); 137 } 138 139 private final OptionsParserImpl impl; 140 private final List<String> residue = new ArrayList<String>(); 141 private boolean allowResidue = true; 142 OptionsParser(OptionsData optionsData)143 OptionsParser(OptionsData optionsData) { 144 impl = new OptionsParserImpl(optionsData); 145 } 146 147 /** 148 * Indicates whether or not the parser will allow a non-empty residue; that 149 * is, iff this value is true then a call to one of the {@code parse} 150 * methods will throw {@link OptionsParsingException} unless 151 * {@link #getResidue()} is empty after parsing. 152 */ setAllowResidue(boolean allowResidue)153 public void setAllowResidue(boolean allowResidue) { 154 this.allowResidue = allowResidue; 155 } 156 157 /** 158 * Indicates whether or not the parser will allow long options with a 159 * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example. 160 */ setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions)161 public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) { 162 this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions); 163 } 164 165 /** Enables the Parser to handle params files loacted insinde the provided {@link FileSystem}. */ enableParamsFileSupport(FileSystem fs)166 public void enableParamsFileSupport(FileSystem fs) { 167 this.impl.setArgsPreProcessor(new ParamsFilePreProcessor(fs)); 168 } 169 parseAndExitUponError(String[] args)170 public void parseAndExitUponError(String[] args) { 171 parseAndExitUponError(OptionPriority.COMMAND_LINE, "unknown", args); 172 } 173 174 /** 175 * A convenience function for use in main methods. Parses the command line 176 * parameters, and exits upon error. Also, prints out the usage message 177 * if "--help" appears anywhere within {@code args}. 178 */ parseAndExitUponError(OptionPriority priority, String source, String[] args)179 public void parseAndExitUponError(OptionPriority priority, String source, String[] args) { 180 for (String arg : args) { 181 if (arg.equals("--help")) { 182 System.out.println(describeOptions(Collections.<String, String>emptyMap(), 183 HelpVerbosity.LONG)); 184 System.exit(0); 185 } 186 } 187 try { 188 parse(priority, source, Arrays.asList(args)); 189 } catch (OptionsParsingException e) { 190 System.err.println("Error parsing command line: " + e.getMessage()); 191 System.err.println("Try --help."); 192 System.exit(2); 193 } 194 } 195 196 /** 197 * The metadata about an option. 198 */ 199 public static final class OptionDescription { 200 201 private final String name; 202 private final Object defaultValue; 203 private final Converter<?> converter; 204 private final boolean allowMultiple; 205 OptionDescription(String name, Object defaultValue, Converter<?> converter, boolean allowMultiple)206 public OptionDescription(String name, Object defaultValue, Converter<?> converter, 207 boolean allowMultiple) { 208 this.name = name; 209 this.defaultValue = defaultValue; 210 this.converter = converter; 211 this.allowMultiple = allowMultiple; 212 } 213 getName()214 public String getName() { 215 return name; 216 } 217 getDefaultValue()218 public Object getDefaultValue() { 219 return defaultValue; 220 } 221 getConverter()222 public Converter<?> getConverter() { 223 return converter; 224 } 225 getAllowMultiple()226 public boolean getAllowMultiple() { 227 return allowMultiple; 228 } 229 } 230 231 /** 232 * The name and value of an option with additional metadata describing its 233 * priority, source, whether it was set via an implicit dependency, and if so, 234 * by which other option. 235 */ 236 public static class OptionValueDescription { 237 private final String name; 238 private final Object value; 239 private final OptionPriority priority; 240 private final String source; 241 private final String implicitDependant; 242 private final String expandedFrom; 243 private final boolean allowMultiple; 244 OptionValueDescription( String name, Object value, OptionPriority priority, String source, String implicitDependant, String expandedFrom, boolean allowMultiple)245 public OptionValueDescription( 246 String name, 247 Object value, 248 OptionPriority priority, 249 String source, 250 String implicitDependant, 251 String expandedFrom, 252 boolean allowMultiple) { 253 this.name = name; 254 this.value = value; 255 this.priority = priority; 256 this.source = source; 257 this.implicitDependant = implicitDependant; 258 this.expandedFrom = expandedFrom; 259 this.allowMultiple = allowMultiple; 260 } 261 getName()262 public String getName() { 263 return name; 264 } 265 266 // Need to suppress unchecked warnings, because the "multiple occurrence" 267 // options use unchecked ListMultimaps due to limitations of Java generics. 268 @SuppressWarnings({"unchecked", "rawtypes"}) getValue()269 public Object getValue() { 270 if (allowMultiple) { 271 // Sort the results by option priority and return them in a new list. 272 // The generic type of the list is not known at runtime, so we can't 273 // use it here. It was already checked in the constructor, so this is 274 // type-safe. 275 List result = Lists.newArrayList(); 276 ListMultimap realValue = (ListMultimap) value; 277 for (OptionPriority priority : OptionPriority.values()) { 278 // If there is no mapping for this key, this check avoids object creation (because 279 // ListMultimap has to return a new object on get) and also an unnecessary addAll call. 280 if (realValue.containsKey(priority)) { 281 result.addAll(realValue.get(priority)); 282 } 283 } 284 return result; 285 } 286 return value; 287 } 288 /** 289 * @return the priority of the thing that set this value for this flag 290 */ getPriority()291 public OptionPriority getPriority() { 292 return priority; 293 } 294 295 /** 296 * @return the thing that set this value for this flag 297 */ getSource()298 public String getSource() { 299 return source; 300 } 301 getImplicitDependant()302 public String getImplicitDependant() { 303 return implicitDependant; 304 } 305 isImplicitDependency()306 public boolean isImplicitDependency() { 307 return implicitDependant != null; 308 } 309 getExpansionParent()310 public String getExpansionParent() { 311 return expandedFrom; 312 } 313 isExpansion()314 public boolean isExpansion() { 315 return expandedFrom != null; 316 } 317 318 @Override toString()319 public String toString() { 320 StringBuilder result = new StringBuilder(); 321 result.append("option '").append(name).append("' "); 322 result.append("set to '").append(value).append("' "); 323 result.append("with priority ").append(priority); 324 if (source != null) { 325 result.append(" and source '").append(source).append("'"); 326 } 327 if (implicitDependant != null) { 328 result.append(" implicitly by "); 329 } 330 return result.toString(); 331 } 332 333 // Need to suppress unchecked warnings, because the "multiple occurrence" 334 // options use unchecked ListMultimaps due to limitations of Java generics. 335 @SuppressWarnings({"unchecked", "rawtypes"}) addValue(OptionPriority addedPriority, Object addedValue)336 void addValue(OptionPriority addedPriority, Object addedValue) { 337 Preconditions.checkState(allowMultiple); 338 ListMultimap optionValueList = (ListMultimap) value; 339 if (addedValue instanceof List<?>) { 340 optionValueList.putAll(addedPriority, (List<?>) addedValue); 341 } else { 342 optionValueList.put(addedPriority, addedValue); 343 } 344 } 345 } 346 347 /** 348 * The name and unparsed value of an option with additional metadata describing its 349 * priority, source, whether it was set via an implicit dependency, and if so, 350 * by which other option. 351 * 352 * <p>Note that the unparsed value and the source parameters can both be null. 353 */ 354 public static class UnparsedOptionValueDescription { 355 private final String name; 356 private final Field field; 357 private final String unparsedValue; 358 private final OptionPriority priority; 359 private final String source; 360 private final boolean explicit; 361 UnparsedOptionValueDescription(String name, Field field, String unparsedValue, OptionPriority priority, String source, boolean explicit)362 public UnparsedOptionValueDescription(String name, Field field, String unparsedValue, 363 OptionPriority priority, String source, boolean explicit) { 364 this.name = name; 365 this.field = field; 366 this.unparsedValue = unparsedValue; 367 this.priority = priority; 368 this.source = source; 369 this.explicit = explicit; 370 } 371 getName()372 public String getName() { 373 return name; 374 } 375 getField()376 Field getField() { 377 return field; 378 } 379 isBooleanOption()380 public boolean isBooleanOption() { 381 return field.getType().equals(boolean.class); 382 } 383 documentationLevel()384 private DocumentationLevel documentationLevel() { 385 Option option = field.getAnnotation(Option.class); 386 return OptionsParser.documentationLevel(option.category()); 387 } 388 isDocumented()389 public boolean isDocumented() { 390 return documentationLevel() == DocumentationLevel.DOCUMENTED; 391 } 392 isHidden()393 public boolean isHidden() { 394 return documentationLevel() == DocumentationLevel.HIDDEN 395 || documentationLevel() == DocumentationLevel.INTERNAL; 396 } 397 isExpansion()398 boolean isExpansion() { 399 Option option = field.getAnnotation(Option.class); 400 return (option.expansion().length > 0 401 || OptionsData.usesExpansionFunction(option)); 402 } 403 isImplicitRequirement()404 boolean isImplicitRequirement() { 405 Option option = field.getAnnotation(Option.class); 406 return option.implicitRequirements().length > 0; 407 } 408 allowMultiple()409 boolean allowMultiple() { 410 Option option = field.getAnnotation(Option.class); 411 return option.allowMultiple(); 412 } 413 getUnparsedValue()414 public String getUnparsedValue() { 415 return unparsedValue; 416 } 417 getPriority()418 OptionPriority getPriority() { 419 return priority; 420 } 421 getSource()422 public String getSource() { 423 return source; 424 } 425 isExplicit()426 public boolean isExplicit() { 427 return explicit; 428 } 429 430 @Override toString()431 public String toString() { 432 StringBuilder result = new StringBuilder(); 433 result.append("option '").append(name).append("' "); 434 result.append("set to '").append(unparsedValue).append("' "); 435 result.append("with priority ").append(priority); 436 if (source != null) { 437 result.append(" and source '").append(source).append("'"); 438 } 439 return result.toString(); 440 } 441 } 442 443 /** 444 * The verbosity with which option help messages are displayed: short (just 445 * the name), medium (name, type, default, abbreviation), and long (full 446 * description). 447 */ 448 public enum HelpVerbosity { LONG, MEDIUM, SHORT } 449 450 /** 451 * The level of documentation. Only documented options are output as part of 452 * the help. 453 * 454 * <p>We use 'hidden' so that options that form the protocol between the 455 * client and the server are not logged. 456 * 457 * <p>Options which are 'internal' are not recognized by the parser at all. 458 */ 459 enum DocumentationLevel { 460 DOCUMENTED, UNDOCUMENTED, HIDDEN, INTERNAL 461 } 462 463 /** 464 * Returns a description of all the options this parser can digest. In addition to {@link Option} 465 * annotations, this method also interprets {@link OptionsUsage} annotations which give an 466 * intuitive short description for the options. Options of the same category (see {@link 467 * Option#category}) will be grouped together. 468 * 469 * @param categoryDescriptions a mapping from category names to category descriptions. 470 * Descriptions are optional; if omitted, a string based on the category name will be used. 471 * @param helpVerbosity if {@code long}, the options will be described verbosely, including their 472 * types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if 473 * {@code short}, the options are just enumerated. 474 */ describeOptions( Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity)475 public String describeOptions( 476 Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) { 477 StringBuilder desc = new StringBuilder(); 478 if (!impl.getOptionsClasses().isEmpty()) { 479 List<Field> allFields = Lists.newArrayList(); 480 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 481 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 482 } 483 Collections.sort(allFields, OptionsUsage.BY_CATEGORY); 484 String prevCategory = null; 485 486 for (Field optionField : allFields) { 487 String category = optionField.getAnnotation(Option.class).category(); 488 if (!category.equals(prevCategory)) { 489 prevCategory = category; 490 String description = categoryDescriptions.get(category); 491 if (description == null) { 492 description = "Options category '" + category + "'"; 493 } 494 if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) { 495 desc.append("\n").append(description).append(":\n"); 496 } 497 } 498 499 if (documentationLevel(prevCategory) == DocumentationLevel.DOCUMENTED) { 500 OptionsUsage.getUsage(optionField, desc, helpVerbosity, impl.getOptionsData()); 501 } 502 } 503 } 504 return desc.toString().trim(); 505 } 506 507 /** 508 * Returns a description of all the options this parser can digest. 509 * In addition to {@link Option} annotations, this method also 510 * interprets {@link OptionsUsage} annotations which give an intuitive short 511 * description for the options. 512 * 513 * @param categoryDescriptions a mapping from category names to category 514 * descriptions. Options of the same category (see {@link 515 * Option#category}) will be grouped together, preceded by the description 516 * of the category. 517 */ describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper)518 public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) { 519 StringBuilder desc = new StringBuilder(); 520 if (!impl.getOptionsClasses().isEmpty()) { 521 List<Field> allFields = Lists.newArrayList(); 522 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 523 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 524 } 525 Collections.sort(allFields, OptionsUsage.BY_CATEGORY); 526 String prevCategory = null; 527 528 for (Field optionField : allFields) { 529 String category = optionField.getAnnotation(Option.class).category(); 530 DocumentationLevel level = documentationLevel(category); 531 if (!category.equals(prevCategory) && level == DocumentationLevel.DOCUMENTED) { 532 String description = categoryDescriptions.get(category); 533 if (description == null) { 534 description = "Options category '" + category + "'"; 535 } 536 if (prevCategory != null) { 537 desc.append("</dl>\n\n"); 538 } 539 desc.append(escaper.escape(description)).append(":\n"); 540 desc.append("<dl>"); 541 prevCategory = category; 542 } 543 544 if (level == DocumentationLevel.DOCUMENTED) { 545 OptionsUsage.getUsageHtml(optionField, desc, escaper, impl.getOptionsData()); 546 } 547 } 548 desc.append("</dl>\n"); 549 } 550 return desc.toString(); 551 } 552 553 /** 554 * Returns a string listing the possible flag completion for this command along with the command 555 * completion if any. See {@link OptionsUsage#getCompletion(Field, StringBuilder)} for more 556 * details on the format for the flag completion. 557 */ getOptionsCompletion()558 public String getOptionsCompletion() { 559 StringBuilder desc = new StringBuilder(); 560 561 // List all options 562 List<Field> allFields = Lists.newArrayList(); 563 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 564 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 565 } 566 // Sort field for deterministic ordering 567 Collections.sort(allFields, new Comparator<Field>() { 568 @Override 569 public int compare(Field f1, Field f2) { 570 String name1 = f1.getAnnotation(Option.class).name(); 571 String name2 = f2.getAnnotation(Option.class).name(); 572 return name1.compareTo(name2); 573 } 574 }); 575 for (Field optionField : allFields) { 576 String category = optionField.getAnnotation(Option.class).category(); 577 if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) { 578 OptionsUsage.getCompletion(optionField, desc); 579 } 580 } 581 582 return desc.toString(); 583 } 584 585 /** 586 * Returns a description of the option. 587 * 588 * @return The {@link OptionValueDescription} for the option, or null if there is no option by 589 * the given name. 590 */ getOptionDescription(String name)591 public OptionDescription getOptionDescription(String name) { 592 return impl.getOptionDescription(name); 593 } 594 595 /** 596 * Returns a description of the option value set by the last previous call to 597 * {@link #parse(OptionPriority, String, List)} that successfully set the given 598 * option. If the option is of type {@link List}, the description will 599 * correspond to any one of the calls, but not necessarily the last. 600 * 601 * @return The {@link OptionValueDescription} for the option, or null if the value has not been 602 * set. 603 * @throws IllegalArgumentException if there is no option by the given name. 604 */ getOptionValueDescription(String name)605 public OptionValueDescription getOptionValueDescription(String name) { 606 return impl.getOptionValueDescription(name); 607 } 608 documentationLevel(String category)609 static DocumentationLevel documentationLevel(String category) { 610 if ("undocumented".equals(category)) { 611 return DocumentationLevel.UNDOCUMENTED; 612 } else if ("hidden".equals(category)) { 613 return DocumentationLevel.HIDDEN; 614 } else if ("internal".equals(category)) { 615 return DocumentationLevel.INTERNAL; 616 } else { 617 return DocumentationLevel.DOCUMENTED; 618 } 619 } 620 621 /** 622 * A convenience method, equivalent to 623 * {@code parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args))}. 624 */ parse(String... args)625 public void parse(String... args) throws OptionsParsingException { 626 parse(OptionPriority.COMMAND_LINE, (String) null, Arrays.asList(args)); 627 } 628 629 /** 630 * A convenience method, equivalent to 631 * {@code parse(OptionPriority.COMMAND_LINE, null, args)}. 632 */ parse(List<String> args)633 public void parse(List<String> args) throws OptionsParsingException { 634 parse(OptionPriority.COMMAND_LINE, (String) null, args); 635 } 636 637 /** 638 * Parses {@code args}, using the classes registered with this parser. 639 * {@link #getOptions(Class)} and {@link #getResidue()} return the results. 640 * May be called multiple times; later options override existing ones if they 641 * have equal or higher priority. The source of options is a free-form string 642 * that can be used for debugging. Strings that cannot be parsed as options 643 * accumulates as residue, if this parser allows it. 644 * 645 * @see OptionPriority 646 */ parse(OptionPriority priority, String source, List<String> args)647 public void parse(OptionPriority priority, String source, 648 List<String> args) throws OptionsParsingException { 649 parseWithSourceFunction(priority, Functions.constant(source), args); 650 } 651 652 /** 653 * Parses {@code args}, using the classes registered with this parser. 654 * {@link #getOptions(Class)} and {@link #getResidue()} return the results. May be called 655 * multiple times; later options override existing ones if they have equal or higher priority. 656 * The source of options is given as a function that maps option names to the source of the 657 * option. Strings that cannot be parsed as options accumulates as* residue, if this parser 658 * allows it. 659 */ parseWithSourceFunction(OptionPriority priority, Function<? super String, String> sourceFunction, List<String> args)660 public void parseWithSourceFunction(OptionPriority priority, 661 Function<? super String, String> sourceFunction, List<String> args) 662 throws OptionsParsingException { 663 Preconditions.checkNotNull(priority); 664 Preconditions.checkArgument(priority != OptionPriority.DEFAULT); 665 residue.addAll(impl.parse(priority, sourceFunction, args)); 666 if (!allowResidue && !residue.isEmpty()) { 667 String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue); 668 throw new OptionsParsingException(errorMsg); 669 } 670 } 671 672 /** 673 * Clears the given option. Also clears expansion arguments and implicit requirements for that 674 * option. 675 * 676 * <p>This will not affect options objects that have already been retrieved from this parser 677 * through {@link #getOptions(Class)}. 678 * 679 * @param optionName The full name of the option to clear. 680 * @return A map of an option name to the old value of the options that were cleared. 681 * @throws IllegalArgumentException If the flag does not exist. 682 */ clearValue(String optionName)683 public Map<String, OptionValueDescription> clearValue(String optionName) 684 throws OptionsParsingException { 685 Map<String, OptionValueDescription> clearedValues = Maps.newHashMap(); 686 impl.clearValue(optionName, clearedValues); 687 return clearedValues; 688 } 689 690 @Override getResidue()691 public List<String> getResidue() { 692 return ImmutableList.copyOf(residue); 693 } 694 695 /** 696 * Returns a list of warnings about problems encountered by previous parse calls. 697 */ getWarnings()698 public List<String> getWarnings() { 699 return impl.getWarnings(); 700 } 701 702 @Override getOptions(Class<O> optionsClass)703 public <O extends OptionsBase> O getOptions(Class<O> optionsClass) { 704 return impl.getParsedOptions(optionsClass); 705 } 706 707 @Override containsExplicitOption(String name)708 public boolean containsExplicitOption(String name) { 709 return impl.containsExplicitOption(name); 710 } 711 712 @Override asListOfUnparsedOptions()713 public List<UnparsedOptionValueDescription> asListOfUnparsedOptions() { 714 return impl.asListOfUnparsedOptions(); 715 } 716 717 @Override asListOfExplicitOptions()718 public List<UnparsedOptionValueDescription> asListOfExplicitOptions() { 719 return impl.asListOfExplicitOptions(); 720 } 721 722 @Override asListOfEffectiveOptions()723 public List<OptionValueDescription> asListOfEffectiveOptions() { 724 return impl.asListOfEffectiveOptions(); 725 } 726 727 @Override canonicalize()728 public List<String> canonicalize() { 729 return impl.asCanonicalizedList(); 730 } 731 } 732