From a1d4bc5726bbac54d256b41b2201ba14ae8da89d Mon Sep 17 00:00:00 2001
From: Joerg Budischewski
Date: Sun, 20 Jul 2025 18:11:44 +0200
Subject: [PATCH 1/5] [cli-221] added new Option.Builder.listValueSeparator()
to allow to properly use options with single argument-commaseparated values.
To remain backward compatibility to the java-property-style parsing wth
default valueSeparator '=', this mutually exclusive new method needed to be
added
---
.../java/org/apache/commons/cli/Char.java | 3 +
.../org/apache/commons/cli/DefaultParser.java | 3 +
.../java/org/apache/commons/cli/Option.java | 68 ++++++++++++++++++-
.../apache/commons/cli/DefaultParserTest.java | 65 ++++++++++++++++++
.../org/apache/commons/cli/OptionTest.java | 24 +++++++
5 files changed, 162 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/apache/commons/cli/Char.java b/src/main/java/org/apache/commons/cli/Char.java
index 9b6e5e2ab..a38ece8bf 100644
--- a/src/main/java/org/apache/commons/cli/Char.java
+++ b/src/main/java/org/apache/commons/cli/Char.java
@@ -40,6 +40,9 @@ final class Char {
/** Tab. */
static final char TAB = '\t';
+ /** Comma. */
+ static final char COMMA = ',';
+
private Char() {
// empty
}
diff --git a/src/main/java/org/apache/commons/cli/DefaultParser.java b/src/main/java/org/apache/commons/cli/DefaultParser.java
index c219113f8..7157ad0b6 100644
--- a/src/main/java/org/apache/commons/cli/DefaultParser.java
+++ b/src/main/java/org/apache/commons/cli/DefaultParser.java
@@ -609,6 +609,9 @@ private void handleToken(final String token) throws ParseException {
skipParsing = true;
} else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) {
currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOn(token));
+ if (currentOption.areValuesAsList()) {
+ currentOption = null;
+ }
} else if (token.startsWith("--")) {
handleLongOption(token);
} else if (token.startsWith("-") && !"-".equals(token)) {
diff --git a/src/main/java/org/apache/commons/cli/Option.java b/src/main/java/org/apache/commons/cli/Option.java
index 155a70291..acc2f2bc8 100644
--- a/src/main/java/org/apache/commons/cli/Option.java
+++ b/src/main/java/org/apache/commons/cli/Option.java
@@ -102,6 +102,9 @@ private static Class> toType(final Class> type) {
/** The character that is the value separator. */
private char valueSeparator;
+ /** multiple values are within a single argument separated by valueSeparator char */
+ private boolean valuesAsList;
+
/**
* Constructs a new {@code Builder} with the minimum required parameters for an {@code Option} instance.
*
@@ -315,7 +318,9 @@ public Builder valueSeparator() {
}
/**
- * The Option will use {@code sep} as a means to separate argument values.
+ * The Option will use {@code sep} as a means to separate java-property-style argument values
+ *
+ * Method is mutually exclusive to listValueSeparator() method.
*
* Example:
*
@@ -331,6 +336,10 @@ public Builder valueSeparator() {
* String propertyValue = line.getOptionValues("D")[1]; // will be "value"
*
*
+ * In the above example (unlimited args), followup arguments are interpreted
+ * to be additional values to this option, needs to be terminated with -- so that
+ * others options or args can follow.
+ *
* @param valueSeparator The value separator.
* @return this builder.
*/
@@ -339,6 +348,49 @@ public Builder valueSeparator(final char valueSeparator) {
return this;
}
+ /**
+ * The Option will use ',' to invoke listValueSeparator()
+ *
+ * @since 1.10.0
+ * @return this builder.
+ */
+ public Builder listValueSeparator() {
+ return listValueSeparator(Char.COMMA);
+ }
+
+ /**
+ * defines the separator used to separate a list of values passed in a single arg
+ *
+ * Method is mutually exclusive to valueSeparator() method.
+ *
+ * Example:
+ *
+ *
+ *
+ * final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator('|').build();
+ * final Options options = new Options();
+ * options.addOption(colors);
+ *
+ * final String[] args = {"-c", "red|blue|yellow", "b,c"};
+ * final DefaultParser parser = new DefaultParser();
+ * final CommandLine commandLine = parser.parse(options, args, null, true);
+ * String [] colorValues = commandLine.getOptionValues(colors);
+ * // colorValues[0] will be "red"
+ * // colorValues[1] will be "blue"
+ * // colorValues[2] will be "yellow"
+ * String arguments = commandLine.getArgs()[0]; // will be b,c
+ *
+ *
+ *
+ * @since 1.10.0
+ * @param listValueSeparator The char to be used to split the argument into mulitple values.
+ * @return this builder.
+ */
+ public Builder listValueSeparator(final char listValueSeparator) {
+ this.valueSeparator = listValueSeparator;
+ this.valuesAsList = true;
+ return this;
+ }
}
/** Empty array. */
@@ -419,6 +471,9 @@ public static Builder builder(final String option) {
/** The character that is the value separator. */
private char valueSeparator;
+ /** multiple values are within a single argument separated by valueSeparator char */
+ private boolean valuesAsList;
+
/**
* Private constructor used by the nested Builder class.
*
@@ -437,6 +492,7 @@ private Option(final Builder builder) {
this.type = builder.type;
this.valueSeparator = builder.valueSeparator;
this.converter = builder.converter;
+ this.valuesAsList = builder.valuesAsList;
}
/**
@@ -820,6 +876,16 @@ public boolean isRequired() {
return required;
}
+ /**
+ * Tests whether multiple values are expected in a single argument separated by a separation character
+ *
+ * @return boolean true when multiple values are expected in a single separated by a separation character
+ * @since 1.10.0
+ */
+ public boolean areValuesAsList() {
+ return valuesAsList;
+ }
+
/**
* 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.
diff --git a/src/test/java/org/apache/commons/cli/DefaultParserTest.java b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
index de52a8600..0e67737d9 100644
--- a/src/test/java/org/apache/commons/cli/DefaultParserTest.java
+++ b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
@@ -35,6 +35,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.junit.jupiter.params.provider.ValueSource;
class DefaultParserTest extends AbstractParserTestCase {
@@ -329,6 +330,70 @@ void legacyStopAtNonOption() throws ParseException {
assertTrue(e.getMessage().contains("-d"));
}
+ @Test
+ void listValueSeparatorTest() throws ParseException {
+ final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator('|').build();
+ final Options options = new Options();
+ options.addOption(colors);
+
+ final String[] args = {"-c", "red|blue|yellow", "b,c"};
+ final DefaultParser parser = new DefaultParser();
+ final CommandLine commandLine = parser.parse(options, args, null, true);
+ String [] colorValues = commandLine.getOptionValues(colors);
+ assertEquals(3, colorValues.length );
+ assertEquals("red", colorValues[0]);
+ assertEquals("blue", colorValues[1]);
+ assertEquals("yellow", colorValues[2]);
+ assertEquals("b,c", commandLine.getArgs()[0]);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "--colors=red,blue,yellow b",
+ "--colors red,blue,yellow b" ,
+ "-c=red,blue,yellow b" ,
+ "-c red,blue,yellow b" })
+ void listValueSeparatorDefaultTest(String args) throws ParseException {
+ final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator().build();
+ final Options options = new Options();
+ options.addOption(colors);
+
+ final DefaultParser parser = new DefaultParser();
+ final CommandLine commandLine = parser.parse(options, args.split(" "), null, true);
+ String [] colorValues = commandLine.getOptionValues(colors);
+ assertEquals(3, colorValues.length );
+ assertEquals("red", colorValues[0]);
+ assertEquals("blue", colorValues[1]);
+ assertEquals("yellow", colorValues[2]);
+ assertEquals("b", commandLine.getArgs()[0]);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "--colors=red,blue,yellow -f bar b",
+ "-f bar --colors=red,blue,yellow b",
+ "b --colors=red,blue,yellow -f bar",
+ "b --colors=red -c blue --colors=yellow -f bar",
+ })
+ void listValueSeparatorSeriesDoesntMatter(final String args) throws ParseException {
+ final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator().build();
+ final Option foo = Option.builder().option("f").hasArg().build();
+ final Options options = new Options();
+ options.addOption(colors);
+ options.addOption(foo);
+
+ final DefaultParser parser = new DefaultParser();
+ final CommandLine commandLine = parser.parse(options, args.split(" "), null, false);
+ final String [] colorValues = commandLine.getOptionValues(colors);
+ final String fooValue = commandLine.getOptionValue(foo);
+ assertEquals(3, colorValues.length );
+ assertEquals("red", colorValues[0]);
+ assertEquals("blue", colorValues[1]);
+ assertEquals("yellow", colorValues[2]);
+ assertEquals("bar", fooValue);
+ assertEquals("b", commandLine.getArgs()[0]);
+ }
+
@Override
@BeforeEach
public void setUp() {
diff --git a/src/test/java/org/apache/commons/cli/OptionTest.java b/src/test/java/org/apache/commons/cli/OptionTest.java
index ec0cb100a..0781f8314 100644
--- a/src/test/java/org/apache/commons/cli/OptionTest.java
+++ b/src/test/java/org/apache/commons/cli/OptionTest.java
@@ -344,4 +344,28 @@ void testTypeObject() {
option.setType(type);
assertEquals(CharSequence.class, option.getType());
}
+
+ @Test
+ void testDefaultValueSeparator() {
+ final Option option = Option.builder().option("a").hasArgs().valueSeparator().build();
+ assertFalse(option.areValuesAsList());
+ assertTrue(option.hasValueSeparator());
+ assertEquals('=',option.getValueSeparator());
+ }
+
+ @Test
+ void testDefaultValueAsList() {
+ final Option option = Option.builder().option("a").hasArgs().listValueSeparator().build();
+ assertTrue(option.areValuesAsList());
+ assertTrue(option.hasValueSeparator());
+ assertEquals(',',option.getValueSeparator());
+ }
+
+ @Test
+ void testValueAsList() {
+ final Option option = Option.builder().option("a").hasArgs().listValueSeparator('|').build();
+ assertTrue(option.areValuesAsList());
+ assertTrue(option.hasValueSeparator());
+ assertEquals('|',option.getValueSeparator());
+ }
}
From b0b1985abfbc97cb05e8131011b0dce803b72379 Mon Sep 17 00:00:00 2001
From: Joerg Budischewski
Date: Sun, 20 Jul 2025 19:56:41 +0200
Subject: [PATCH 2/5] [cli-221] checkstyle fixes, option attribute renamed,
documentation fixes
---
.../org/apache/commons/cli/DefaultParser.java | 2 +-
.../java/org/apache/commons/cli/Option.java | 43 ++++++++++++-------
.../apache/commons/cli/DefaultParserTest.java | 18 ++++----
.../org/apache/commons/cli/OptionTest.java | 24 +++++++----
4 files changed, 53 insertions(+), 34 deletions(-)
diff --git a/src/main/java/org/apache/commons/cli/DefaultParser.java b/src/main/java/org/apache/commons/cli/DefaultParser.java
index 7157ad0b6..bbd63c7fc 100644
--- a/src/main/java/org/apache/commons/cli/DefaultParser.java
+++ b/src/main/java/org/apache/commons/cli/DefaultParser.java
@@ -609,7 +609,7 @@ private void handleToken(final String token) throws ParseException {
skipParsing = true;
} else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) {
currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOn(token));
- if (currentOption.areValuesAsList()) {
+ if (currentOption.isValueSeparatorUsedForSingleArgument()) {
currentOption = null;
}
} else if (token.startsWith("--")) {
diff --git a/src/main/java/org/apache/commons/cli/Option.java b/src/main/java/org/apache/commons/cli/Option.java
index acc2f2bc8..a4a326bc8 100644
--- a/src/main/java/org/apache/commons/cli/Option.java
+++ b/src/main/java/org/apache/commons/cli/Option.java
@@ -103,7 +103,7 @@ private static Class> toType(final Class> type) {
private char valueSeparator;
/** multiple values are within a single argument separated by valueSeparator char */
- private boolean valuesAsList;
+ private boolean valueSeparatorUsedForSingleArgument;
/**
* Constructs a new {@code Builder} with the minimum required parameters for an {@code Option} instance.
@@ -318,7 +318,7 @@ public Builder valueSeparator() {
}
/**
- * The Option will use {@code sep} as a means to separate java-property-style argument values
+ * The Option will use {@code sep} as a means to separate java-property-style argument values.
*
* Method is mutually exclusive to listValueSeparator() method.
*
@@ -336,7 +336,7 @@ public Builder valueSeparator() {
* String propertyValue = line.getOptionValues("D")[1]; // will be "value"
*
*
- * In the above example (unlimited args), followup arguments are interpreted
+ * In the above example, followup arguments are interpreted
* to be additional values to this option, needs to be terminated with -- so that
* others options or args can follow.
*
@@ -359,26 +359,28 @@ public Builder listValueSeparator() {
}
/**
- * defines the separator used to separate a list of values passed in a single arg
+ * defines the separator used to split a list of values passed in a single argument.
+ *
+ * Method is mutually exclusive to valueSeparator() method. In the resulting option,
+ * isValueSeparatorUsedForSingleArgument() will return true.
*
- * Method is mutually exclusive to valueSeparator() method.
*
* Example:
*
*
*
- * final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator('|').build();
+ * final Option colors = Option.builder().option("c").hasArgs().listValueSeparator('|').build();
* final Options options = new Options();
* options.addOption(colors);
*
* final String[] args = {"-c", "red|blue|yellow", "b,c"};
* final DefaultParser parser = new DefaultParser();
* final CommandLine commandLine = parser.parse(options, args, null, true);
- * String [] colorValues = commandLine.getOptionValues(colors);
+ * final String [] colorValues = commandLine.getOptionValues(colors);
* // colorValues[0] will be "red"
* // colorValues[1] will be "blue"
* // colorValues[2] will be "yellow"
- * String arguments = commandLine.getArgs()[0]; // will be b,c
+ * final String arguments = commandLine.getArgs()[0]; // will be b,c
*
*
*
@@ -388,7 +390,7 @@ public Builder listValueSeparator() {
*/
public Builder listValueSeparator(final char listValueSeparator) {
this.valueSeparator = listValueSeparator;
- this.valuesAsList = true;
+ this.valueSeparatorUsedForSingleArgument = true;
return this;
}
}
@@ -472,7 +474,7 @@ public static Builder builder(final String option) {
private char valueSeparator;
/** multiple values are within a single argument separated by valueSeparator char */
- private boolean valuesAsList;
+ private boolean valueSeparatorUsedForSingleArgument;
/**
* Private constructor used by the nested Builder class.
@@ -492,7 +494,7 @@ private Option(final Builder builder) {
this.type = builder.type;
this.valueSeparator = builder.valueSeparator;
this.converter = builder.converter;
- this.valuesAsList = builder.valuesAsList;
+ this.valueSeparatorUsedForSingleArgument = builder.valueSeparatorUsedForSingleArgument;
}
/**
@@ -877,13 +879,24 @@ public boolean isRequired() {
}
/**
- * Tests whether multiple values are expected in a single argument separated by a separation character
+ * Tests whether multiple values are expected in a single argument split by a separation character
+ *
+ * @return boolean true when the builder's listValueSeparator() method was used. Multiple values are expected in a single argument and
+ * are split by a separation character.
+ * @since 1.10.0
+ */
+ public boolean isValueSeparatorUsedForSingleArgument() {
+ return valueSeparatorUsedForSingleArgument;
+ }
+
+ /**
+ * Set this to true to use the valueSeparator only on a single argument. See also builder's listValueSeparator() method.
*
- * @return boolean true when multiple values are expected in a single separated by a separation character
+ * @param valueSeparatorUsedForSingleArgument the new value for this property
* @since 1.10.0
*/
- public boolean areValuesAsList() {
- return valuesAsList;
+ public void setValueSeparatorUsedForSingleArgument(final boolean valueSeparatorUsedForSingleArgument) {
+ this.valueSeparatorUsedForSingleArgument = valueSeparatorUsedForSingleArgument;
}
/**
diff --git a/src/test/java/org/apache/commons/cli/DefaultParserTest.java b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
index 0e67737d9..86a6763b4 100644
--- a/src/test/java/org/apache/commons/cli/DefaultParserTest.java
+++ b/src/test/java/org/apache/commons/cli/DefaultParserTest.java
@@ -339,8 +339,8 @@ void listValueSeparatorTest() throws ParseException {
final String[] args = {"-c", "red|blue|yellow", "b,c"};
final DefaultParser parser = new DefaultParser();
final CommandLine commandLine = parser.parse(options, args, null, true);
- String [] colorValues = commandLine.getOptionValues(colors);
- assertEquals(3, colorValues.length );
+ final String [] colorValues = commandLine.getOptionValues(colors);
+ assertEquals(3, colorValues.length);
assertEquals("red", colorValues[0]);
assertEquals("blue", colorValues[1]);
assertEquals("yellow", colorValues[2]);
@@ -350,18 +350,18 @@ void listValueSeparatorTest() throws ParseException {
@ParameterizedTest
@ValueSource(strings = {
"--colors=red,blue,yellow b",
- "--colors red,blue,yellow b" ,
- "-c=red,blue,yellow b" ,
- "-c red,blue,yellow b" })
- void listValueSeparatorDefaultTest(String args) throws ParseException {
+ "--colors red,blue,yellow b",
+ "-c=red,blue,yellow b",
+ "-c red,blue,yellow b"})
+ void listValueSeparatorDefaultTest(final String args) throws ParseException {
final Option colors = Option.builder().option("c").longOpt("colors").hasArgs().listValueSeparator().build();
final Options options = new Options();
options.addOption(colors);
final DefaultParser parser = new DefaultParser();
final CommandLine commandLine = parser.parse(options, args.split(" "), null, true);
- String [] colorValues = commandLine.getOptionValues(colors);
- assertEquals(3, colorValues.length );
+ final String [] colorValues = commandLine.getOptionValues(colors);
+ assertEquals(3, colorValues.length);
assertEquals("red", colorValues[0]);
assertEquals("blue", colorValues[1]);
assertEquals("yellow", colorValues[2]);
@@ -386,7 +386,7 @@ void listValueSeparatorSeriesDoesntMatter(final String args) throws ParseExcepti
final CommandLine commandLine = parser.parse(options, args.split(" "), null, false);
final String [] colorValues = commandLine.getOptionValues(colors);
final String fooValue = commandLine.getOptionValue(foo);
- assertEquals(3, colorValues.length );
+ assertEquals(3, colorValues.length);
assertEquals("red", colorValues[0]);
assertEquals("blue", colorValues[1]);
assertEquals("yellow", colorValues[2]);
diff --git a/src/test/java/org/apache/commons/cli/OptionTest.java b/src/test/java/org/apache/commons/cli/OptionTest.java
index 0781f8314..779194d84 100644
--- a/src/test/java/org/apache/commons/cli/OptionTest.java
+++ b/src/test/java/org/apache/commons/cli/OptionTest.java
@@ -348,24 +348,30 @@ void testTypeObject() {
@Test
void testDefaultValueSeparator() {
final Option option = Option.builder().option("a").hasArgs().valueSeparator().build();
- assertFalse(option.areValuesAsList());
+ assertFalse(option.isValueSeparatorUsedForSingleArgument());
assertTrue(option.hasValueSeparator());
- assertEquals('=',option.getValueSeparator());
+ assertEquals('=', option.getValueSeparator());
}
@Test
- void testDefaultValueAsList() {
+ void testDefaultListValueSeparator() {
final Option option = Option.builder().option("a").hasArgs().listValueSeparator().build();
- assertTrue(option.areValuesAsList());
+ assertTrue(option.isValueSeparatorUsedForSingleArgument());
assertTrue(option.hasValueSeparator());
- assertEquals(',',option.getValueSeparator());
+ assertEquals(',', option.getValueSeparator());
}
-
+
@Test
- void testValueAsList() {
+ void testListValueSeparator() {
final Option option = Option.builder().option("a").hasArgs().listValueSeparator('|').build();
- assertTrue(option.areValuesAsList());
+ assertTrue(option.isValueSeparatorUsedForSingleArgument());
+ assertTrue(option.hasValueSeparator());
+ assertEquals('|', option.getValueSeparator());
+
+ option.setValueSeparatorUsedForSingleArgument(false);
+ assertFalse(option.isValueSeparatorUsedForSingleArgument());
assertTrue(option.hasValueSeparator());
- assertEquals('|',option.getValueSeparator());
+ assertEquals('|', option.getValueSeparator());
+
}
}
From ceacf036b08a7d477e15807492639ecfdd22c89f Mon Sep 17 00:00:00 2001
From: Joerg Budischewski
Date: Wed, 30 Jul 2025 23:18:25 +0200
Subject: [PATCH 3/5] [cli-221] resolved merge conflict from master
---
.github/pull_request_template.md | 6 +-
.github/workflows/codeql-analysis.yml | 6 +-
.github/workflows/scorecards-analysis.yml | 2 +-
CONTRIBUTING.md | 4 +-
README.md | 2 +-
RELEASE-NOTES.txt | 62 ++
pom.xml | 2 +-
src/changes/changes.xml | 13 +-
.../commons/cli/AlreadySelectedException.java | 16 +-
.../org/apache/commons/cli/DefaultParser.java | 92 +-
.../org/apache/commons/cli/HelpFormatter.java | 10 +-
.../commons/cli/MissingOptionException.java | 17 +-
.../java/org/apache/commons/cli/Option.java | 42 +-
.../org/apache/commons/cli/OptionBuilder.java | 2 +-
.../org/apache/commons/cli/OptionGroup.java | 20 +-
.../apache/commons/cli/OptionValidator.java | 2 +-
.../java/org/apache/commons/cli/Options.java | 98 ++-
.../java/org/apache/commons/cli/Parser.java | 16 +-
.../commons/cli/PatternOptionBuilder.java | 4 +-
.../cli/help/AbstractHelpFormatter.java | 10 +-
src/main/javadoc/overview.html | 791 +++++++++++++++++-
src/site/site.xml | 10 +-
src/site/xdoc/download_cli.xml | 8 +-
src/site/xdoc/introduction.xml | 102 ---
src/site/xdoc/properties.xml | 122 ---
src/site/xdoc/usage.xml | 613 --------------
.../commons/cli/AbstractParserTestCase.java | 60 +-
.../cli/AlreadySelectedExceptionTest.java | 8 +-
.../apache/commons/cli/CommandLineTest.java | 66 +-
.../apache/commons/cli/DefaultParserTest.java | 198 +++--
.../apache/commons/cli/HelpFormatterTest.java | 91 +-
.../cli/MissingOptionExceptionTest.java | 51 ++
.../apache/commons/cli/OptionBuilderTest.java | 4 +-
.../apache/commons/cli/OptionGroupTest.java | 60 +-
.../org/apache/commons/cli/OptionTest.java | 73 +-
.../org/apache/commons/cli/OptionsTest.java | 107 ++-
.../org/apache/commons/cli/SolrCliTest.java | 122 ++-
.../commons/cli/SolrCreateToolTest.java | 84 +-
.../org/apache/commons/cli/ValueTest.java | 2 +-
.../apache/commons/cli/bug/BugCLI252Test.java | 4 +-
.../apache/commons/cli/bug/BugCLI265Test.java | 8 +-
.../apache/commons/cli/bug/BugCLI266Test.java | 71 +-
.../apache/commons/cli/bug/BugCLI312Test.java | 8 +-
.../apache/commons/cli/bug/BugCLI325Test.java | 7 +-
.../org/apache/commons/cli/bug/BugsTest.java | 8 +-
.../commons/cli/help/HelpFormatterTest.java | 110 +--
.../commons/cli/help/OptionFormatterTest.java | 67 +-
47 files changed, 1705 insertions(+), 1576 deletions(-)
delete mode 100644 src/site/xdoc/introduction.xml
delete mode 100644 src/site/xdoc/properties.xml
delete mode 100644 src/site/xdoc/usage.xml
create mode 100644 src/test/java/org/apache/commons/cli/MissingOptionExceptionTest.java
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index e17973cb0..7578b4da0 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -22,7 +22,9 @@ Thanks for your contribution to [Apache Commons](https://commons.apache.org/)! Y
Before you push a pull request, review this list:
- [ ] Read the [contribution guidelines](CONTRIBUTING.md) for this project.
+- [ ] Read the [ASF Generative Tooling Guidance](https://www.apache.org/legal/generative-tooling.html) if you use Artificial Intelligence (AI).
+- [ ] I used AI to create any part of, or all of, this pull request.
- [ ] Run a successful build using the default [Maven](https://maven.apache.org/) goal with `mvn`; that's `mvn` on the command line by itself.
-- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible but is a best-practice.
+- [ ] Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best-practice.
- [ ] Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
-- [ ] Each commit in the pull request should have a meaningful subject line and body. Note that commits might be squashed by a maintainer on merge.
+- [ ] Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 5bb1f901d..7400fb888 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # 3.29.4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -68,7 +68,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # 3.29.4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -82,4 +82,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # 3.29.4
diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml
index c52dbd34e..325206c18 100644
--- a/.github/workflows/scorecards-analysis.yml
+++ b/.github/workflows/scorecards-analysis.yml
@@ -64,6 +64,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # 3.29.2
+ uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # 3.29.4
with:
sarif_file: results.sarif
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 927ab3f6f..e4440b2a2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -61,11 +61,11 @@ Making Changes
+ Create a _topic branch_ for your isolated work.
* Usually you should base your branch from the `master` branch.
- * A good topic branch name can be the JIRA bug ID plus a keyword, for example, `CLI-123-InputStream`.
+ * A good topic branch name can be the JIRA bug ID plus a keyword, e.g. `CLI-123-InputStream`.
* If you have submitted multiple JIRA issues, try to maintain separate branches and pull requests.
+ Make commits of logical units.
* Make sure your commit messages are meaningful and in the proper format. Your commit message should contain the key of the JIRA issue.
- * For example, `[CLI-123] Close input stream earlier`
+ * For example, `[CLI-123] Close input stream sooner`
+ Respect the original code style:
+ Only use spaces for indentation; you can check for unnecessary whitespace with `git diff` before committing.
+ Create minimal diffs - disable _On Save_ actions like _Reformat Source Code_ or _Organize Imports_. If you feel the source code should be reformatted create a separate PR for this change first.
diff --git a/README.md b/README.md
index 2c6c46fbb..7c56440fc 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ There are some guidelines which will make applying PRs easier for us:
+ Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change.
+ Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running `mvn`.
+ Before you pushing a PR, run `mvn` (by itself), this runs the default goal, which contains all build checks.
-+ To see the code coverage report, regardless of coverage failures, run `mvn clean site -Dcommons.jacoco.haltOnFailure=false`
++ To see the code coverage report, regardless of coverage failures, run `mvn clean site -Dcommons.jacoco.haltOnFailure=false -Pjacoco`
If you plan to contribute on a regular basis, please consider filing a [contributor license agreement](https://www.apache.org/licenses/#clas).
You can learn more about contributing via GitHub in our [contribution guidelines](CONTRIBUTING.md).
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 48cc3f083..332b23281 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,11 +1,73 @@
Apache Commons CLI 1.10.0 Release Notes
---------------------------------------
+The Apache Commons CLI team is pleased to announce the release of Apache Commons CLI 1.10.0.
+
Apache Commons CLI provides a simple API for presenting, processing, and validating a Command Line Interface.
This is a feature and maintenance release. Java 8 or later is required.
+New Features
+------------
+
+* CLI-339: Help formatter extension in the new package #314. Thanks to Claude Warren, Gary Gregory.
+* CommandLine.Builder implements Supplier. Thanks to Gary Gregory.
+* DefaultParser.Builder implements Supplier. Thanks to Gary Gregory.
+* CLI-340: Add CommandLine.getParsedOptionValues() #334. Thanks to Claude Warren, Gary Gregory.
+* CLI-333: org.apache.commons.cli.Option.Builder implements Supplier
*
* @param converter the Converter to use.
- * @return this builder, to allow method chaining.
+ * @return {@code this} instance..
* @since 1.7.0
*/
public Builder converter(final Converter, ?> converter) {
@@ -180,17 +180,28 @@ public Builder deprecated(final DeprecatedAttributes deprecated) {
* Sets the description for this option.
*
* @param description the description of the option.
- * @return this builder, to allow method chaining.
+ * @return {@code this} instance..
*/
public Builder desc(final String description) {
this.description = description;
return this;
}
+ /**
+ * Constructs an Option with the values declared by this {@link Builder}.
+ *
+ * @return the new {@link Option}.
+ * @throws IllegalStateException if neither {@code opt} or {@code longOpt} has been set.
+ */
+ @Override
+ public Option get() {
+ return new Option(this);
+ }
+
/**
* Tests whether the Option will require an argument.
*
- * @return this builder, to allow method chaining.
+ * @return {@code this} instance..
*/
public Builder hasArg() {
return hasArg(true);
@@ -200,7 +211,7 @@ public Builder hasArg() {
* Tests whether the Option has an argument or not.
*
* @param hasArg specifies whether the Option takes an argument or not.
- * @return this builder, to allow method chaining.
+ * @return {@code this} instance..
*/
public Builder hasArg(final boolean hasArg) {
// set to UNINITIALIZED when no arg is specified to be compatible with OptionBuilder
@@ -480,8 +491,12 @@ public static Builder builder(final String option) {
* Private constructor used by the nested Builder class.
*
* @param builder builder used to create this option.
+ * @throws IllegalStateException if neither {@code opt} or {@code longOpt} has been set.
*/
private Option(final Builder builder) {
+ if (builder.option == null && builder.longOption == null) {
+ throw new IllegalStateException("Either opt or longOpt must be specified");
+ }
this.argName = builder.argName;
this.description = builder.description;
this.longOption = builder.longOption;
@@ -575,8 +590,7 @@ private void add(final String value) {
*/
@Deprecated
public boolean addValue(final String value) {
- throw new UnsupportedOperationException(
- "The addValue method is not intended for client use. Subclasses should use the processValue method instead.");
+ throw new UnsupportedOperationException("The addValue method is not intended for client use. Subclasses should use the processValue method instead.");
}
/**
@@ -907,9 +921,9 @@ public void setValueSeparatorUsedForSingleArgument(final boolean valueSeparatorU
*/
void processValue(final String value) {
if (argCount == UNINITIALIZED) {
- throw new IllegalArgumentException("NO_ARGS_ALLOWED");
+ throw new IllegalStateException("NO_ARGS_ALLOWED");
}
- String add = value;
+ String add = Objects.requireNonNull(value, "value");
// this Option has a separator character
if (hasValueSeparator()) {
// get the separator character
diff --git a/src/main/java/org/apache/commons/cli/OptionBuilder.java b/src/main/java/org/apache/commons/cli/OptionBuilder.java
index 2d44ff027..41ff9b63f 100644
--- a/src/main/java/org/apache/commons/cli/OptionBuilder.java
+++ b/src/main/java/org/apache/commons/cli/OptionBuilder.java
@@ -72,7 +72,7 @@ public final class OptionBuilder {
public static Option create() throws IllegalArgumentException {
if (longOption == null) {
reset();
- throw new IllegalArgumentException("must specify longopt");
+ throw new IllegalStateException("must specify longopt");
}
return create(null);
diff --git a/src/main/java/org/apache/commons/cli/OptionGroup.java b/src/main/java/org/apache/commons/cli/OptionGroup.java
index 945ca9f20..19e6825fe 100644
--- a/src/main/java/org/apache/commons/cli/OptionGroup.java
+++ b/src/main/java/org/apache/commons/cli/OptionGroup.java
@@ -31,13 +31,15 @@ public class OptionGroup implements Serializable {
/** The serial version UID. */
private static final long serialVersionUID = 1L;
- /** Hold the options */
+ /**
+ * Maps options where keys are option name and values are the options.
+ */
private final Map optionMap = new LinkedHashMap<>();
- /** The name of the selected option */
+ /** The name of the selected option. */
private String selected;
- /** Specified whether this group is required */
+ /** Specified whether this group is required. */
private boolean required;
/**
@@ -50,12 +52,10 @@ public OptionGroup() {
/**
* Adds the given {@code Option} to this group.
*
- * @param option the option to add to this group
- * @return this option group with the option added
+ * @param option the option to add to this group.
+ * @return this option group with the option added.
*/
public OptionGroup addOption(final Option option) {
- // key - option name
- // value - the option
optionMap.put(option.getKey(), option);
return this;
}
@@ -93,7 +93,7 @@ public String getSelected() {
/**
* Tests whether this option group is required.
*
- * @return whether this option group is required
+ * @return whether this option group is required.
*/
public boolean isRequired() {
return required;
@@ -123,7 +123,7 @@ public void setRequired(final boolean required) {
* Sets the selected option of this group to {@code name}.
*
* If the selected option is deprecated no warning is logged.
- * @param option the option that is selected
+ * @param option the option that is selected.
* @throws AlreadySelectedException if an option from this group has already been selected.
*/
public void setSelected(final Option option) throws AlreadySelectedException {
@@ -144,7 +144,7 @@ public void setSelected(final Option option) throws AlreadySelectedException {
/**
* Returns the stringified version of this OptionGroup.
*
- * @return the stringified representation of this group
+ * @return the stringified representation of this group.
*/
@Override
public String toString() {
diff --git a/src/main/java/org/apache/commons/cli/OptionValidator.java b/src/main/java/org/apache/commons/cli/OptionValidator.java
index 987f2d338..e170ccb11 100644
--- a/src/main/java/org/apache/commons/cli/OptionValidator.java
+++ b/src/main/java/org/apache/commons/cli/OptionValidator.java
@@ -129,7 +129,7 @@ static String validate(final String option) throws IllegalArgumentException {
for (int i = 1; i < chars.length; i++) {
final char ch = chars[i];
if (!isValidChar(ch)) {
- throw new IllegalArgumentException(String.format("The option '%s' contains an illegal " + "character : '%s'.", option, ch));
+ throw new IllegalArgumentException(String.format("The option '%s' contains an illegal character : '%s'.", option, ch));
}
}
}
diff --git a/src/main/java/org/apache/commons/cli/Options.java b/src/main/java/org/apache/commons/cli/Options.java
index 66db08422..eb03aaed5 100644
--- a/src/main/java/org/apache/commons/cli/Options.java
+++ b/src/main/java/org/apache/commons/cli/Options.java
@@ -65,10 +65,10 @@ public Options() {
}
/**
- * Adds an option instance
+ * Adds an option instance.
*
- * @param opt the option that is to be added
- * @return the resulting Options instance
+ * @param opt the option that is to be added.
+ * @return the resulting Options instance.
*/
public Options addOption(final Option opt) {
final String key = opt.getKey();
@@ -94,9 +94,9 @@ public Options addOption(final Option opt) {
*
*
* @param opt Short single-character name of the option.
- * @param hasArg flag signaling if an argument is required after this option
- * @param description Self-documenting description
- * @return the resulting Options instance
+ * @param hasArg flag signaling if an argument is required after this option.
+ * @param description Self-documenting description.
+ * @return the resulting Options instance.
*/
public Options addOption(final String opt, final boolean hasArg, final String description) {
addOption(opt, null, hasArg, description);
@@ -110,8 +110,8 @@ public Options addOption(final String opt, final boolean hasArg, final String de
*
*
* @param opt Short single-character name of the option.
- * @param description Self-documenting description
- * @return the resulting Options instance
+ * @param description Self-documenting description.
+ * @return the resulting Options instance.
* @since 1.3
*/
public Options addOption(final String opt, final String description) {
@@ -127,9 +127,9 @@ public Options addOption(final String opt, final String description) {
*
* @param opt Short single-character name of the option.
* @param longOpt Long multi-character name of the option.
- * @param hasArg flag signaling if an argument is required after this option
- * @param description Self-documenting description
- * @return the resulting Options instance
+ * @param hasArg flag signaling if an argument is required after this option.
+ * @param description Self-documenting description.
+ * @return the resulting Options instance.
*/
public Options addOption(final String opt, final String longOpt, final boolean hasArg, final String description) {
addOption(new Option(opt, longOpt, hasArg, description));
@@ -138,28 +138,34 @@ public Options addOption(final String opt, final String longOpt, final boolean h
/**
* Adds the specified option group.
+ *
+ * An Option cannot be required if it is in an {@link OptionGroup}, either the group is required or nothing is required. This means that {@link Option} in
+ * the given group are set to optional.
+ *
*
- * @param group the OptionGroup that is to be added
- * @return the resulting Options instance
+ * @param optionGroup the OptionGroup that is to be added.
+ * @return the resulting Options instance.
*/
- public Options addOptionGroup(final OptionGroup group) {
- if (group.isRequired()) {
- requiredOpts.add(group);
+ public Options addOptionGroup(final OptionGroup optionGroup) {
+ if (optionGroup.isRequired()) {
+ requiredOpts.add(optionGroup);
}
- for (final Option option : group.getOptions()) {
+ for (final Option option : optionGroup.getOptions()) {
// an Option cannot be required if it is in an
// OptionGroup, either the group is required or
// nothing is required
option.setRequired(false);
+ final String key = option.getKey();
+ requiredOpts.remove(key);
addOption(option);
- optionGroups.put(option.getKey(), group);
+ optionGroups.put(key, optionGroup);
}
return this;
}
/**
* Adds options to this option. If any Option in {@code options} already exists
- * in this Options an IllegalArgumentException is thrown
+ * in this Options an IllegalArgumentException is thrown.
*
* @param options the options to add.
* @return The resulting Options instance.
@@ -191,9 +197,9 @@ public Options addOptions(final Options options) {
*
* @param opt Short single-character name of the option.
* @param longOpt Long multi-character name of the option.
- * @param hasArg flag signaling if an argument is required after this option
- * @param description Self-documenting description
- * @return the resulting Options instance
+ * @param hasArg flag signaling if an argument is required after this option.
+ * @param description Self-documenting description.
+ * @return the resulting Options instance.
* @since 1.4
*/
public Options addRequiredOption(final String opt, final String longOpt, final boolean hasArg, final String description) {
@@ -206,8 +212,8 @@ public Options addRequiredOption(final String opt, final String longOpt, final b
/**
* Gets the options with a long name starting with the name specified.
*
- * @param opt the partial name of the option
- * @return the options matching the partial name specified, or an empty list if none matches
+ * @param opt the partial name of the option.
+ * @return the options matching the partial name specified, or an empty list if none matches.
* @since 1.3
*/
public List getMatchingOptions(final String opt) {
@@ -231,8 +237,8 @@ public List getMatchingOptions(final String opt) {
* The leading hyphens in the name are ignored (up to 2).
*
*
- * @param opt short or long name of the {@link Option}
- * @return the option represented by opt
+ * @param opt short or long name of the {@link Option}.
+ * @return the option represented by opt.
*/
public Option getOption(final String opt) {
final String clean = Util.stripLeadingHyphens(opt);
@@ -243,11 +249,11 @@ public Option getOption(final String opt) {
/**
* Gets the OptionGroup the {@code opt} belongs to.
*
- * @param opt the option whose OptionGroup is being queried.
- * @return the OptionGroup if {@code opt} is part of an OptionGroup, otherwise return null
+ * @param option the option whose OptionGroup is being queried.
+ * @return the OptionGroup if {@code opt} is part of an OptionGroup, otherwise return null.
*/
- public OptionGroup getOptionGroup(final Option opt) {
- return optionGroups.get(opt.getKey());
+ public OptionGroup getOptionGroup(final Option option) {
+ return optionGroups.get(option.getKey());
}
/**
@@ -256,18 +262,18 @@ public OptionGroup getOptionGroup(final Option opt) {
* @return a Collection of OptionGroup instances.
*/
Collection getOptionGroups() {
- /* The optionGroups map will have duplicates in the values() results. We
- * use the HashSet to filter out duplicates and return a collection of
- * OpitonGroup. The decision to return a Collection rather than a set
- * was probably to keep symmetry with the getOptions() method.
- */
+
+ // The optionGroups map will have duplicates in the values() results. We
+ // use the HashSet to filter out duplicates and return a collection of
+ // OpitonGroup. The decision to return a Collection rather than a set
+ // was probably to keep symmetry with the getOptions() method.
return new HashSet<>(optionGroups.values());
}
/**
- * Gets a read-only list of options in this set
+ * Gets a read-only list of options in this set.
*
- * @return read-only Collection of {@link Option} objects in this descriptor
+ * @return read-only Collection of {@link Option} objects in this descriptor.
*/
public Collection
getOptions() {
return Collections.unmodifiableCollection(helpOptions());
@@ -276,7 +282,7 @@ public Collection
getOptions() {
/**
* Gets the required options.
*
- * @return read-only List of required options
+ * @return read-only List of required options.
*/
public List> getRequiredOptions() {
return Collections.unmodifiableList(requiredOpts);
@@ -285,8 +291,8 @@ public List> getRequiredOptions() {
/**
* Tests whether the named {@link Option} is a member of this {@link Options}.
*
- * @param opt long name of the {@link Option}
- * @return true if the named {@link Option} is a member of this {@link Options}
+ * @param opt long name of the {@link Option}.
+ * @return true if the named {@link Option} is a member of this {@link Options}.
* @since 1.3
*/
public boolean hasLongOption(final String opt) {
@@ -296,8 +302,8 @@ public boolean hasLongOption(final String opt) {
/**
* Tests whether the named {@link Option} is a member of this {@link Options}.
*
- * @param opt short or long name of the {@link Option}
- * @return true if the named {@link Option} is a member of this {@link Options}
+ * @param opt short or long name of the {@link Option}.
+ * @return true if the named {@link Option} is a member of this {@link Options}.
*/
public boolean hasOption(final String opt) {
final String clean = Util.stripLeadingHyphens(opt);
@@ -307,8 +313,8 @@ public boolean hasOption(final String opt) {
/**
* Tests whether the named {@link Option} is a member of this {@link Options}.
*
- * @param opt short name of the {@link Option}
- * @return true if the named {@link Option} is a member of this {@link Options}
+ * @param opt short name of the {@link Option}.
+ * @return true if the named {@link Option} is a member of this {@link Options}.
* @since 1.3
*/
public boolean hasShortOption(final String opt) {
@@ -319,7 +325,7 @@ public boolean hasShortOption(final String opt) {
/**
* Returns the Options for use by the HelpFormatter.
*
- * @return the List of Options
+ * @return the List of Options.
*/
List
helpOptions() {
return new ArrayList<>(shortOpts.values());
@@ -328,7 +334,7 @@ List
helpOptions() {
/**
* Dump state, suitable for debugging.
*
- * @return Stringified form of this object
+ * @return Stringified form of this object.
*/
@Override
public String toString() {
diff --git a/src/main/java/org/apache/commons/cli/Parser.java b/src/main/java/org/apache/commons/cli/Parser.java
index b1ff32653..759827f26 100644
--- a/src/main/java/org/apache/commons/cli/Parser.java
+++ b/src/main/java/org/apache/commons/cli/Parser.java
@@ -152,8 +152,8 @@ public CommandLine parse(final Options options, final String[] arguments, final
opt.clearValues();
}
// clear the data from the groups
- for (final OptionGroup group : options.getOptionGroups()) {
- group.setSelected(null);
+ for (final OptionGroup optionGroup : options.getOptionGroups()) {
+ optionGroup.setSelected(null);
}
// initialize members
setOptions(options);
@@ -280,8 +280,8 @@ protected void processProperties(final Properties properties) throws ParseExcept
throw new UnrecognizedOptionException("Default option wasn't defined", option);
}
// if the option is part of a group, check if another option of the group has been selected
- final OptionGroup group = options.getOptionGroup(opt);
- final boolean selected = group != null && group.isSelected();
+ final OptionGroup optionGroup = options.getOptionGroup(opt);
+ final boolean selected = optionGroup != null && optionGroup.isSelected();
if (!cmd.hasOption(option) && !selected) {
// get the value from the properties instance
final String value = properties.getProperty(option);
@@ -328,11 +328,11 @@ private void updateRequiredOptions(final Option opt) throws ParseException {
// if the option is in an OptionGroup make that option the selected
// option of the group
if (getOptions().getOptionGroup(opt) != null) {
- final OptionGroup group = getOptions().getOptionGroup(opt);
- if (group.isRequired()) {
- getRequiredOptions().remove(group);
+ final OptionGroup optionGroup = getOptions().getOptionGroup(opt);
+ if (optionGroup.isRequired()) {
+ getRequiredOptions().remove(optionGroup);
}
- group.setSelected(opt);
+ optionGroup.setSelected(opt);
}
}
diff --git a/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java
index dc11c6979..e62ce40b0 100644
--- a/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java
+++ b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java
@@ -191,7 +191,7 @@ public static Options parsePattern(final String pattern) {
.required(required)
.type(type)
.converter(converter)
- .build();
+ .get();
// @formatter:on
// we have a previous one to deal with
options.addOption(option);
@@ -213,7 +213,7 @@ public static Options parsePattern(final String pattern) {
}
if (opt != Char.SP) {
- final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).build();
+ final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).get();
// we have a final one to deal with
options.addOption(option);
diff --git a/src/main/java/org/apache/commons/cli/help/AbstractHelpFormatter.java b/src/main/java/org/apache/commons/cli/help/AbstractHelpFormatter.java
index fd2fa34b8..91f356d59 100644
--- a/src/main/java/org/apache/commons/cli/help/AbstractHelpFormatter.java
+++ b/src/main/java/org/apache/commons/cli/help/AbstractHelpFormatter.java
@@ -428,15 +428,15 @@ protected String toSyntaxOptions(final Iterable
options, final Function<
for (final Option option : optList) {
// get the next Option
// check if the option is part of an OptionGroup
- final OptionGroup group = lookup.apply(option);
+ final OptionGroup optionGroup = lookup.apply(option);
// if the option is part of a group
- if (group != null) {
+ if (optionGroup != null) {
// and if the group has not already been processed
- if (!processedGroups.contains(group)) {
+ if (!processedGroups.contains(optionGroup)) {
// add the group to the processed list
- processedGroups.add(group);
+ processedGroups.add(optionGroup);
// add the usage clause
- buff.append(prefix).append(toSyntaxOptions(group));
+ buff.append(prefix).append(toSyntaxOptions(optionGroup));
prefix = " ";
}
// otherwise the option was displayed in the group previously so ignore it.
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
index 9798b2952..7d84a88b9 100644
--- a/src/main/javadoc/overview.html
+++ b/src/main/javadoc/overview.html
@@ -21,7 +21,7 @@
-
The Commons CLI component parses command-line arguments for your application.
+
The Apacahe Commons CLI component parses command-line arguments for your application.
Commons CLI parses command-line arguments using a descriptor of
valid options (long and short), potentially with arguments.
@@ -33,11 +33,790 @@
'cvs-style' command-lines, where some global options are specified
before a 'command' argument, and command-specific options are
specified after the command argument:
-
-
+
+
+ There are three stages to command line processing. They are the
+ definition, parsing and interrogation stages. The following
+ sections discuss each of these stages in turn, and show how
+ to implement them with CLI.
+
+
+
Defining the CLI
+
+ Each command line must define the set of options that will be used
+ to define the interface to the application.
+
+
+ CLI uses the
+ Options class, as a container for
+
+ Option instances. There are two ways to create
+ Options in CLI. One of them is via the constructors,
+ the other way is via the factory methods defined in
+ Options.
+
+
+ The Usage Scenarios document provides
+ examples how to create an Options object and also
+ provides some real world examples.
+
+
+ The result of the definition stage is an Options
+ instance.
+
+
+
+
Parsing the CLI
+
+ The parsing stage is where the text passed into the
+ application via the command line is processed. The text is
+ processed according to the rules defined by the parser
+ implementation.
+
+
+ The parse method defined on
+
+ CommandLineParser takes an Options
+ instance and a String[] of arguments and
+ returns a
+
+ CommandLine.
+
+
+ The result of the parsing stage is a CommandLine
+ instance.
+
+
+
+
Interrogating the CLI
+
+ The interrogation stage is where the application queries the
+ CommandLine to decide what execution branch to
+ take depending on boolean options and uses the option values
+ to provide the application data.
+
+
+ This stage is implemented in the user code. The accessor methods
+ on CommandLine provide the interrogation capability
+ to the user code.
+
+
+ The result of the interrogation stage is that the user code
+ is fully informed of all the text that was supplied on the command
+ line and processed according to the parser and Options
+ rules.
+
+
+
+
+
Using Apache Commons CLI
+
+ The following sections describe some example scenarios on how to
+ use CLI in applications.
+
+
+
+
Using a boolean option
+
+ A boolean option is represented on a command line by the presence
+ of the option, i.e. if the option is found then the option value
+ is true, otherwise the value is false.
+
+
+ The DateApp utility prints the current date to standard
+ output. If the -t option is present the current time is
+ also printed.
+
+
+
+
Creating the Options
+
+ An
+ Options object must be created and the Option must be
+ added to it.
+
+
+// create Options object
+Options options = new Options();
+
+// add t option
+options.addOption("t", false, "display current time");
+
+
+ The addOption method has three parameters. The first
+ parameter is a java.lang.String that represents the option.
+ The second parameter is a boolean that specifies whether the
+ option requires an argument or not. In the case of a boolean option
+ (sometimes referred to as a flag) an argument value is not present so
+ false is passed. The third parameter is the description
+ of the option. This description will be used in the usage text of the
+ application.
+
+
+
+
Parsing the command line arguments
+
+ The parse methods of CommandLineParser are used
+ to parse the command line arguments. There may be several implementations
+ of the CommandLineParser interface, the recommended one is the
+ DefaultParser.
+
+ Now we need to check if the t option is present. To do
+ this we will interrogate the
+ CommandLine
+ object. The hasOption method takes a
+ java.lang.String parameter and returns true if the option
+ represented by the java.lang.String is present, otherwise
+ it returns false.
+
+
if(cmd.hasOption("t")) {
+ // print the date and time
+}
+else {
+ // print the date
+}
+
+ Note
+
+
+ As of version 1.5, the
+ DefaultParser's constructor now has an override with
+ the signature DefaultParser(final boolean allowPartialMatching).
+ Given the following code:
+ final Options options = new Options();
+options.addOption(new Option("d", "debug", false, "Turn on debug."));
+options.addOption(new Option("e", "extract", false, "Turn on extract."));
+options.addOption(new Option("o", "option", true, "Turn on option with argument."));
+ we define "partial matching" as -de only matching the
+ "debug" option. We can consequently, now, turn this off and have
+ -de match both the debug option as well as the
+ extract option.
+
+
+
+
International Time
+
+ The InternationalDateApp utility extends the
+ DateApp utility by providing the ability to print the
+ date and time in any country in the world. To facilitate this a new
+ command line option, c, has been introduced.
+
+
// add c option
+options.addOption("c", true, "country code");
+
+ The second parameter is true this time. This specifies that the
+ c option requires an argument value. If the required option
+ argument value is specified on the command line it is returned,
+ otherwise null is returned.
+
+
+
+
Retrieving the argument value
+
+ The getOptionValue methods of CommandLine are
+ used to retrieve the argument values of options.
+
+
// get c option value
+String countryCode = cmd.getOptionValue("c");
+
+if(countryCode == null) {
+ // print default date
+}
+else {
+ // print date for country specified by countryCode
+}
+
+
+
+
+
Using Ant as an Example
+
+ Ant will be used
+ here to illustrate how to create the Options required. The following
+ is the help output for Ant.
+
+
ant [options] [target [target2 [target3] ...]]
+ Options:
+ -help print this message
+ -projecthelp print project help information
+ -version print the version information and exit
+ -quiet be extra quiet
+ -verbose be extra verbose
+ -debug print debugging information
+ -emacs produce logging information without adornments
+ -logfile <file> use given file for log
+ -logger <classname> the class which is to perform logging
+ -listener <classname> add an instance of class as a project listener
+ -buildfile <file> use given buildfile
+ -D<property>=<value> use value for given property
+ -find <file> search for buildfile towards the root of the
+ filesystem and use it
+
+
Defining Boolean Options
+
+ Lets create the boolean options for the application as they
+ are the easiest to create. For clarity the constructors for
+ Option are used here.
+
+
Option help = new Option("help", "print this message");
+Option projecthelp = new Option("projecthelp", "print project help information");
+Option version = new Option("version", "print the version information and exit");
+Option quiet = new Option("quiet", "be extra quiet");
+Option verbose = new Option("verbose", "be extra verbose");
+Option debug = new Option("debug", "print debugging information");
+Option emacs = new Option("emacs",
+ "produce logging information without adornments");
+
+
+
Defining Argument Options
+
+ The argument options are created using the Option#Builder.
+
+
Option logFile = Option.builder("logfile")
+ .argName("file")
+ .hasArg()
+ .desc("use given file for log")
+ .build();
+
+Option logger = Option.builder("logger")
+ .argName("classname")
+ .hasArg()
+ .desc("the class which it to perform logging")
+ .build();
+
+Option listener = Option.builder("listener")
+ .argName("classname")
+ .hasArg()
+ .desc("add an instance of class as "
+ + "a project listener")
+ .build();
+
+Option buildFile = Option.builder("buildfile")
+ .argName("file")
+ .hasArg()
+ .desc("use given buildfile")
+ .build();
+
+Option find = Option.builder("find")
+ .argName("file")
+ .hasArg()
+ .desc("search for buildfile towards the "
+ + "root of the filesystem and use it")
+ .build();
+
+
+
Defining Java Property Option
+
+ The last option to create is the Java property, and it is also created
+ using the Option class' Builder.
+
+ All the preparation is now complete, and we are now ready to
+ parse the command line arguments.
+
+
+
+
Creating the Parser
+
+ We now need to create a CommandLineParser. This will parse the command
+ line arguments, using the rules specified by the Options and
+ return an instance of CommandLine.
+
+
public static void main(String[] args) {
+ // create the parser
+ CommandLineParser parser = new DefaultParser();
+ try {
+ // parse the command line arguments
+ CommandLine line = parser.parse(options, args);
+ }
+ catch (ParseException exp) {
+ // oops, something went wrong
+ System.err.println("Parsing failed. Reason: " + exp.getMessage());
+ }
+}
+
+
+
Querying the commandline
+
+ To see if an option has been passed the hasOption
+ method is used. The argument value can be retrieved using
+ the getOptionValue method.
+
+
// has the buildfile argument been passed?
+if (line.hasOption("buildfile")) {
+ // initialize the member variable
+ this.buildfile = line.getOptionValue("buildfile");
+}
+
+
+
Displaying Usage and Help
+
+ CLI also provides the means to automatically generate usage
+ and help information. This is achieved with the
+ HelpFormatter
+ class.
+
+
// automatically generate the help statement
+HelpFormatter formatter = new HelpFormatter();
+formatter.printHelp("ant", options);
+
+ When executed the following output is produced:
+
+
usage: ant
+-D <property=value> use value for given property
+-buildfile <file> use given buildfile
+-debug print debugging information
+-emacs produce logging information without adornments
+-file <file> search for buildfile towards the root of the
+ filesystem and use it
+-help print this message
+-listener <classname> add an instance of class as a project listener
+-logger <classname> the class which it to perform logging
+-projecthelp print project help information
+-quiet be extra quiet
+-verbose be extra verbose
+-version print the version information and exit
+
+ If you also require to have a usage statement printed
+ then calling formatter.printHelp("ant", options, true)
+ will generate a usage statement as well as the help information.
+
+
+
+
+
+
Creating an ls Example
+
+ One of the most widely used command line applications in the *nix world
+ is ls. Due to the large number of options required for ls
+ this example will only cover a small proportion of the options. The following
+ is a section of the help output.
+
+
Usage: ls [OPTION]... [FILE]...
+List information about the FILEs (the current directory by default).
+Sort entries alphabetically if none of -cftuSUX nor --sort.
+
+-a, --all do not hide entries starting with .
+-A, --almost-all do not list implied . and ..
+-b, --escape print octal escapes for non-graphic characters
+ --block-size=SIZE use SIZE-byte blocks
+-B, --ignore-backups do not list implied entries ending with ~
+-c with -lt: sort by, and show, ctime (time of last
+ modification of file status information)
+ with -l: show ctime and sort by name
+ otherwise: sort by ctime
+-C list entries by columns
+
+ The following is the code that is used to create the
+ Options for this example.
+
+
// create the command line parser
+CommandLineParser parser = new DefaultParser();
+
+// create the Options
+Options options = new Options();
+options.addOption("a", "all", false, "do not hide entries starting with .");
+options.addOption("A", "almost-all", false, "do not list implied . and ..");
+options.addOption("b", "escape", false, "print octal escapes for non-graphic "
+ + "characters");
+options.addOption(Option.builder("SIZE").longOpt("block-size")
+ .desc("use SIZE-byte blocks")
+ .hasArg()
+ .build());
+options.addOption("B", "ignore-backups", false, "do not list implied entries "
+ + "ending with ~");
+options.addOption("c", false, "with -lt: sort by, and show, ctime (time of last "
+ + "modification of file status information) with "
+ + "-l:show ctime and sort by name otherwise: sort "
+ + "by ctime");
+options.addOption("C", false, "list entries by columns");
+
+String[] args = new String[]{ "--block-size=10" };
+
+try {
+ // parse the command line arguments
+ CommandLine line = parser.parse(options, args);
+
+ // validate that block-size has been set
+ if (line.hasOption("block-size")) {
+ // print the value of block-size
+ System.out.println(line.getOptionValue("block-size"));
+ }
+}
+catch (ParseException exp) {
+ System.out.println("Unexpected exception:" + exp.getMessage());
+}
+
+
+
Converting (Parsing) Option Values
+
+ By in most cases the values on the command line are retrieved as Strings via the
+ commandLine.getOptionValue(key) command. However, it is possible for
+ the CLI library to convert the string into a different object. For example to specify
+ that the "count" option should return an Integer the following code could be used:
+
+
+public static void main(String[] args) {
+ Option count = Option.builder("count")
+ .hasArg()
+ .desc("the number of things")
+ .type(Integer.class)
+ .build();
+ Options options = new Options().addOption(count);
+ // create the parser
+ CommandLineParser parser = new DefaultParser();
+ try {
+ // parse the command line arguments
+ CommandLine line = parser.parse(options, args);
+ } catch (ParseException exp) {
+ // oops, something went wrong
+ System.err.println("Parsing failed. Reason: " + exp.getMessage());
+ }
+
+ try {
+ Integer value = line.getParsedOptionValue(count);
+ System.out.format("The value is %s%n", value );
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+}
+
+ The value types natively supported by commons-cli are:
+
+
+
Object.class - The string value must be the name of a class with a no argument constructor
+
Class.class - The string value must be the name of a class
+
Date.class - The string value must be a date parsable by new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy")
+
File.class - The string value is the name of the file.
+
Path.class - The string value is the name of a Path.
+
Number.class - The string value is a number representation can can be converted into an Integer or a Double.
+
URL.class - The string value is the textual representation of a URL
+
FileInputStream.class - The string value is passed to new FileInputStream(s).
+
Long.class - The string value is a valid argument to Long.parseLong().
+
Integer.class - The string value is a valid argument to Integer.parseInt().
+
Short.class - The string value is a valid argument to Short.parseShort().
+
Byte.class - The string value is a valid argument to Byte.parseByte().
+
Character.class - The string value is either a UTF-8 encoding for a character (e.g. "\\u0124") or the first character from the String."
+
Double.class - The string value is a valid argument to Double.parseDouble().
+
Float.class - The string value is a valid argument to Float.parseFloat().
+
BigInteger.class - The string value is a valid argument to new BigInteger(s).
+
BigDecimal.class - The string value is a valid argument to new BigDecimal(s).
+
+
+ Additional types may be added to the automatic parsing system by calling TypeHandler.register(Class<T> clazz, Converter<T> converter).
+ The Class<T> can be any defined class. The converter is a function that takes a String argument and returns an instance of
+ the class. Any exception thrown by the constructor will be caught and reported as a ParseException
+
+
+ Conversions can be specified without using the TypeHandler class by specifying the converter
+ directly during the option build. For example:
+
+ The above will create an option that passes the string value to the Foo constructor when commandLine.getParsedOptionValue(fooOpt) is called.
+
+
+ Conversions that are added to the TypeHandler or that are specified directly will not deserialize if the option is serialized unless the type is registered with the TypeHandler
+ before deserialization begins.
+
+
+
+
Deprecating Options
+
+ Options may be marked as deprecated using ghe Option.builder.deprecated() method.
+ Additional information may be specified by passing a DeprecatedAttributes instance to the
+ deprecated method.
+
+ Usage of the deprecated option is announced when the presence of the option is checked
+ or the value of the option is retrieved. By default, the announcement printed to Standard out.
+
+ The HelpFormatter output will by default show the description prefixed by "[Deprecated]"
+
+
+ The examples below will implement doSomething in the following code block.
+
+
+ public static void main(String[] args) {
+ Option n = Option.builder("n")
+ .deprecated(DeprecatedAttributes.builder()
+ .setDescription("Use '-count' instead")
+ .setForRemoval(true)
+ .setSince("now").get())
+ .hasArg()
+ .desc("the number of things")
+ .type(Integer.class)
+ .build();
+ Option count = Option.builder("count")
+ .hasArg()
+ .desc("the number of things")
+ .type(Integer.class)
+ .build();
+ Options options = new Options().addOption(n).addOption(count);
+
+ doSomething(options);
+ }
+
+
+
Changing Usage Announcement
+
+ The usage announcement may be changed by providing a Consumer<Option> to the
+ CommandLine.Builder.deprecatedHandler method. This is commonly used to log usage
+ of deprecated options rather than printing them on the standard output.
+
+
+ for example if doSomething is implemented as:
+
+
+ void doSomething(Options options) {
+ CommandLineParser parser = new DefaultParser();
+ CommandLine line;
+ try {
+ // parse the command line arguments
+ line = parser.parse(options, new String[] {"-n", "5"});
+ System.out.println("n=" + line.getParsedOptionValue("n"));
+ } catch (ParseException exp) {
+ // oops, something went wrong
+ System.err.println("Parsing failed. Reason: " + exp.getMessage());
+ }
+ }
+
+ The output of the run would be.
+
+
+
+ Option 'n': Deprecated for removal since now: Use '-count' instead
+ n=5
+ usage: Command line syntax:
+ -count <arg> the number of things
+ -n <arg> [Deprecated] the number of things
+
+ The display of deprecated options may be changed through the use of the
+ HelpFormatter.Builder.setShowDeprecated() method.
+
+
+
Calling HelpFormatter.Builder.setShowDeprecated(false) will disable the "[Deprecated]" tag.
+
Calling HelpFormatter.Builder.setShowDeprecated with a Function<Option, String>
+ will use the output of the function as the description for the option.
+
+
+ As an example of the second case above, changing the implementation of doSomething to
+
+ usage: Command line syntax:
+ -count <arg> the number of things
+ -n <arg> the number of things. Deprecated for removal since now:
+ Use '-count' instead
+
+
+
+
Defining Option Properties
+
+ The following are the properties that each
+ Option has. All of these
+ can be set using the accessors or using the methods
+ defined in the
+ Option.Builder.
+
+
+
Option Properties
+
+
Name
+
Type
+
Description
+
+
+
arg
+
boolean
+
A flag to say whether the option takes an argument.
+
+
+
args
+
boolean
+
A flag to say whether the option takes more than one argument.
+
+
+
argName
+
java.lang.String
+
The name of the argument value for the usage statement.
+
+
+
converter
+
org.apache.commons.cli.Converter<T, E extends Throwable>
+
A FunctionalInterface that converts a String to type T and may throw an exception E. When
+ CommandLine.getParsedValue() is called this FunctionalInterface will perform the conversion from the
+ command line argument to the parsed type. This is used when a desired type is not registered, or to
+ provide a custom type implementation. The 'type' property is not required to use the 'converter' property.
+
+
+
deprecated
+
org.apache.commons.cli.DeprecatedAttributes
+
Marks the option as deprecated and has the deprecation properties.
+
+
+
description
+
java.lang.String
+
A description of the function of the option.
+
+
+
longOpt
+
java.lang.String
+
An alias and more descriptive identification string. May be null or not specified if 'opt' is provided.
+
+
+
opt
+
java.lang.String
+
The identification string of the Option. May be null or not specified if 'longOpt' is provided.
+
+
+
optionalArg
+
boolean
+
A flag to say whether the option's argument is optional.
+
+
+
required
+
boolean
+
A flag to say whether the option must appear on the command line.
+
+
+
type
+
java.lang.Class<?>
+
The class of the object returned from getParsedValue(). The class must be registered in TypeHandler instance.
+ See also 'converter' property.
+
+
+
value
+
java.lang.String
+
The value of the option.
+
+
+
values
+
java.lang.String[]
+
The values of the option.
+
+
+
valueSeparator
+
char
+
The character value used to split the argument string, that is used in conjunction with multipleArgs e.g.
+ if the separator is ',' and the argument string is 'a,b,c' then there are three argument values, 'a', 'b'
+ and 'c'.
+
+
+
diff --git a/src/site/site.xml b/src/site/site.xml
index f8121d255..907eb40d5 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -45,11 +45,11 @@
diff --git a/src/site/xdoc/download_cli.xml b/src/site/xdoc/download_cli.xml
index 8af1846be..30877bf9d 100644
--- a/src/site/xdoc/download_cli.xml
+++ b/src/site/xdoc/download_cli.xml
@@ -56,10 +56,12 @@ limitations under the License.
| |
+======================================================================+
-->
-
+Download Apache Commons CLI
- Apache Commons Documentation Team
+ Apache Commons Team
@@ -79,7 +81,7 @@ limitations under the License.
mirrors (at the end of the mirrors list) that should be
available.
- [if-any logo][end]
+ [if-any logo][end]