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 <garydgreg...@users.noreply.github.com>
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"));
+    }
+}

Reply via email to