Skip to content

Commit e366a69

Browse files
committed
Implemented the partial matching for long options in the PosixParser (CLI-160)
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/cli/trunk@778664 13f79535-47bb-0310-9956-ffa450edef68
1 parent 8161311 commit e366a69

9 files changed

Lines changed: 267 additions & 14 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.cli;
19+
20+
import java.util.Collection;
21+
22+
/**
23+
* Exception thrown when an option can't be identified from a partial name.
24+
*
25+
* @author Emmanuel Bourg
26+
* @version $Revision$, $Date$
27+
* @since 1.3
28+
*/
29+
public class AmbiguousOptionException extends UnrecognizedOptionException
30+
{
31+
/** The list of options matching the partial name specified */
32+
private Collection matchingOptions;
33+
34+
/**
35+
* Constructs a new AmbiguousOptionException.
36+
*
37+
* @param option the partial option name
38+
* @param matchingOptions the options matching the name
39+
*/
40+
public AmbiguousOptionException(String option, Collection matchingOptions)
41+
{
42+
super("Ambiguous option: " + option, option);
43+
this.matchingOptions = matchingOptions;
44+
}
45+
46+
/**
47+
* Returns the options matching the partial name.
48+
*/
49+
public Collection getMatchingOptions()
50+
{
51+
return matchingOptions;
52+
}
53+
}

src/java/org/apache/commons/cli/Options.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,32 @@ public Option getOption(String opt)
213213
return (Option) longOpts.get(opt);
214214
}
215215

216+
/**
217+
* Returns the options with a long name starting with the name specified.
218+
*
219+
* @param opt the partial name of the option
220+
* @return the options matching the partial name specified, or an empty list if none matches
221+
* @since 1.3
222+
*/
223+
public List getMatchingOptions(String opt)
224+
{
225+
opt = Util.stripLeadingHyphens(opt);
226+
227+
List matchingOpts = new ArrayList();
228+
229+
Iterator it = longOpts.keySet().iterator();
230+
while (it.hasNext())
231+
{
232+
String longOpt = (String) it.next();
233+
if (longOpt.startsWith(opt))
234+
{
235+
matchingOpts.add(longOpt);
236+
}
237+
}
238+
239+
return matchingOpts;
240+
}
241+
216242
/**
217243
* Returns whether the named {@link Option} is a member of this {@link Options}.
218244
*

src/java/org/apache/commons/cli/Parser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ protected List getRequiredOptions()
6868
* flattening when a non option has been encountered
6969
* @return a String array of the flattened arguments
7070
*/
71-
protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption);
71+
protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption) throws ParseException;
7272

7373
/**
7474
* Parses the specified <code>arguments</code> based

src/java/org/apache/commons/cli/PosixParser.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private void init()
9292
* when an non option is found.
9393
* @return The flattened <code>arguments</code> String array.
9494
*/
95-
protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption)
95+
protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
9696
{
9797
init();
9898
this.options = options;
@@ -106,39 +106,59 @@ protected String[] flatten(Options options, String[] arguments, boolean stopAtNo
106106
// get the next command line token
107107
String token = (String) iter.next();
108108

109+
// single or double hyphen
110+
if ("-".equals(token) || "--".equals(token))
111+
{
112+
tokens.add(token);
113+
}
114+
109115
// handle long option --foo or --foo=bar
110-
if (token.startsWith("--"))
116+
else if (token.startsWith("--"))
111117
{
112118
int pos = token.indexOf('=');
113119
String opt = pos == -1 ? token : token.substring(0, pos); // --foo
120+
121+
List matchingOpts = options.getMatchingOptions(opt);
114122

115-
if (!options.hasOption(opt))
123+
if (matchingOpts.isEmpty())
116124
{
117125
processNonOptionToken(token, stopAtNonOption);
118126
}
127+
else if (matchingOpts.size() > 1)
128+
{
129+
throw new AmbiguousOptionException(opt, matchingOpts);
130+
}
119131
else
120132
{
121-
currentOption = options.getOption(opt);
133+
currentOption = options.getOption((String) matchingOpts.get(0));
122134

123-
tokens.add(opt);
135+
tokens.add("--" + currentOption.getLongOpt());
124136
if (pos != -1)
125137
{
126138
tokens.add(token.substring(pos + 1));
127139
}
128140
}
129141
}
130142

131-
// single hyphen
132-
else if ("-".equals(token))
133-
{
134-
tokens.add(token);
135-
}
136143
else if (token.startsWith("-"))
137144
{
138145
if (token.length() == 2 || options.hasOption(token))
139146
{
140147
processOptionToken(token, stopAtNonOption);
141148
}
149+
else if (!options.getMatchingOptions(token).isEmpty())
150+
{
151+
List matchingOpts = options.getMatchingOptions(token);
152+
if (matchingOpts.size() > 1)
153+
{
154+
throw new AmbiguousOptionException(token, matchingOpts);
155+
}
156+
else
157+
{
158+
Option opt = options.getOption((String) matchingOpts.get(0));
159+
processOptionToken("-" + opt.getLongOpt(), stopAtNonOption);
160+
}
161+
}
142162
// requires bursting
143163
else
144164
{

src/test/org/apache/commons/cli/BasicParserTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,29 @@ public void testLongWithEqualSingleDash() throws Exception
5353
{
5454
// not supported by the BasicParser
5555
}
56+
57+
public void testUnambiguousPartialLongOption1() throws Exception
58+
{
59+
// not supported by the BasicParser
60+
}
61+
62+
public void testUnambiguousPartialLongOption2() throws Exception
63+
{
64+
// not supported by the BasicParser
65+
}
66+
67+
public void testAmbiguousPartialLongOption1() throws Exception
68+
{
69+
// not supported by the BasicParser
70+
}
71+
72+
public void testAmbiguousPartialLongOption2() throws Exception
73+
{
74+
// not supported by the BasicParser
75+
}
76+
77+
public void testPartialLongOptionWithShort() throws Exception
78+
{
79+
// not supported by the BasicParser
80+
}
5681
}

src/test/org/apache/commons/cli/GnuParserTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,29 @@ public void setUp()
2424
super.setUp();
2525
parser = new GnuParser();
2626
}
27+
28+
public void testUnambiguousPartialLongOption1() throws Exception
29+
{
30+
// not supported by the GnuParser
31+
}
32+
33+
public void testUnambiguousPartialLongOption2() throws Exception
34+
{
35+
// not supported by the GnuParser
36+
}
37+
38+
public void testAmbiguousPartialLongOption1() throws Exception
39+
{
40+
// not supported by the GnuParser
41+
}
42+
43+
public void testAmbiguousPartialLongOption2() throws Exception
44+
{
45+
// not supported by the GnuParser
46+
}
47+
48+
public void testPartialLongOptionWithShort() throws Exception
49+
{
50+
// not supported by the GnuParser
51+
}
2752
}

src/test/org/apache/commons/cli/OptionsTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,15 @@ public void testGetOptionsGroups()
159159
assertNotNull(options.getOptionGroups());
160160
assertEquals(2, options.getOptionGroups().size());
161161
}
162+
163+
public void testGetMatchingOpts()
164+
{
165+
Options options = new Options();
166+
options.addOption(OptionBuilder.withLongOpt("version").create());
167+
options.addOption(OptionBuilder.withLongOpt("verbose").create());
168+
169+
assertTrue(options.getMatchingOptions("foo").isEmpty());
170+
assertEquals(1, options.getMatchingOptions("version").size());
171+
assertEquals(2, options.getMatchingOptions("ver").size());
172+
}
162173
}

src/test/org/apache/commons/cli/ParserTestCase.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,94 @@ public void testPropertiesOption() throws Exception
303303
assertEquals("Should be 1 arg left",1,argsleft.size());
304304
assertEquals("Expecting foo","foo",argsleft.get(0));
305305
}
306+
307+
public void testUnambiguousPartialLongOption1() throws Exception
308+
{
309+
String[] args = new String[] { "--ver" };
310+
311+
Options options = new Options();
312+
options.addOption(OptionBuilder.withLongOpt("version").create());
313+
options.addOption(OptionBuilder.withLongOpt("help").create());
314+
315+
CommandLine cl = parser.parse(options, args);
316+
317+
assertTrue("Confirm --version is set", cl.hasOption("version"));
318+
}
319+
320+
public void testUnambiguousPartialLongOption2() throws Exception
321+
{
322+
String[] args = new String[] { "-ver" };
323+
324+
Options options = new Options();
325+
options.addOption(OptionBuilder.withLongOpt("version").create());
326+
options.addOption(OptionBuilder.withLongOpt("help").create());
327+
328+
CommandLine cl = parser.parse(options, args);
329+
330+
assertTrue("Confirm --version is set", cl.hasOption("version"));
331+
}
332+
333+
public void testAmbiguousPartialLongOption1() throws Exception
334+
{
335+
String[] args = new String[] { "--ver" };
336+
337+
Options options = new Options();
338+
options.addOption(OptionBuilder.withLongOpt("version").create());
339+
options.addOption(OptionBuilder.withLongOpt("verbose").create());
340+
341+
boolean caught = false;
342+
343+
try
344+
{
345+
parser.parse(options, args);
346+
}
347+
catch (AmbiguousOptionException e)
348+
{
349+
caught = true;
350+
assertEquals("Partial option", "--ver", e.getOption());
351+
assertNotNull("Matching options null", e.getMatchingOptions());
352+
assertEquals("Matching options size", 2, e.getMatchingOptions().size());
353+
}
354+
355+
assertTrue( "Confirm MissingArgumentException caught", caught );
356+
}
357+
358+
public void testAmbiguousPartialLongOption2() throws Exception
359+
{
360+
String[] args = new String[] { "-ver" };
361+
362+
Options options = new Options();
363+
options.addOption(OptionBuilder.withLongOpt("version").create());
364+
options.addOption(OptionBuilder.withLongOpt("verbose").create());
365+
366+
boolean caught = false;
367+
368+
try
369+
{
370+
parser.parse(options, args);
371+
}
372+
catch (AmbiguousOptionException e)
373+
{
374+
caught = true;
375+
assertEquals("Partial option", "-ver", e.getOption());
376+
assertNotNull("Matching options null", e.getMatchingOptions());
377+
assertEquals("Matching options size", 2, e.getMatchingOptions().size());
378+
}
379+
380+
assertTrue( "Confirm MissingArgumentException caught", caught );
381+
}
382+
383+
public void testPartialLongOptionWithShort() throws Exception
384+
{
385+
String[] args = new String[] { "-ver" };
386+
387+
Options options = new Options();
388+
options.addOption(OptionBuilder.withLongOpt("version").create());
389+
options.addOption(OptionBuilder.hasArg().create('v'));
390+
391+
CommandLine cl = parser.parse(options, args);
392+
393+
assertTrue("Confirm --version is set", cl.hasOption("version"));
394+
assertTrue("Confirm -v is not set", !cl.hasOption("v"));
395+
}
306396
}

xdocs/changes.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
<title>Commons CLI Release Notes</title>
2222
</properties>
2323
<body>
24+
25+
<release version="1.3" date="in SVN">
26+
<action type="add" dev="ebourg" issue="CLI-160">
27+
PosixParser now supports partial long options (--ver instead of --version)
28+
</action>
29+
</release>
2430

2531
<release version="1.2" date="2009-03-19" description="This is a maintenance release containing bug fixes.">
2632
<action type="fix" dev="oheger" issue="CLI-175">
@@ -38,9 +44,6 @@
3844
<action type="fix" dev="bayard" issue="CLI-170">
3945
TypeHandler prints messages to stderr.
4046
</action>
41-
<action type="fix" dev="bayard" issue="CLI-170">
42-
TypeHandler prints messages to stderr.
43-
</action>
4447
<action type="fix" dev="bayard" issue="CLI-162">
4548
Infinite loop in the wrapping code of HelpFormatter.
4649
</action>

0 commit comments

Comments
 (0)