diff --git a/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java index f31bf21a9..ac53dd972 100644 --- a/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java +++ b/src/main/java/org/apache/commons/cli/PatternOptionBuilder.java @@ -70,7 +70,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more * *

* For example, the following accepts command-line arguments such as {@code -v -p string-value -f https://example.com}. The exclamation mark makes the - * preceding option mandatory. + * preceding option mandatory, here {@code -p}. An exclamation mark at the start of the pattern makes the first option mandatory. *

* *
diff --git a/src/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java b/src/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java
index dd1013eae..874c5d68a 100644
--- a/src/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java
+++ b/src/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java
@@ -99,6 +99,35 @@ void testObjectPattern() throws Exception {
         assertNull(line.getOptionObject("n"), "n value");
     }
 
+    @Test
+    void testRequiredMarkAfterOption() throws Exception {
+        // The class Javadoc example: '!' makes the preceding option, -p, mandatory.
+        final Options options = PatternOptionBuilder.parsePattern("vp:!f/");
+        assertFalse(options.getOption("v").isRequired(), "v");
+        assertTrue(options.getOption("p").isRequired(), "p");
+        assertFalse(options.getOption("f").isRequired(), "f");
+        final MissingOptionException e = assertThrows(MissingOptionException.class,
+                () -> new DefaultParser().parse(options, new String[] { "-v", "-f", "https://example.com" }));
+        assertEquals(1, e.getMissingOptions().size());
+        assertTrue(e.getMissingOptions().contains("p"));
+    }
+
+    @Test
+    void testRequiredMarkAtEnd() {
+        final Options options = PatternOptionBuilder.parsePattern("a:b!");
+        assertFalse(options.getOption("a").isRequired(), "a");
+        assertTrue(options.getOption("b").isRequired(), "b");
+    }
+
+    @Test
+    void testRequiredMarkBeforeValueCode() {
+        // The example from CLI-55: "hc!<" makes -c mandatory.
+        final Options options = PatternOptionBuilder.parsePattern("hc!<");
+        assertFalse(options.getOption("h").isRequired(), "h");
+        assertTrue(options.getOption("c").isRequired(), "c");
+        assertEquals(PatternOptionBuilder.EXISTING_FILE_VALUE, options.getOption("c").getType());
+    }
+
     @Test
     void testRequiredOption() throws Exception {
         final Options options = PatternOptionBuilder.parsePattern("!n%m%");
@@ -108,6 +137,14 @@ void testRequiredOption() throws Exception {
         assertTrue(e.getMissingOptions().contains("n"));
     }
 
+    @Test
+    void testRequiredOptions() {
+        final Options options = PatternOptionBuilder.parsePattern("a!b:!c");
+        assertTrue(options.getOption("a").isRequired(), "a");
+        assertTrue(options.getOption("b").isRequired(), "b");
+        assertFalse(options.getOption("c").isRequired(), "c");
+    }
+
     @Test
     void testSimplePattern() throws Exception {
         /*