blob: f47045bd5777b61087ce9e9012146f397045c9fe [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.common.options;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MoreCollectors;
import com.google.common.escape.Escaper;
import com.google.devtools.common.options.OptionsParserImpl.ResidueAndPriority;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* A parser for options. Typical use case in a main method:
*
* <pre>
* OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class);
* parser.parseAndExitUponError(args);
* FooOptions foo = parser.getOptions(FooOptions.class);
* BarOptions bar = parser.getOptions(BarOptions.class);
* List&lt;String&gt; otherArguments = parser.getResidue();
* </pre>
*
* <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase,
* that contain fields annotated with @Option(...).
*
* <p>Alternatively, rather than calling {@link
* #parseAndExitUponError(OptionPriority.PriorityCategory, String, String[])}, client code may call
* {@link #parse(OptionPriority.PriorityCategory,String,List)}, and handle parser exceptions usage
* messages themselves.
*
* <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz'
* and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz'
* in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch
* options between void/boolean/tristate and everything else without breaking backwards
* compatibility.
*
* @see Options a simpler class which you can use if you only have one options specification class
*/
public class OptionsParser implements OptionsParsingResult {
// TODO(b/65049598) make ConstructionException checked.
/**
* An unchecked exception thrown when there is a problem constructing a parser, e.g. an error
* while validating an {@link OptionDefinition} in one of its {@link OptionsBase} subclasses.
*
* <p>This exception is unchecked because it generally indicates an internal error affecting all
* invocations of the program. I.e., any such error should be immediately obvious to the
* developer. Although unchecked, we explicitly mark some methods as throwing it as a reminder in
* the API.
*/
public static class ConstructionException extends RuntimeException {
public ConstructionException(String message) {
super(message);
}
public ConstructionException(Throwable cause) {
super(cause);
}
public ConstructionException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* A cache for the parsed options data. Both keys and values are immutable, so
* this is always safe. Only access this field through the {@link
* #getOptionsData} method for thread-safety! The cache is very unlikely to
* grow to a significant amount of memory, because there's only a fixed set of
* options classes on the classpath.
*/
private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData =
new HashMap<>();
/**
* Returns {@link OpaqueOptionsData} suitable for passing along to {@link
* Builder#optionsData(OpaqueOptionsData optionsData)}.
*
* <p>This is useful when you want to do the work of analyzing the given {@code optionsClasses}
* exactly once, but you want to parse lots of different lists of strings (and thus need to
* construct lots of different {@link OptionsParser} instances).
*/
public static OpaqueOptionsData getOptionsData(
List<Class<? extends OptionsBase>> optionsClasses) {
return getOptionsDataInternal(optionsClasses);
}
/**
* Returns the {@link OptionsData} associated with the given list of options classes.
*/
static synchronized OptionsData getOptionsDataInternal(
List<Class<? extends OptionsBase>> optionsClasses) throws ConstructionException {
ImmutableList<Class<? extends OptionsBase>> immutableOptionsClasses =
ImmutableList.copyOf(optionsClasses);
OptionsData result = optionsData.get(immutableOptionsClasses);
if (result == null) {
try {
result = OptionsData.from(immutableOptionsClasses);
} catch (Exception e) {
Throwables.throwIfInstanceOf(e, ConstructionException.class);
throw new ConstructionException(e.getMessage(), e);
}
optionsData.put(immutableOptionsClasses, result);
}
return result;
}
/**
* Returns the {@link OptionsData} associated with the given options class.
*/
static OptionsData getOptionsDataInternal(Class<? extends OptionsBase> optionsClass)
throws ConstructionException {
return getOptionsDataInternal(ImmutableList.of(optionsClass));
}
/** A helper class to create new instances of {@link OptionsParser}. */
public static final class Builder {
private final OptionsParserImpl.Builder implBuilder = OptionsParserImpl.builder();
private boolean allowResidue = true;
/** Directly sets the {@link OptionsData} used by this parser. */
public Builder optionsData(OptionsData optionsData) {
this.implBuilder.optionsData(optionsData);
return this;
}
/** Directly sets the {@link OpaqueOptionsData} used by this parser. */
public Builder optionsData(OpaqueOptionsData optionsData) {
return this.optionsData((OptionsData) optionsData);
}
/**
* Sets the {@link OptionsData} used by this parser, based on the given {@code optionsClasses}.
*/
@SafeVarargs
public final Builder optionsClasses(Class<? extends OptionsBase>... optionsClasses) {
return this.optionsData(
(OpaqueOptionsData) getOptionsDataInternal(ImmutableList.copyOf(optionsClasses)));
}
/**
* Sets the {@link OptionsData} used by this parser, based on the given {@code optionsClasses}.
*/
public Builder optionsClasses(Iterable<? extends Class<? extends OptionsBase>> optionsClasses) {
return this.optionsData(
(OpaqueOptionsData) getOptionsDataInternal(ImmutableList.copyOf(optionsClasses)));
}
/**
* Enables the Parser to handle params files using the provided {@link ParamsFilePreProcessor}.
*/
public Builder argsPreProcessor(ArgsPreProcessor preProcessor) {
this.implBuilder.argsPreProcessor(preProcessor);
return this;
}
/** Any flags with this prefix will be skipped during processing. */
public Builder skippedPrefix(String skippedPrefix) {
this.implBuilder.skippedPrefix(skippedPrefix);
return this;
}
/** Skip all the prefixes associated with Starlark options */
public Builder skipStarlarkOptionPrefixes() {
this.implBuilder
.skippedPrefix("--//")
.skippedPrefix("--no//")
.skippedPrefix("--@")
.skippedPrefix("--no@");
return this;
}
/**
* Indicates whether or not the parser will allow a non-empty residue; that is, iff this value
* is true then a call to one of the {@code parse} methods will throw {@link
* OptionsParsingException} unless {@link #getResidue()} is empty after parsing.
*/
public Builder allowResidue(boolean allowResidue) {
this.allowResidue = allowResidue;
return this;
}
/** Sets whether the parser should ignore internal-only options. */
public Builder ignoreInternalOptions(boolean ignoreInternalOptions) {
this.implBuilder.ignoreInternalOptions(ignoreInternalOptions);
return this;
}
/** Returns a new {@link OptionsParser}. */
public OptionsParser build() {
return new OptionsParser(implBuilder.build(), allowResidue);
}
}
/** Returns a new {@link Builder} to create {@link OptionsParser} instances. */
public static Builder builder() {
return new Builder();
}
private final OptionsParserImpl impl;
private final List<String> residue = new ArrayList<>();
private final List<String> postDoubleDashResidue = new ArrayList<>();
private final boolean allowResidue;
private ImmutableSortedMap<String, Object> starlarkOptions = ImmutableSortedMap.of();
private OptionsParser(OptionsParserImpl impl, boolean allowResidue) {
this.impl = impl;
this.allowResidue = allowResidue;
}
@Override
public ImmutableSortedMap<String, Object> getStarlarkOptions() {
return starlarkOptions;
}
public void setStarlarkOptions(Map<String, Object> starlarkOptions) {
this.starlarkOptions = ImmutableSortedMap.copyOf(starlarkOptions);
}
public void parseAndExitUponError(String[] args) {
parseAndExitUponError(OptionPriority.PriorityCategory.COMMAND_LINE, "unknown", args);
}
/**
* A convenience function for use in main methods. Parses the command line parameters, and exits
* upon error. Also, prints out the usage message if "--help" appears anywhere within {@code
* args}.
*/
public void parseAndExitUponError(
OptionPriority.PriorityCategory priority, String source, String[] args) {
for (String arg : args) {
if (arg.equals("--help")) {
System.out.println(
describeOptionsWithDeprecatedCategories(ImmutableMap.of(), HelpVerbosity.LONG));
System.exit(0);
}
}
try {
parse(priority, source, Arrays.asList(args));
} catch (OptionsParsingException e) {
System.err.println("Error parsing command line: " + e.getMessage());
System.err.println("Try --help.");
System.exit(2);
}
}
/** The metadata about an option, in the context of this options parser. */
public static final class OptionDescription {
private final OptionDefinition optionDefinition;
private final ImmutableList<String> evaluatedExpansion;
OptionDescription(OptionDefinition definition, OptionsData optionsData) {
this.optionDefinition = definition;
this.evaluatedExpansion = optionsData.getEvaluatedExpansion(optionDefinition);
}
public OptionDefinition getOptionDefinition() {
return optionDefinition;
}
public boolean isExpansion() {
return optionDefinition.isExpansionOption();
}
/** Return a list of flags that this option expands to. */
public ImmutableList<String> getExpansion() throws OptionsParsingException {
return evaluatedExpansion;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof OptionDescription) {
OptionDescription other = (OptionDescription) obj;
// Check that the option is the same, with the same expansion.
return other.optionDefinition.equals(optionDefinition)
&& other.evaluatedExpansion.equals(evaluatedExpansion);
}
return false;
}
@Override
public int hashCode() {
return optionDefinition.hashCode() + evaluatedExpansion.hashCode();
}
}
/**
* The verbosity with which option help messages are displayed: short (just
* the name), medium (name, type, default, abbreviation), and long (full
* description).
*/
public enum HelpVerbosity { LONG, MEDIUM, SHORT }
/**
* Returns a description of all the options this parser can digest. In addition to {@link Option}
* annotations, this method also interprets {@link OptionsUsage} annotations which give an
* intuitive short description for the options. Options of the same category (see {@link
* OptionDocumentationCategory}) will be grouped together.
*
* @param productName the name of this product (blaze, bazel)
* @param helpVerbosity if {@code long}, the options will be described verbosely, including their
* types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
* {@code short}, the options are just enumerated.
*/
public String describeOptions(String productName, HelpVerbosity helpVerbosity) {
StringBuilder desc = new StringBuilder();
LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
getOptionsSortedByCategory();
ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
for (Map.Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
optionsByCategory.entrySet()) {
String categoryDescription = optionCategoryDescriptions.get(e.getKey());
List<OptionDefinition> categorizedOptionList = e.getValue();
// Describe the category if we're going to end up using it at all.
if (!categorizedOptionList.isEmpty()) {
desc.append("\n").append(categoryDescription).append(":\n");
}
// Describe the options in this category.
for (OptionDefinition optionDef : categorizedOptionList) {
OptionsUsage.getUsage(optionDef, desc, helpVerbosity, impl.getOptionsData(), true);
}
}
return desc.toString().trim();
}
/**
* @return all documented options loaded in this parser, grouped by categories in display order.
*/
private LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>>
getOptionsSortedByCategory() {
OptionsData data = impl.getOptionsData();
if (data.getOptionsClasses().isEmpty()) {
return new LinkedHashMap<>();
}
// Get the documented options grouped by category.
ListMultimap<OptionDocumentationCategory, OptionDefinition> optionsByCategories =
ArrayListMultimap.create();
for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
for (OptionDefinition optionDefinition :
OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
// Only track documented options.
if (optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
optionsByCategories.put(optionDefinition.getDocumentationCategory(), optionDefinition);
}
}
}
// Put the categories into display order and sort the options in each category.
LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> sortedCategoriesToOptions =
new LinkedHashMap<>(OptionFilterDescriptions.documentationOrder.length, 1);
for (OptionDocumentationCategory category : OptionFilterDescriptions.documentationOrder) {
List<OptionDefinition> optionList = optionsByCategories.get(category);
if (optionList != null) {
optionList.sort(OptionDefinition.BY_OPTION_NAME);
sortedCategoriesToOptions.put(category, optionList);
}
}
return sortedCategoriesToOptions;
}
/**
* Returns a description of all the options this parser can digest. In addition to {@link Option}
* annotations, this method also interprets {@link OptionsUsage} annotations which give an
* intuitive short description for the options. Options of the same category (see {@link
* Option#category}) will be grouped together.
*
* @param categoryDescriptions a mapping from category names to category descriptions.
* Descriptions are optional; if omitted, a string based on the category name will be used.
* @param helpVerbosity if {@code long}, the options will be described verbosely, including their
* types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
* {@code short}, the options are just enumerated.
*/
@Deprecated
public String describeOptionsWithDeprecatedCategories(
Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) {
OptionsData data = impl.getOptionsData();
StringBuilder desc = new StringBuilder();
if (!data.getOptionsClasses().isEmpty()) {
List<OptionDefinition> allFields = new ArrayList<>();
for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
allFields.addAll(OptionsData.getAllOptionDefinitionsForClass(optionsClass));
}
Collections.sort(allFields, OptionDefinition.BY_CATEGORY);
String prevCategory = null;
for (OptionDefinition optionDefinition : allFields) {
String category = optionDefinition.getOptionCategory();
if (!category.equals(prevCategory)
&& optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
String description = categoryDescriptions.get(category);
if (description == null) {
description = "Options category '" + category + "'";
}
desc.append("\n").append(description).append(":\n");
prevCategory = category;
}
if (optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
OptionsUsage.getUsage(
optionDefinition, desc, helpVerbosity, impl.getOptionsData(), false);
}
}
}
return desc.toString().trim();
}
/**
* Returns a description of all the options this parser can digest. In addition to {@link Option}
* annotations, this method also interprets {@link OptionsUsage} annotations which give an
* intuitive short description for the options.
*
* @param categoryDescriptions a mapping from category names to category descriptions. Options of
* the same category (see {@link Option#category}) will be grouped together, preceded by the
* description of the category.
*/
@Deprecated
public String describeOptionsHtmlWithDeprecatedCategories(
Map<String, String> categoryDescriptions, Escaper escaper) {
OptionsData data = impl.getOptionsData();
StringBuilder desc = new StringBuilder();
if (!data.getOptionsClasses().isEmpty()) {
List<OptionDefinition> allFields = new ArrayList<>();
for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
allFields.addAll(OptionsData.getAllOptionDefinitionsForClass(optionsClass));
}
Collections.sort(allFields, OptionDefinition.BY_CATEGORY);
String prevCategory = null;
for (OptionDefinition optionDefinition : allFields) {
String category = optionDefinition.getOptionCategory();
if (!category.equals(prevCategory)
&& optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
String description = categoryDescriptions.get(category);
if (description == null) {
description = "Options category '" + category + "'";
}
if (prevCategory != null) {
desc.append("</dl>\n\n");
}
desc.append(escaper.escape(description)).append(":\n");
desc.append("<dl>");
prevCategory = category;
}
if (optionDefinition.getDocumentationCategory()
!= OptionDocumentationCategory.UNDOCUMENTED) {
OptionsUsage.getUsageHtml(optionDefinition, desc, escaper, impl.getOptionsData(), false);
}
}
desc.append("</dl>\n");
}
return desc.toString();
}
/**
* Returns a description of all the options this parser can digest. In addition to {@link Option}
* annotations, this method also interprets {@link OptionsUsage} annotations which give an
* intuitive short description for the options.
*/
public String describeOptionsHtml(Escaper escaper, String productName) {
StringBuilder desc = new StringBuilder();
LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
getOptionsSortedByCategory();
ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
for (Map.Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
optionsByCategory.entrySet()) {
desc.append("<dl>");
String categoryDescription = optionCategoryDescriptions.get(e.getKey());
List<OptionDefinition> categorizedOptionsList = e.getValue();
// Describe the category if we're going to end up using it at all.
if (!categorizedOptionsList.isEmpty()) {
desc.append(escaper.escape(categoryDescription)).append(":\n");
}
// Describe the options in this category.
for (OptionDefinition optionDef : categorizedOptionsList) {
OptionsUsage.getUsageHtml(optionDef, desc, escaper, impl.getOptionsData(), true);
}
desc.append("</dl>\n");
}
return desc.toString();
}
/**
* Returns a string listing the possible flag completion for this command along with the command
* completion if any. See {@link OptionsUsage#getCompletion(OptionDefinition, StringBuilder)} for
* more details on the format for the flag completion.
*/
public String getOptionsCompletion() {
StringBuilder desc = new StringBuilder();
visitOptions(
optionDefinition ->
optionDefinition.getDocumentationCategory() != OptionDocumentationCategory.UNDOCUMENTED,
optionDefinition -> OptionsUsage.getCompletion(optionDefinition, desc));
return desc.toString();
}
public void visitOptions(
Predicate<OptionDefinition> predicate, Consumer<OptionDefinition> visitor) {
Preconditions.checkNotNull(predicate, "Missing predicate.");
Preconditions.checkNotNull(visitor, "Missing visitor.");
OptionsData data = impl.getOptionsData();
data.getOptionsClasses()
// List all options
.stream()
.flatMap(optionsClass -> OptionsData.getAllOptionDefinitionsForClass(optionsClass).stream())
// Sort field for deterministic ordering
.sorted(OptionDefinition.BY_OPTION_NAME)
.filter(predicate)
.forEach(visitor);
}
/**
* Returns a description of the option.
*
* @return The {@link OptionDescription} for the option, or null if there is no option by the
* given name.
*/
OptionDescription getOptionDescription(String name) throws OptionsParsingException {
return impl.getOptionDescription(name);
}
/**
* Returns the parsed options that get expanded from this option, whether it expands due to an
* implicit requirement or expansion.
*
* @param expansionOption the option that might need to be expanded. If this option does not
* expand to other options, the empty list will be returned.
* @param originOfExpansionOption the origin of the option that's being expanded. This function
* will take care of adjusting the source messages as necessary.
*/
ImmutableList<ParsedOptionDescription> getExpansionValueDescriptions(
OptionDefinition expansionOption, OptionInstanceOrigin originOfExpansionOption)
throws OptionsParsingException {
return impl.getExpansionValueDescriptions(expansionOption, originOfExpansionOption);
}
/**
* {@inheritDoc}
*
* <p>Returns the value set by the last previous call to {@link
* #parse(OptionPriority.PriorityCategory, String, List)} that successfully set the given option.
* If the option is of type {@link List}, the description will correspond to any one of the calls,
* but not necessarily the last.
*/
@Override
public OptionValueDescription getOptionValueDescription(String name) {
return impl.getOptionValueDescription(name);
}
/**
* A convenience method, equivalent to {@code parse(PriorityCategory.COMMAND_LINE, null,
* Arrays.asList(args))}.
*/
public void parse(String... args) throws OptionsParsingException {
parse(OptionPriority.PriorityCategory.COMMAND_LINE, null, Arrays.asList(args));
}
/**
* A convenience method, equivalent to {@code parse(PriorityCategory.COMMAND_LINE, null, args)}.
*/
public void parse(List<String> args) throws OptionsParsingException {
parse(OptionPriority.PriorityCategory.COMMAND_LINE, null, args);
}
/**
* Parses {@code args}, using the classes registered with this parser, at the given priority.
*
* <p>May be called multiple times; later options override existing ones if they have equal or
* higher priority. Strings that cannot be parsed as options are accumulated as residue, if this
* parser allows it.
*
* <p>{@link #getOptions(Class)} and {@link #getResidue()} will return the results.
*
* @param priority the priority at which to parse these options. Within this priority category,
* each option will be given an index to track its position. If parse() has already been
* called at this priority, the indexing will continue where it left off, to keep ordering.
* @param source the source to track for each option parsed.
* @param args the arg list to parse. Each element might be an option, a value linked to an
* option, or residue.
*/
public void parse(OptionPriority.PriorityCategory priority, String source, List<String> args)
throws OptionsParsingException {
parseWithSourceFunction(priority, o -> source, args);
}
/**
* Parses {@code args}, using the classes registered with this parser, at the given priority.
*
* <p>May be called multiple times; later options override existing ones if they have equal or
* higher priority. Strings that cannot be parsed as options are accumulated as residue, if this
* parser allows it.
*
* <p>{@link #getOptions(Class)} and {@link #getResidue()} will return the results.
*
* @param priority the priority at which to parse these options. Within this priority category,
* each option will be given an index to track its position. If parse() has already been
* called at this priority, the indexing will continue where it left off, to keep ordering.
* @param sourceFunction a function that maps option names to the source of the option.
* @param args the arg list to parse. Each element might be an option, a value linked to an
* option, or residue.
*/
public void parseWithSourceFunction(
OptionPriority.PriorityCategory priority,
Function<OptionDefinition, String> sourceFunction,
List<String> args)
throws OptionsParsingException {
Preconditions.checkNotNull(priority);
Preconditions.checkArgument(priority != OptionPriority.PriorityCategory.DEFAULT);
ResidueAndPriority residueAndPriority = impl.parse(priority, sourceFunction, args);
residue.addAll(residueAndPriority.getResidue());
postDoubleDashResidue.addAll(residueAndPriority.postDoubleDashResidue);
if (!allowResidue && !residue.isEmpty()) {
String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
throw new OptionsParsingException(errorMsg);
}
}
/**
* Parses the args at the priority of the provided option. This is useful for after-the-fact
* expansion.
*
* @param optionToExpand the option that is being "expanded" after the fact. The provided args
* will have the same priority as this option.
* @param source a description of where the expansion arguments came from.
* @param args the arguments to parse as the expansion. Order matters, as the value of a flag may
* be in the following argument.
*/
public void parseArgsAsExpansionOfOption(
ParsedOptionDescription optionToExpand, String source, List<String> args)
throws OptionsParsingException {
Preconditions.checkNotNull(
optionToExpand, "Option for expansion not specified for arglist " + args);
Preconditions.checkArgument(
optionToExpand.getPriority().getPriorityCategory()
!= OptionPriority.PriorityCategory.DEFAULT,
"Priority cannot be default, which was specified for arglist " + args);
ResidueAndPriority residueAndPriority =
impl.parseArgsAsExpansionOfOption(optionToExpand, o -> source, args);
residue.addAll(residueAndPriority.getResidue());
postDoubleDashResidue.addAll(residueAndPriority.postDoubleDashResidue);
if (!allowResidue && !residue.isEmpty()) {
String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
throw new OptionsParsingException(errorMsg);
}
}
/**
* @param origin the origin of this option instance, it includes the priority of the value. If
* other values have already been or will be parsed at a higher priority, they might override
* the provided value. If this option already has a value at this priority, this value will
* have precedence, but this should be avoided, as it breaks order tracking.
* @param option the option to add the value for.
* @param value the value to add at the given priority.
*/
void addOptionValueAtSpecificPriority(
OptionInstanceOrigin origin, OptionDefinition option, String value)
throws OptionsParsingException {
impl.addOptionValueAtSpecificPriority(origin, option, value);
}
/**
* Clears the given option.
*
* <p>This will not affect options objects that have already been retrieved from this parser
* through {@link #getOptions(Class)}.
*
* @param option The option to clear.
* @return The old value of the option that was cleared.
* @throws IllegalArgumentException If the flag does not exist.
*/
public OptionValueDescription clearValue(OptionDefinition option) throws OptionsParsingException {
return impl.clearValue(option);
}
@Override
public List<String> getResidue() {
return ImmutableList.copyOf(residue);
}
@Override
public List<String> getPreDoubleDashResidue() {
return postDoubleDashResidue.isEmpty()
? ImmutableList.copyOf(residue)
: residue.stream()
.filter(residue -> !postDoubleDashResidue.contains(residue))
.collect(toImmutableList());
}
public List<String> getPostDoubleDashResidue() {
return ImmutableList.copyOf(postDoubleDashResidue);
}
/* Sets the residue (all elements parsed as non-options) to {@code residue}, as well as the part
* of the residue that follows the double-dash on the command line, {@code postDoubleDashResidue}.
* {@code postDoubleDashResidue} must be a subset of {@code residue}. */
public void setResidue(List<String> residue, List<String> postDoubleDashResidue) {
Preconditions.checkArgument(residue.containsAll(postDoubleDashResidue));
this.residue.clear();
this.residue.addAll(residue);
this.postDoubleDashResidue.clear();
this.postDoubleDashResidue.addAll(postDoubleDashResidue);
}
/** Returns a list of warnings about problems encountered by previous parse calls. */
public List<String> getWarnings() {
return ImmutableList.copyOf(impl.getWarnings());
}
@Override
public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
return impl.getParsedOptions(optionsClass);
}
@Override
public boolean containsExplicitOption(String name) {
return impl.containsExplicitOption(name);
}
@Override
public List<ParsedOptionDescription> asCompleteListOfParsedOptions() {
return impl.asCompleteListOfParsedOptions();
}
@Override
public List<ParsedOptionDescription> asListOfExplicitOptions() {
return impl.asListOfExplicitOptions();
}
@Override
public List<ParsedOptionDescription> asListOfCanonicalOptions() {
return impl.asCanonicalizedListOfParsedOptions();
}
@Override
public List<OptionValueDescription> asListOfOptionValues() {
return impl.asListOfEffectiveOptions();
}
@Override
public List<String> canonicalize() {
return impl.asCanonicalizedList();
}
/** Returns all options fields of the given options class, in alphabetic order. */
public static ImmutableList<OptionDefinition> getOptionDefinitions(
Class<? extends OptionsBase> optionsClass) {
return OptionsData.getAllOptionDefinitionsForClass(optionsClass);
}
/**
* Returns the option with the given name from the given class.
*
* <p>The preferred way of using this method is as the initializer for a static final field in the
* options class which defines the option. This reduces the possibility that another contributor
* might change the name of the option without realizing it's used by name elsewhere.
*
* @throws IllegalArgumentException if there are two or more options with that name.
* @throws java.util.NoSuchElementException if there are no options with that name.
*/
public static OptionDefinition getOptionDefinitionByName(
Class<? extends OptionsBase> optionsClass, String optionName) {
return getOptionDefinitions(optionsClass).stream()
.filter(definition -> definition.getOptionName().equals(optionName))
.collect(MoreCollectors.onlyElement());
}
/**
* Returns whether the given options class uses only the core types listed in {@link
* UsesOnlyCoreTypes#CORE_TYPES}. These are guaranteed to be deeply immutable and serializable.
*/
public static boolean getUsesOnlyCoreTypes(Class<? extends OptionsBase> optionsClass) {
OptionsData data = OptionsParser.getOptionsDataInternal(optionsClass);
return data.getUsesOnlyCoreTypes(optionsClass);
}
}