This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-cli.git
The following commit(s) were added to refs/heads/master by this push:
new d9a9433 [CLI-329] Support "Deprecated" CLI Options (#252)
d9a9433 is described below
commit d9a9433131d8c821278af738deaf6146f11a25b9
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Mar 29 12:15:41 2024 -0400
[CLI-329] Support "Deprecated" CLI Options (#252)
* [CLI-329] Support "Deprecated" CLI Options
* [CLI-329] Support "Deprecated" CLI Options
- HelpFormatter does not print deprecated options by default
- HelpFormatter can show deprecated options:
HelpFormatter.builder().setShowDeprecated(true).get();
* [CLI-329] Support "Deprecated" CLI Options
Deprecated string looks like: "Option 'c': Deprecated for removal since
2.0: Use X."
---
.../java/org/apache/commons/cli/CommandLine.java | 70 ++++++--
.../java/org/apache/commons/cli/DefaultParser.java | 46 +++--
.../apache/commons/cli/DeprecatedAttributes.java | 186 +++++++++++++++++++++
.../java/org/apache/commons/cli/HelpFormatter.java | 72 ++++++++
src/main/java/org/apache/commons/cli/Option.java | 162 ++++++++++++------
.../org/apache/commons/cli/CommandLineTest.java | 17 ++
.../org/apache/commons/cli/DefaultParserTest.java | 37 ++++
.../commons/cli/DeprecatedAttributesTest.java | 75 +++++++++
.../java/org/apache/commons/cli/OptionTest.java | 64 +++++--
.../java/org/apache/commons/cli/OptionsTest.java | 20 ++-
.../java/org/apache/commons/cli/SolrCliTest.java | 162 ++++++++++++++++++
.../org/apache/commons/cli/SolrCreateToolTest.java | 106 ++++++++++++
12 files changed, 923 insertions(+), 94 deletions(-)
diff --git a/src/main/java/org/apache/commons/cli/CommandLine.java
b/src/main/java/org/apache/commons/cli/CommandLine.java
index bbcc540..afe8461 100644
--- a/src/main/java/org/apache/commons/cli/CommandLine.java
+++ b/src/main/java/org/apache/commons/cli/CommandLine.java
@@ -26,6 +26,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -47,12 +48,22 @@ public class CommandLine implements Serializable {
*/
public static final class Builder {
+ /**
+ * Prints an Option to {@link System#out}.
+ */
+ static final Consumer<Option> DEPRECATED_HANDLER = o ->
System.out.println(o.toDeprecatedString());
+
/** The unrecognized options/arguments */
private final List<String> args = new LinkedList<>();
/** The processed options */
private final List<Option> options = new ArrayList<>();
+ /**
+ * Deprecated Option handler.
+ */
+ private Consumer<Option> deprecatedHandler = DEPRECATED_HANDLER;
+
/**
* Adds left-over unrecognized option/argument.
*
@@ -82,15 +93,30 @@ public class CommandLine implements Serializable {
}
/**
- * Returns the new instance.
+ * Creates the new instance.
*
* @return the new instance.
*/
public CommandLine build() {
- return new CommandLine(args, options);
+ return new CommandLine(args, options, deprecatedHandler);
+ }
+
+ /**
+ * Sets the deprecated option handler.
+ *
+ * @param deprecatedHandler the deprecated option handler.
+ * @return this.
+ * @since 1.7.0
+ */
+ public Builder setDeprecatedHandler(final Consumer<Option>
deprecatedHandler) {
+ this.deprecatedHandler = deprecatedHandler;
+ return this;
}
}
+ /** The serial version UID. */
+ private static final long serialVersionUID = 1L;
+
/**
* Creates a new builder.
*
@@ -101,28 +127,34 @@ public class CommandLine implements Serializable {
return new Builder();
}
- /** The serial version UID. */
- private static final long serialVersionUID = 1L;
-
/** The unrecognized options/arguments */
private final List<String> args;
/** The processed options */
private final List<Option> options;
+ /**
+ * The deprecated option handler.
+ * <p>
+ * If you want to serialize this field, use a serialization proxy.
+ * </p>
+ */
+ private final transient Consumer<Option> deprecatedHandler;
+
/**
* Creates a command line.
*/
protected CommandLine() {
- this(new LinkedList<>(), new ArrayList<>());
+ this(new LinkedList<>(), new ArrayList<>(),
Builder.DEPRECATED_HANDLER);
}
/**
* Creates a command line.
*/
- private CommandLine(final List<String> args, final List<Option> options) {
+ private CommandLine(final List<String> args, final List<Option> options,
final Consumer<Option> deprecatedHandler) {
this.args = Objects.requireNonNull(args, "args");
this.options = Objects.requireNonNull(options, "options");
+ this.deprecatedHandler = deprecatedHandler;
}
/**
@@ -530,13 +562,14 @@ public class CommandLine implements Serializable {
}
/**
- * Tests to see if an option has been set.
+ * Handles deprecated options.
*
- * @param opt character name of the option.
- * @return true if set, false if not.
+ * @param option a deprecated option.
*/
- public boolean hasOption(final char opt) {
- return hasOption(String.valueOf(opt));
+ private void handleDeprecated(final Option option) {
+ if (deprecatedHandler != null) {
+ deprecatedHandler.accept(option);
+ }
}
/**
@@ -557,6 +590,16 @@ public class CommandLine implements Serializable {
* return buf.toString(); }
*/
+ /**
+ * Tests to see if an option has been set.
+ *
+ * @param opt character name of the option.
+ * @return true if set, false if not.
+ */
+ public boolean hasOption(final char opt) {
+ return hasOption(String.valueOf(opt));
+ }
+
/**
* Tests to see if an option has been set.
*
@@ -615,6 +658,9 @@ public class CommandLine implements Serializable {
if (actual != null) {
for (final Option option : options) {
if (actual.equals(option.getOpt()) ||
actual.equals(option.getLongOpt())) {
+ if (option.isDeprecated()) {
+ handleDeprecated(option);
+ }
return option;
}
}
diff --git a/src/main/java/org/apache/commons/cli/DefaultParser.java
b/src/main/java/org/apache/commons/cli/DefaultParser.java
index 077a1a6..4737596 100644
--- a/src/main/java/org/apache/commons/cli/DefaultParser.java
+++ b/src/main/java/org/apache/commons/cli/DefaultParser.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
+import java.util.function.Consumer;
/**
* Default parser.
@@ -48,6 +49,14 @@ public class DefaultParser implements CommandLineParser {
/** Flag indicating if partial matching of long options is supported.
*/
private boolean allowPartialMatching = true;
+ /**
+ * The deprecated option handler.
+ * <p>
+ * If you want to serialize this field, use a serialization proxy.
+ * </p>
+ */
+ private Consumer<Option> deprecatedHandler =
CommandLine.Builder.DEPRECATED_HANDLER;
+
/** Flag indicating if balanced leading and trailing double quotes
should be stripped from option arguments. */
private Boolean stripLeadingAndTrailingQuotes;
@@ -67,7 +76,7 @@ public class DefaultParser implements CommandLineParser {
* @since 1.5.0
*/
public DefaultParser build() {
- return new DefaultParser(allowPartialMatching,
stripLeadingAndTrailingQuotes);
+ return new DefaultParser(allowPartialMatching,
stripLeadingAndTrailingQuotes, deprecatedHandler);
}
/**
@@ -97,6 +106,18 @@ public class DefaultParser implements CommandLineParser {
return this;
}
+ /**
+ * Sets the deprecated option handler.
+ *
+ * @param deprecatedHandler the deprecated option handler.
+ * @return this.
+ * @since 1.7.0
+ */
+ public Builder setDeprecatedHandler(final Consumer<Option>
deprecatedHandler) {
+ this.deprecatedHandler = deprecatedHandler;
+ return this;
+ }
+
/**
* Sets if balanced leading and trailing double quotes should be
stripped from option arguments.
*
@@ -160,6 +181,14 @@ public class DefaultParser implements CommandLineParser {
* null represents the historic arbitrary behavior */
private final Boolean stripLeadingAndTrailingQuotes;
+ /**
+ * The deprecated option handler.
+ * <p>
+ * If you want to serialize this field, use a serialization proxy.
+ * </p>
+ */
+ private final Consumer<Option> deprecatedHandler;
+
/**
* Creates a new DefaultParser instance with partial matching enabled.
*
@@ -182,6 +211,7 @@ public class DefaultParser implements CommandLineParser {
public DefaultParser() {
this.allowPartialMatching = true;
this.stripLeadingAndTrailingQuotes = null;
+ this.deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
}
/**
@@ -208,6 +238,7 @@ public class DefaultParser implements CommandLineParser {
public DefaultParser(final boolean allowPartialMatching) {
this.allowPartialMatching = allowPartialMatching;
this.stripLeadingAndTrailingQuotes = null;
+ this.deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
}
/**
@@ -217,10 +248,10 @@ public class DefaultParser implements CommandLineParser {
* @param allowPartialMatching if partial matching of long options shall
be enabled
* @param stripLeadingAndTrailingQuotes if balanced outer double quoutes
should be stripped
*/
- private DefaultParser(final boolean allowPartialMatching,
- final Boolean stripLeadingAndTrailingQuotes) {
+ private DefaultParser(final boolean allowPartialMatching, final Boolean
stripLeadingAndTrailingQuotes, final Consumer<Option> deprecatedHandler) {
this.allowPartialMatching = allowPartialMatching;
this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
+ this.deprecatedHandler = deprecatedHandler;
}
/**
@@ -671,28 +702,21 @@ public class DefaultParser implements CommandLineParser {
skipParsing = false;
currentOption = null;
expectedOpts = new ArrayList<>(options.getRequiredOptions());
-
// clear the data from the groups
for (final OptionGroup group : options.getOptionGroups()) {
group.setSelected(null);
}
-
- cmd = CommandLine.builder().build();
-
+ cmd =
CommandLine.builder().setDeprecatedHandler(deprecatedHandler).build();
if (arguments != null) {
for (final String argument : arguments) {
handleToken(argument);
}
}
-
// check the arguments of the last option
checkRequiredArgs();
-
// add the default options
handleProperties(properties);
-
checkRequiredOptions();
-
return cmd;
}
diff --git a/src/main/java/org/apache/commons/cli/DeprecatedAttributes.java
b/src/main/java/org/apache/commons/cli/DeprecatedAttributes.java
new file mode 100644
index 0000000..bb4521d
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/DeprecatedAttributes.java
@@ -0,0 +1,186 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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 org.apache.commons.cli;
+
+import java.util.function.Supplier;
+
+/**
+ * Deprecated attributes.
+ * <p>
+ * Note: This class isn't called "Deprecated" to avoid clashing with
"java.lang.Deprecated".
+ * </p>
+ * <p>
+ * If you want to serialize this class, use a serialization proxy.
+ * </p>
+ *
+ * @since 1.7.0
+ * @see Deprecated
+ */
+public final class DeprecatedAttributes {
+
+ /**
+ * Builds {@link DeprecatedAttributes}.
+ */
+ public static class Builder implements Supplier<DeprecatedAttributes> {
+
+ /** The description. */
+ private String description;
+
+ /**
+ * Whether this option is subject to removal in a future version.
+ *
+ * @see <a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Deprecated.html#forRemoval()">Deprecated.forRemoval</a>
+ */
+ private boolean forRemoval;
+
+ /**
+ * The version in which the option became deprecated.
+ *
+ * @see <a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Deprecated.html#forRemoval()">Deprecated.since</a>
+ */
+ private String since;
+
+ @Override
+ public DeprecatedAttributes get() {
+ return new DeprecatedAttributes(description, since, forRemoval);
+ }
+
+ /**
+ * Sets the description.
+ *
+ * @param description the description.
+ * @return this.
+ */
+ public Builder setDescription(final String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * Whether this option is subject to removal in a future version.
+ *
+ * @param forRemoval whether this is subject to removal in a future
version.
+ * @return this.
+ * @see <a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Deprecated.html#forRemoval()">Deprecated.forRemoval</a>
+ */
+ public Builder setForRemoval(final boolean forRemoval) {
+ this.forRemoval = forRemoval;
+ return this;
+ }
+
+ /**
+ * Sets the version in which the option became deprecated.
+ *
+ * @param since the version in which the option became deprecated.
+ * @return this.
+ */
+ public Builder setSince(final String since) {
+ this.since = since;
+ return this;
+ }
+ }
+
+ /**
+ * The default value for a DeprecatedAttributes.
+ */
+ static final DeprecatedAttributes DEFAULT = new DeprecatedAttributes("",
"", false);
+
+ /**
+ * The empty string.
+ */
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * Creates a new builder.
+ *
+ * @return a new builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** The description. */
+ private final String description;
+
+ /** Whether this option will be removed. */
+ private final boolean forRemoval;
+
+ /** The version label for removal. */
+ private final String since;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param description The description.
+ * @param since The version label for removal.
+ * @param forRemoval Whether this option will be removed.
+ */
+ private DeprecatedAttributes(final String description, final String since,
final boolean forRemoval) {
+ this.description = toEmpty(description);
+ this.since = toEmpty(since);
+ this.forRemoval = forRemoval;
+ }
+
+ /**
+ * Gets the descriptions.
+ *
+ * @return the descriptions.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Gets version in which the option became deprecated.
+ *
+ * @return the version in which the option became deprecated.
+ */
+ public String getSince() {
+ return since;
+ }
+
+ /**
+ * Tests whether this option is subject to removal in a future version.
+ *
+ * @return whether this option is subject to removal in a future version.
+ */
+ public boolean isForRemoval() {
+ return forRemoval;
+ }
+
+ private String toEmpty(final String since) {
+ return since != null ? since : EMPTY_STRING;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("Deprecated");
+ if (forRemoval) {
+ builder.append(" for removal");
+ }
+ if (!since.isEmpty()) {
+ builder.append(" since ");
+ builder.append(since);
+ }
+ if (!description.isEmpty()) {
+ builder.append(": ");
+ builder.append(description);
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/HelpFormatter.java
b/src/main/java/org/apache/commons/cli/HelpFormatter.java
index e8faece..2137efe 100644
--- a/src/main/java/org/apache/commons/cli/HelpFormatter.java
+++ b/src/main/java/org/apache/commons/cli/HelpFormatter.java
@@ -29,6 +29,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Supplier;
/**
* A formatter of help messages for command line options.
@@ -63,6 +64,38 @@ import java.util.List;
*/
public class HelpFormatter {
+ /**
+ * Builds {@link HelpFormatter}.
+ *
+ * @since 1.7.0
+ */
+ public static final class Builder implements Supplier<HelpFormatter> {
+ // TODO All other instance HelpFormatter instance variables.
+ // Make HelpFormatter immutable for 2.0
+
+ /**
+ * Whether to show deprecated options.
+ */
+ private boolean showDeprecated;
+
+ @Override
+ public HelpFormatter get() {
+ return new HelpFormatter(showDeprecated);
+ }
+
+ /**
+ * Sets whether to show deprecated options.
+ *
+ * @param showDeprecated Whether to show deprecated options.
+ * @return this.
+ */
+ public Builder setShowDeprecated(final boolean showDeprecated) {
+ this.showDeprecated = showDeprecated;
+ return this;
+ }
+
+ }
+
/**
* This class implements the {@code Comparator} interface for comparing
Options.
*/
@@ -113,6 +146,16 @@ public class HelpFormatter {
/** Default name for an argument */
public static final String DEFAULT_ARG_NAME = "arg";
+ /**
+ * Creates a new builder.
+ *
+ * @return a new builder.
+ * @since 1.7.0
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
/**
* Number of characters per line
*
@@ -184,11 +227,34 @@ public class HelpFormatter {
*/
protected Comparator<Option> optionComparator = new OptionComparator();
+ /**
+ * Whether to show deprecated options.
+ */
+ private final boolean showDeprecated;
+
/**
* The separator displayed between the long option and its value.
*/
private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR;
+ /**
+ * Constructs a new instance.
+ */
+ public HelpFormatter() {
+ super();
+ this.showDeprecated = false;
+ }
+
+ /**
+ * Constructs a new instance.
+ */
+ private HelpFormatter(final boolean showDeprecated) {
+ // TODO All other instance HelpFormatter instance variables.
+ // Make HelpFormatter immutable for 2.0
+ super();
+ this.showDeprecated = showDeprecated;
+ }
+
/**
* Appends the usage clause for an Option to a StringBuffer.
*
@@ -686,7 +752,13 @@ public class HelpFormatter {
}
optBuf.append(dpad);
final int nextLineTabStop = max + descPad;
+ if (showDeprecated && option.isDeprecated()) {
+ optBuf.append("[Deprecated]");
+ }
if (option.getDescription() != null) {
+ if (showDeprecated && option.isDeprecated()) {
+ optBuf.append(' ');
+ }
optBuf.append(option.getDescription());
}
renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
diff --git a/src/main/java/org/apache/commons/cli/Option.java
b/src/main/java/org/apache/commons/cli/Option.java
index b6f378f..8c54ce2 100644
--- a/src/main/java/org/apache/commons/cli/Option.java
+++ b/src/main/java/org/apache/commons/cli/Option.java
@@ -25,16 +25,13 @@ import java.util.List;
import java.util.Objects;
/**
- * Describes a single command-line option. It maintains information regarding
the short-name of the option, the
- * long-name, if any exists, a flag indicating if an argument is required for
this option, and a self-documenting
- * description of the option.
+ * Describes a single command-line option. It maintains information regarding
the short-name of the option, the long-name, if any exists, a flag indicating if
+ * an argument is required for this option, and a self-documenting description
of the option.
* <p>
- * An Option is not created independently, but is created through an instance
of {@link Options}. An Option is required
- * to have at least a short or a long-name.
+ * An Option is not created independently, but is created through an instance
of {@link Options}. An Option is required to have at least a short or a
long-name.
* </p>
* <p>
- * <b>Note:</b> once an {@link Option} has been added to an instance of {@link
Options}, its required flag cannot be
- * changed.
+ * <b>Note:</b> once an {@link Option} has been added to an instance of {@link
Options}, its required flag cannot be changed.
* </p>
*
* @see org.apache.commons.cli.Options
@@ -68,6 +65,9 @@ public class Option implements Cloneable, Serializable {
/** The name of the argument for this option. */
private String argName;
+ /** Specifies whether this option is deprecated. */
+ private DeprecatedAttributes deprecated;
+
/** Specifies whether this option is required to be present. */
private boolean required;
@@ -123,7 +123,9 @@ public class Option implements Cloneable, Serializable {
/**
* Sets the converter for the option.
*
- * <p>Note: see {@link TypeHandler} for serialization discussion.</p>
+ * <p>
+ * Note: see {@link TypeHandler} for serialization discussion.
+ * </p>
*
* @param converter the Converter to use.
* @return this builder, to allow method chaining.
@@ -134,6 +136,28 @@ public class Option implements Cloneable, Serializable {
return this;
}
+ /**
+ * Marks this Option as deprecated.
+ *
+ * @return this builder.
+ * @since 1.7.0
+ */
+ public Builder deprecated() {
+ return deprecated(DeprecatedAttributes.DEFAULT);
+ }
+
+ /**
+ * Sets whether the Option is deprecated.
+ *
+ * @param deprecated specifies whether the Option is deprecated.
+ * @return this builder.
+ * @since 1.7.0
+ */
+ public Builder deprecated(final DeprecatedAttributes deprecated) {
+ this.deprecated = deprecated;
+ return this;
+ }
+
/**
* Sets the description for this option.
*
@@ -235,9 +259,9 @@ public class Option implements Cloneable, Serializable {
}
/**
- * Sets whether the Option is mandatory.
+ * Sets whether the Option is required.
*
- * @param required specifies whether the Option is mandatory.
+ * @param required specifies whether the Option is required.
* @return this builder.
*/
public Builder required(final boolean required) {
@@ -275,7 +299,7 @@ public class Option implements Cloneable, Serializable {
* Option opt =
Option.builder("D").hasArgs().valueSeparator('=').build();
* Options options = new Options();
* options.addOption(opt);
- * String[] args = {"-Dkey=value"};
+ * String[] args = { "-Dkey=value" };
* CommandLineParser parser = new DefaultParser();
* CommandLine line = parser.parse(options, args);
* String propertyName = line.getOptionValues("D")[0]; // will be "key"
@@ -338,6 +362,14 @@ public class Option implements Cloneable, Serializable {
/** Description of the option. */
private String description;
+ /**
+ * Specifies whether this option is deprecated, may be null.
+ * <p>
+ * If you want to serialize this field, use a serialization proxy.
+ * </p>
+ */
+ private final transient DeprecatedAttributes deprecated;
+
/** Specifies whether this option is required to be present. */
private boolean required;
@@ -356,7 +388,7 @@ public class Option implements Cloneable, Serializable {
/** The character that is the value separator. */
private char valuesep;
- /** The explicit converter for this option. May be null. */
+ /** The explicit converter for this option. May be null. */
private transient Converter<?, ?> converter;
/**
@@ -371,6 +403,7 @@ public class Option implements Cloneable, Serializable {
this.argCount = builder.argCount;
this.option = builder.option;
this.optionalArg = builder.optionalArg;
+ this.deprecated = builder.deprecated;
this.required = builder.required;
this.type = builder.type;
this.valuesep = builder.valueSeparator;
@@ -380,8 +413,8 @@ public class Option implements Cloneable, Serializable {
/**
* Creates an Option using the specified parameters.
*
- * @param option short representation of the option.
- * @param hasArg specifies whether the Option takes an argument or not.
+ * @param option short representation of the option.
+ * @param hasArg specifies whether the Option takes an argument or
not.
* @param description describes the function of the option.
*
* @throws IllegalArgumentException if there are any non valid Option
characters in {@code opt}.
@@ -393,7 +426,7 @@ public class Option implements Cloneable, Serializable {
/**
* Creates an Option using the specified parameters. The option does not
take an argument.
*
- * @param option short representation of the option.
+ * @param option short representation of the option.
* @param description describes the function of the option.
*
* @throws IllegalArgumentException if there are any non valid Option
characters in {@code opt}.
@@ -405,23 +438,22 @@ public class Option implements Cloneable, Serializable {
/**
* Creates an Option using the specified parameters.
*
- * @param option short representation of the option.
- * @param longOption the long representation of the option.
- * @param hasArg specifies whether the Option takes an argument or not.
+ * @param option short representation of the option.
+ * @param longOption the long representation of the option.
+ * @param hasArg specifies whether the Option takes an argument or
not.
* @param description describes the function of the option.
*
* @throws IllegalArgumentException if there are any non valid Option
characters in {@code opt}.
*/
public Option(final String option, final String longOption, final boolean
hasArg, final String description) throws IllegalArgumentException {
// ensure that the option is valid
+ this.deprecated = null;
this.option = OptionValidator.validate(option);
this.longOption = longOption;
-
// if hasArg is set then the number of arguments is 1
if (hasArg) {
this.argCount = 1;
}
-
this.description = description;
}
@@ -436,8 +468,8 @@ public class Option implements Cloneable, Serializable {
}
/**
- * Adds the value to this Option. If the number of arguments is greater
than zero and there is enough space in the list
- * then add the value. Otherwise, throw a runtime exception.
+ * Adds the value to this Option. If the number of arguments is greater
than zero and there is enough space in the list then add the value. Otherwise,
throw
+ * a runtime exception.
*
* @param value The value to be added to this Option.
*
@@ -452,8 +484,7 @@ public class Option implements Cloneable, Serializable {
}
/**
- * This method is not intended to be used. It was a piece of internal API
that was made public in 1.0. It currently
- * throws an UnsupportedOperationException.
+ * This method is not intended to be used. It was a piece of internal API
that was made public in 1.0. It currently throws an
UnsupportedOperationException.
*
* @param value the value to add.
* @return always throws an {@link UnsupportedOperationException}.
@@ -463,7 +494,7 @@ public class Option implements Cloneable, Serializable {
@Deprecated
public boolean addValue(final String value) {
throw new UnsupportedOperationException(
- "The addValue method is not intended for client use. " +
"Subclasses should use the addValueForProcessing method instead. ");
+ "The addValue method is not intended for client use. " +
"Subclasses should use the addValueForProcessing method instead. ");
}
/**
@@ -479,8 +510,7 @@ public class Option implements Cloneable, Serializable {
}
/**
- * Clears the Option values. After a parse is complete, these are left
with data in them and they need clearing if
- * another parse is done.
+ * Clears the Option values. After a parse is complete, these are left
with data in them and they need clearing if another parse is done.
*
* See: <a href="https://issues.apache.org/jira/browse/CLI-71">CLI-71</a>
*/
@@ -533,9 +563,8 @@ public class Option implements Cloneable, Serializable {
* Gets the number of argument values this Option can take.
*
* <p>
- * A value equal to the constant {@link #UNINITIALIZED} (= -1) indicates
the number of arguments has not been specified.
- * A value equal to the constant {@link #UNLIMITED_VALUES} (= -2)
indicates that this options takes an unlimited amount
- * of values.
+ * A value equal to the constant {@link #UNINITIALIZED} (= -1) indicates
the number of arguments has not been specified. A value equal to the constant
+ * {@link #UNLIMITED_VALUES} (= -2) indicates that this options takes an
unlimited amount of values.
* </p>
*
* @return num the number of argument values.
@@ -556,6 +585,16 @@ public class Option implements Cloneable, Serializable {
return converter == null ? TypeHandler.getConverter(type) : converter;
}
+ /**
+ * Gets deprecated attributes if any.
+ *
+ * @return boolean deprecated attributes or null.
+ * @since 1.7.0
+ */
+ public DeprecatedAttributes getDeprecated() {
+ return deprecated;
+ }
+
/**
* Gets the self-documenting description of this Option.
*
@@ -566,8 +605,7 @@ public class Option implements Cloneable, Serializable {
}
/**
- * Gets the id of this Option. This is only set when the Option shortOpt
is a single character. This is used for
- * switch statements.
+ * Gets the id of this Option. This is only set when the Option shortOpt
is a single character. This is used for switch statements.
*
* @return the id of this Option.
*/
@@ -576,8 +614,7 @@ public class Option implements Cloneable, Serializable {
}
/**
- * Gets the 'unique' Option identifier. This is the option value if set
or the long value
- * if the options value is not set.
+ * Gets the 'unique' Option identifier. This is the option value if set or
the long value if the options value is not set.
*
* @return the 'unique' Option identifier.
* @since 1.7.0
@@ -599,8 +636,8 @@ public class Option implements Cloneable, Serializable {
/**
* Gets the name of this Option.
*
- * It is this String which can be used with {@link
CommandLine#hasOption(String opt)} and
- * {@link CommandLine#getOptionValue(String opt)} to check for existence
and argument.
+ * It is this String which can be used with {@link
CommandLine#hasOption(String opt)} and {@link CommandLine#getOptionValue(String
opt)} to check for
+ * existence and argument.
*
* @return The name of this option.
*/
@@ -748,19 +785,28 @@ public class Option implements Cloneable, Serializable {
return valuesep > 0;
}
+ /**
+ * Tests whether this Option is deprecated.
+ *
+ * @return boolean flag indicating whether this Option is deprecated.
+ * @since 1.7.0
+ */
+ public boolean isDeprecated() {
+ return deprecated != null;
+ }
+
/**
* Tests whether this Option is required.
*
- * @return boolean flag indicating whether this Option is mandatory.
+ * @return boolean flag indicating whether this Option is required.
*/
public boolean isRequired() {
return required;
}
/**
- * Processes the value. If this Option has a value separator the value
will have to be parsed into individual tokens.
- * When n-1 tokens have been processed and there are more value separators
in the value, parsing is ceased and the
- * remaining characters are added as a single token.
+ * Processes the value. If this Option has a value separator the value
will have to be parsed into individual tokens. When n-1 tokens have been
processed
+ * and there are more value separators in the value, parsing is ceased and
the remaining characters are added as a single token.
*
* @param value The String to be processed.
*
@@ -892,8 +938,7 @@ public class Option implements Cloneable, Serializable {
/**
* Sets the type of this Option.
* <p>
- * <b>Note:</b> this method is kept for binary compatibility and the input
type is supposed to be a {@link Class}
- * object.
+ * <b>Note:</b> this method is kept for binary compatibility and the input
type is supposed to be a {@link Class} object.
* </p>
*
* @param type the type of this Option.
@@ -913,6 +958,21 @@ public class Option implements Cloneable, Serializable {
this.valuesep = sep;
}
+ String toDeprecatedString() {
+ if (!isDeprecated()) {
+ return "";
+ }
+ final StringBuilder buf = new StringBuilder().append("Option '");
+ buf.append(option).append('\'');
+ if (longOption != null) {
+ buf.append('\'').append(longOption).append('\'');
+ }
+ if (isDeprecated()) {
+ buf.append(": ").append(deprecated);
+ }
+ return buf.toString();
+ }
+
/**
* Creates a String suitable for debugging.
*
@@ -920,30 +980,26 @@ public class Option implements Cloneable, Serializable {
*/
@Override
public String toString() {
- final StringBuilder buf = new StringBuilder().append("[ option: ");
-
+ final StringBuilder buf = new StringBuilder().append("[ ");
+ buf.append("Option ");
buf.append(option);
-
if (longOption != null) {
- buf.append(" ").append(longOption);
+ buf.append(' ').append(longOption);
+ }
+ if (isDeprecated()) {
+ buf.append(' ');
+ buf.append(deprecated.toString());
}
-
- buf.append(" ");
-
if (hasArgs()) {
buf.append("[ARG...]");
} else if (hasArg()) {
buf.append(" [ARG]");
}
-
buf.append(" :: ").append(description);
-
if (type != null) {
buf.append(" :: ").append(type);
}
-
buf.append(" ]");
-
return buf.toString();
}
}
diff --git a/src/test/java/org/apache/commons/cli/CommandLineTest.java
b/src/test/java/org/apache/commons/cli/CommandLineTest.java
index 32f3fad..7a6b2b7 100644
--- a/src/test/java/org/apache/commons/cli/CommandLineTest.java
+++ b/src/test/java/org/apache/commons/cli/CommandLineTest.java
@@ -20,8 +20,10 @@ package org.apache.commons.cli;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -29,6 +31,21 @@ import org.junit.jupiter.api.Test;
@SuppressWarnings("deprecation") // tests some deprecated classes
public class CommandLineTest {
+ @Test
+ public void testDeprecatedOption() {
+ final CommandLine.Builder builder = new CommandLine.Builder();
+ builder.addArg("foo").addArg("bar");
+ final Option opt = Option.builder().option("T").deprecated().build();
+ builder.addOption(opt);
+ final AtomicReference<Option> handler = new AtomicReference<>();
+ final CommandLine cmd =
builder.setDeprecatedHandler(handler::set).build();
+ cmd.getOptionValue(opt.getOpt());
+ assertSame(opt, handler.get());
+ handler.set(null);
+ cmd.getOptionValue("Nope");
+ assertNull(handler.get());
+ }
+
@Test
public void testBuilder() {
final CommandLine.Builder builder = new CommandLine.Builder();
diff --git a/src/test/java/org/apache/commons/cli/DefaultParserTest.java
b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
index cf6c083..31d4805 100644
--- a/src/test/java/org/apache/commons/cli/DefaultParserTest.java
+++ b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
@@ -18,6 +18,11 @@
package org.apache.commons.cli;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashSet;
+import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -33,13 +38,45 @@ public class DefaultParserTest extends
AbstractParserTestCase {
@Test
public void testBuilder() {
+ // @formatter:off
parser = DefaultParser.builder()
.setStripLeadingAndTrailingQuotes(false)
.setAllowPartialMatching(false)
+ .setDeprecatedHandler(null)
.build();
+ // @formatter:on
assertEquals(DefaultParser.class, parser.getClass());
}
+ @Test
+ public void testDeprecated() throws ParseException {
+ final Set<Option> handler = new HashSet<>();
+ parser =
DefaultParser.builder().setDeprecatedHandler(handler::add).build();
+ final Option opt1 = Option.builder().option("d1").deprecated().build();
+ // @formatter:off
+ final Option opt2 =
Option.builder().option("d2").deprecated(DeprecatedAttributes.builder()
+ .setForRemoval(true)
+ .setSince("1.0")
+ .setDescription("Do this instead.").get()).build();
+ // @formatter:on
+ final Option opt3 = Option.builder().option("a").build();
+ // @formatter:off
+ final CommandLine cl = parser.parse(new Options()
+ .addOption(opt1)
+ .addOption(opt2)
+ .addOption(opt3),
+ new String[] {"-d1", "-d2", "-a"});
+ // @formatter:on
+ // Trigger handler:
+ assertTrue(cl.hasOption(opt1.getOpt()));
+ assertTrue(cl.hasOption(opt2.getOpt()));
+ assertTrue(cl.hasOption(opt3.getOpt()));
+ // Assert handler was triggered
+ assertTrue(handler.contains(opt1));
+ assertTrue(handler.contains(opt2));
+ assertFalse(handler.contains(opt3));
+ }
+
@Test
public void testLongOptionQuoteHandlingWithoutStrip() throws Exception {
parser =
DefaultParser.builder().setStripLeadingAndTrailingQuotes(false).build();
diff --git a/src/test/java/org/apache/commons/cli/DeprecatedAttributesTest.java
b/src/test/java/org/apache/commons/cli/DeprecatedAttributesTest.java
new file mode 100644
index 0000000..c518035
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/DeprecatedAttributesTest.java
@@ -0,0 +1,75 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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 org.apache.commons.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class DeprecatedAttributesTest {
+
+ @Test
+ public void testBuilderNonDefaults() {
+ // @formatter:off
+ final DeprecatedAttributes value = DeprecatedAttributes.builder()
+ .setDescription("Use Bar instead!")
+ .setForRemoval(true)
+ .setSince("2.0")
+ .get();
+ // @formatter:on
+ assertEquals("Use Bar instead!", value.getDescription());
+ assertEquals("2.0", value.getSince());
+ assertEquals(true, value.isForRemoval());
+ }
+
+ @Test
+ public void testBuilderNonDefaultsToString() {
+ // @formatter:off
+ assertEquals("Deprecated for removal since 2.0: Use Bar instead!",
DeprecatedAttributes.builder()
+ .setDescription("Use Bar instead!")
+ .setForRemoval(true)
+ .setSince("2.0")
+ .get().toString());
+ assertEquals("Deprecated for removal: Use Bar instead!",
DeprecatedAttributes.builder()
+ .setDescription("Use Bar instead!")
+ .setForRemoval(true)
+ .get().toString());
+ assertEquals("Deprecated since 2.0: Use Bar instead!",
+ DeprecatedAttributes.builder()
+ .setDescription("Use Bar instead!")
+ .setSince("2.0")
+ .get().toString());
+ assertEquals("Deprecated: Use Bar instead!",
DeprecatedAttributes.builder()
+ .setDescription("Use Bar instead!")
+ .get().toString());
+ // @formatter:on
+ }
+
+ @Test
+ public void testDefaultBuilder() {
+ final DeprecatedAttributes defaultValue =
DeprecatedAttributes.builder().get();
+ assertEquals(DeprecatedAttributes.DEFAULT.getDescription(),
defaultValue.getDescription());
+ assertEquals(DeprecatedAttributes.DEFAULT.getSince(),
defaultValue.getSince());
+ assertEquals(DeprecatedAttributes.DEFAULT.isForRemoval(),
defaultValue.isForRemoval());
+ }
+
+ @Test
+ public void testDefaultToString() {
+ assertEquals("Deprecated", DeprecatedAttributes.DEFAULT.toString());
+ }
+}
diff --git a/src/test/java/org/apache/commons/cli/OptionTest.java
b/src/test/java/org/apache/commons/cli/OptionTest.java
index 4d5783d..68ec905 100644
--- a/src/test/java/org/apache/commons/cli/OptionTest.java
+++ b/src/test/java/org/apache/commons/cli/OptionTest.java
@@ -66,7 +66,8 @@ public class OptionTest {
}
private static void checkOption(final Option option, final String opt,
final String description, final String longOpt, final int numArgs,
- final String argName, final boolean required, final boolean
optionalArg, final char valueSeparator, final Class<?> cls) {
+ final String argName, final boolean required, final boolean
optionalArg, final char valueSeparator, final Class<?> cls, final String
deprecatedDesc,
+ final Boolean deprecatedForRemoval, final String deprecatedSince) {
assertEquals(opt, option.getOpt());
assertEquals(description, option.getDescription());
assertEquals(longOpt, option.getLongOpt());
@@ -77,6 +78,15 @@ public class OptionTest {
assertEquals(optionalArg, option.hasOptionalArg());
assertEquals(valueSeparator, option.getValueSeparator());
assertEquals(cls, option.getType());
+ if (deprecatedDesc != null) {
+ assertEquals(deprecatedDesc,
option.getDeprecated().getDescription());
+ }
+ if (deprecatedForRemoval != null) {
+ assertEquals(deprecatedForRemoval,
option.getDeprecated().isForRemoval());
+ }
+ if (deprecatedSince != null) {
+ assertEquals(deprecatedSince, option.getDeprecated().getSince());
+ }
}
private Option roundTrip(final Option o) throws IOException,
ClassNotFoundException {
@@ -122,31 +132,52 @@ public class OptionTest {
public void testBuilderMethods() {
final char defaultSeparator = (char) 0;
- checkOption(Option.builder("a").desc("desc").build(), "a", "desc",
null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class);
- checkOption(Option.builder("a").desc("desc").build(), "a", "desc",
null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class);
+ checkOption(Option.builder("a").desc("desc").build(), "a", "desc",
null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class,
null,
+ null, null);
+ checkOption(Option.builder("a").desc("desc").build(), "a", "desc",
null, Option.UNINITIALIZED, null, false, false, defaultSeparator, String.class,
null,
+ null, null);
checkOption(Option.builder("a").desc("desc").longOpt("aaa").build(),
"a", "desc", "aaa", Option.UNINITIALIZED, null, false, false, defaultSeparator,
- String.class);
- checkOption(Option.builder("a").desc("desc").hasArg(true).build(),
"a", "desc", null, 1, null, false, false, defaultSeparator, String.class);
+ String.class, null, null, null);
+ checkOption(Option.builder("a").desc("desc").hasArg(true).build(),
"a", "desc", null, 1, null, false, false, defaultSeparator, String.class, null,
null,
+ null);
checkOption(Option.builder("a").desc("desc").hasArg(false).build(),
"a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
- String.class);
- checkOption(Option.builder("a").desc("desc").hasArg(true).build(),
"a", "desc", null, 1, null, false, false, defaultSeparator, String.class);
- checkOption(Option.builder("a").desc("desc").numberOfArgs(3).build(),
"a", "desc", null, 3, null, false, false, defaultSeparator, String.class);
+ String.class, null, null, null);
+ checkOption(Option.builder("a").desc("desc").hasArg(true).build(),
"a", "desc", null, 1, null, false, false, defaultSeparator, String.class, null,
null,
+ null);
+ checkOption(Option.builder("a").desc("desc").numberOfArgs(3).build(),
"a", "desc", null, 3, null, false, false, defaultSeparator, String.class, null,
+ null, null);
checkOption(Option.builder("a").desc("desc").required(true).build(),
"a", "desc", null, Option.UNINITIALIZED, null, true, false, defaultSeparator,
- String.class);
+ String.class, null, null, null);
checkOption(Option.builder("a").desc("desc").required(false).build(),
"a", "desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
- String.class);
+ String.class, null, null, null);
checkOption(Option.builder("a").desc("desc").argName("arg1").build(),
"a", "desc", null, Option.UNINITIALIZED, "arg1", false, false, defaultSeparator,
- String.class);
+ String.class, null, null, null);
checkOption(Option.builder("a").desc("desc").optionalArg(false).build(), "a",
"desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
- String.class);
-
checkOption(Option.builder("a").desc("desc").optionalArg(true).build(), "a",
"desc", null, 1, null, false, true, defaultSeparator, String.class);
+ String.class, null, null, null);
+
checkOption(Option.builder("a").desc("desc").optionalArg(true).build(), "a",
"desc", null, 1, null, false, true, defaultSeparator, String.class, null,
+ null, null);
checkOption(Option.builder("a").desc("desc").valueSeparator(':').build(), "a",
"desc", null, Option.UNINITIALIZED, null, false, false, ':',
- String.class);
+ String.class, null, null, null);
checkOption(Option.builder("a").desc("desc").type(Integer.class).build(), "a",
"desc", null, Option.UNINITIALIZED, null, false, false, defaultSeparator,
- Integer.class);
+ Integer.class, null, null, null);
checkOption(Option.builder().option("a").desc("desc").type(Integer.class).build(),
"a", "desc", null, Option.UNINITIALIZED, null, false, false,
- defaultSeparator, Integer.class);
+ defaultSeparator, Integer.class, null, null, null);
+ // Deprecated
+
checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated().build(),
"a", "desc", null, Option.UNINITIALIZED, null, false,
+ false, defaultSeparator, Integer.class, "", false, "");
+
checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated(DeprecatedAttributes.builder().get()).build(),
"a", "desc", null,
+ Option.UNINITIALIZED, null, false, false, defaultSeparator,
Integer.class, "", false, "");
+
checkOption(Option.builder().option("a").desc("desc").type(Integer.class).deprecated(DeprecatedAttributes.builder().setDescription("X").get()).build(),
+ "a", "desc", null, Option.UNINITIALIZED, null, false, false,
defaultSeparator, Integer.class, "X", false, "");
+ checkOption(
+ Option.builder().option("a").desc("desc").type(Integer.class)
+
.deprecated(DeprecatedAttributes.builder().setDescription("X").setForRemoval(true).get()).build(),
+ "a", "desc", null, Option.UNINITIALIZED, null, false, false,
defaultSeparator, Integer.class, "X", true, "");
+ checkOption(
+ Option.builder().option("a").desc("desc").type(Integer.class)
+
.deprecated(DeprecatedAttributes.builder().setDescription("X").setForRemoval(true).setSince("2.0").get()).build(),
+ "a", "desc", null, Option.UNINITIALIZED, null, false, false,
defaultSeparator, Integer.class, "X", true, "2.0");
}
@Test
@@ -235,7 +266,6 @@ public class OptionTest {
@Test
public void testSerialization() throws IOException, ClassNotFoundException
{
-
final Option o =
Option.builder("o").type(TypeHandlerTest.Instantiable.class).build();
assertEquals(Converter.DEFAULT, o.getConverter());
Option o2 = roundTrip(o);
diff --git a/src/test/java/org/apache/commons/cli/OptionsTest.java
b/src/test/java/org/apache/commons/cli/OptionsTest.java
index 18fc74f..7b7b382 100644
--- a/src/test/java/org/apache/commons/cli/OptionsTest.java
+++ b/src/test/java/org/apache/commons/cli/OptionsTest.java
@@ -17,8 +17,10 @@
package org.apache.commons.cli;
+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -129,12 +131,28 @@ public class OptionsTest {
assertEquals("toggle -a*", opts.getOption("a").getDescription(), "last
one in wins");
}
+ @Test
+ public void testDeprecated() {
+ final Options opts = new Options();
+ opts.addOption(Option.builder().option("a").build());
+ opts.addOption(Option.builder().option("b").deprecated().build());
+ opts.addOption(Option.builder().option("c")
+
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("2.0").setDescription("Use
X.").get()).build());
+ // toString()
+ assertTrue(opts.getOption("a").toString().startsWith("[ Option a"));
+ assertTrue(opts.getOption("b").toString().startsWith("[ Option b"));
+ assertTrue(opts.getOption("c").toString().startsWith("[ Option c"));
+ // toDeprecatedString()
+
assertFalse(opts.getOption("a").toDeprecatedString().startsWith("Option a"));
+ assertEquals("Option 'b': Deprecated",
opts.getOption("b").toDeprecatedString());
+ assertEquals("Option 'c': Deprecated for removal since 2.0: Use X.",
opts.getOption("c").toDeprecatedString());
+ }
+
@Test
public void testDuplicateSimple() {
final Options opts = new Options();
opts.addOption("a", false, "toggle -a");
opts.addOption("a", true, "toggle -a*");
-
assertEquals("toggle -a*", opts.getOption("a").getDescription(), "last
one in wins");
}
diff --git a/src/test/java/org/apache/commons/cli/SolrCliTest.java
b/src/test/java/org/apache/commons/cli/SolrCliTest.java
new file mode 100644
index 0000000..0500100
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/SolrCliTest.java
@@ -0,0 +1,162 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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 org.apache.commons.cli;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test fixtures used in SOLR tests.
+ */
+class SolrCliTest {
+
+ public static final String ZK_HOST = "localhost:9983";
+
+ public static final String DEFAULT_CONFIG_SET = "_default";
+
+ public static final Option OPTION_ZKHOST_DEPRECATED =
+ // @formatter:off
+ Option.builder("zkHost")
+ .longOpt("zkHost")
+ .deprecated(
+ DeprecatedAttributes.builder()
+ .setForRemoval(true)
+ .setSince("9.6")
+ .setDescription("Use --zk-host instead")
+ .get())
+ .argName("HOST")
+ .hasArg()
+ .required(false)
+ .desc("Zookeeper connection string; unnecessary if ZK_HOST is
defined in solr.in.sh; otherwise, defaults to "
+ + ZK_HOST
+ + '.')
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_ZKHOST =
+ // @formatter:off
+ Option.builder("z")
+ .longOpt("zk-host")
+ .argName("HOST")
+ .hasArg()
+ .required(false)
+ .desc("Zookeeper connection string; unnecessary if ZK_HOST is
defined in solr.in.sh; otherwise, defaults to "
+ + ZK_HOST
+ + '.')
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_SOLRURL_DEPRECATED =
+ // @formatter:off
+ Option.builder("solrUrl")
+ .longOpt("solrUrl")
+ .deprecated(
+ DeprecatedAttributes.builder()
+ .setForRemoval(true)
+ .setSince("9.6")
+ .setDescription("Use --solr-url instead")
+ .get())
+ .argName("HOST")
+ .hasArg()
+ .required(false)
+ .desc("Base Solr URL, which can be used to determine the zk-host
if that's not known; defaults to: "
+ + getDefaultSolrUrl()
+ + '.')
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_SOLRURL =
+ // @formatter:off
+ Option.builder("url")
+ .longOpt("solr-url")
+ .argName("HOST")
+ .hasArg()
+ .required(false)
+ .desc("Base Solr URL, which can be used to determine the zk-host
if that's not known; defaults to: "
+ + getDefaultSolrUrl()
+ + '.')
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_VERBOSE =
+ // @formatter:off
+ Option.builder("v")
+ .longOpt("verbose")
+ .argName("verbose")
+ .required(false)
+ .desc("Enable more verbose command output.")
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_HELP =
+ // @formatter:off
+ Option.builder("h")
+ .longOpt("help")
+ .required(false)
+ .desc("Print this message.")
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_RECURSE =
+ // @formatter:off
+ Option.builder("r")
+ .longOpt("recurse")
+ .argName("recurse")
+ .hasArg()
+ .required(false)
+ .desc("Recurse (true|false), default is false.")
+ .build();
+ // @formatter:on
+
+ public static final Option OPTION_CREDENTIALS =
+ // @formatter:off
+ Option.builder("u")
+ .longOpt("credentials")
+ .argName("credentials")
+ .hasArg()
+ .required(false)
+ .desc("Credentials in the format username:password. Example:
--credentials solr:SolrRocks")
+ .build();
+ // @formatter:on
+
+ public static String getDefaultSolrUrl() {
+ final String scheme = "http";
+ final String host = "localhost";
+ final String port = "8983";
+ return String.format(Locale.ROOT, "%s://%s:%s",
scheme.toLowerCase(Locale.ROOT), host, port);
+ }
+
+ @Test
+ public void testOptions() {
+ // sanity checks
+ assertNotNull(DEFAULT_CONFIG_SET);
+ assertNotNull(OPTION_CREDENTIALS);
+ assertNotNull(OPTION_HELP);
+ assertNotNull(OPTION_RECURSE);
+ assertNotNull(OPTION_SOLRURL);
+ assertNotNull(OPTION_SOLRURL_DEPRECATED);
+ assertNotNull(OPTION_VERBOSE);
+ assertNotNull(OPTION_ZKHOST);
+ assertNotNull(OPTION_ZKHOST_DEPRECATED);
+ assertNotNull(ZK_HOST);
+ assertNotNull(getDefaultSolrUrl());
+ }
+}
diff --git a/src/test/java/org/apache/commons/cli/SolrCreateToolTest.java
b/src/test/java/org/apache/commons/cli/SolrCreateToolTest.java
new file mode 100644
index 0000000..6dcc70d
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/SolrCreateToolTest.java
@@ -0,0 +1,106 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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 org.apache.commons.cli;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+public class SolrCreateToolTest {
+
+ public List<Option> getOptions() {
+ // @formatter:off
+ return Arrays.asList(
+ SolrCliTest.OPTION_ZKHOST,
+ SolrCliTest.OPTION_SOLRURL,
+ SolrCliTest.OPTION_ZKHOST_DEPRECATED,
+ SolrCliTest.OPTION_SOLRURL,
+ Option.builder("c")
+ .longOpt("name")
+ .argName("NAME")
+ .hasArg()
+ .required(true)
+ .desc("Name of collection or core to create.")
+ .build(),
+ Option.builder("s")
+ .longOpt("shards")
+ .argName("#")
+ .hasArg()
+ .required(false)
+ .desc("Number of shards; default is 1.")
+ .build(),
+ Option.builder("rf")
+ .longOpt("replication-factor")
+ .argName("#")
+ .hasArg()
+ .required(false)
+ .desc("Number of copies of each document across the collection
(replicas per shard); default is 1.")
+ .build(),
+ Option.builder("d")
+ .longOpt("confdir")
+ .argName("NAME")
+ .hasArg()
+ .required(false)
+ .desc("Configuration directory to copy when creating the new
collection; default is "
+ + SolrCliTest.DEFAULT_CONFIG_SET
+ + '.')
+ .build(),
+ Option.builder("n")
+ .longOpt("confname")
+ .argName("NAME")
+ .hasArg()
+ .required(false)
+ .desc("Configuration name; default is the collection name.")
+ .build(),
+ SolrCliTest.OPTION_CREDENTIALS);
+ // @formatter:on
+ }
+
+ private String printHelp(final HelpFormatter formatter) {
+ final Options options = new Options();
+ getOptions().forEach(options::addOption);
+ final String cmdLineSyntax = getClass().getName();
+ final StringWriter out = new StringWriter();
+ final PrintWriter pw = new PrintWriter(out);
+ formatter.printHelp(pw, formatter.getWidth(), cmdLineSyntax, null,
options, formatter.getLeftPadding(), formatter.getDescPadding(), null, false);
+ pw.flush();
+ final String actual = out.toString();
+ assertTrue(actual.contains("-z,--zk-host <HOST> Zookeeper
connection string; unnecessary"));
+ return actual;
+ }
+
+ @Test
+ public void testHelpFormatter() {
+ final HelpFormatter formatter = new HelpFormatter();
+ final String actual = printHelp(formatter);
+ assertFalse(actual.contains("Deprecated"));
+ }
+
+ @Test
+ public void testHelpFormatterDeprecated() {
+ final HelpFormatter formatter =
HelpFormatter.builder().setShowDeprecated(true).get();
+ final String actual = printHelp(formatter);
+ assertTrue(actual.contains("-zkHost,--zkHost <HOST>
[Deprecated] Zookeeper connection"));
+ }
+}