Skip to content

Commit 9cd78b4

Browse files
author
Oliver Heger
committed
CLI-150: Make WriteableCommandLineImpl.looksLikeOption() more intelligent, so that it does not misinterpret negative numbers passed to arguments as options.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/cli/trunk@681191 13f79535-47bb-0310-9956-ffa450edef68
1 parent 028893d commit 9cd78b4

4 files changed

Lines changed: 151 additions & 10 deletions

File tree

src/java/org/apache/commons/cli2/WriteableCommandLine.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,20 @@ public interface WriteableCommandLine extends CommandLine {
9393
* @return true if the argument looks like an Option trigger
9494
*/
9595
boolean looksLikeOption(final String argument);
96+
97+
/**
98+
* Returns the option that is currently processed.
99+
*
100+
* @return the current option
101+
*/
102+
Option getCurrentOption();
103+
104+
/**
105+
* Sets the current option. This method is called by concrete option
106+
* implementations during command line processing. It enables the command
107+
* line to keep track about the option that is currently processed.
108+
*
109+
* @param currentOption the new current option
110+
*/
111+
void setCurrentOption(Option currentOption);
96112
}

src/java/org/apache/commons/cli2/commandline/WriteableCommandLineImpl.java

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public class WriteableCommandLineImpl
4848
private final Map defaultSwitches = new HashMap();
4949
private final List normalised;
5050
private final Set prefixes;
51+
private Option currentOption;
52+
private String checkForOption;
5153

5254
/**
5355
* Creates a new WriteableCommandLineImpl rooted on the specified Option, to
@@ -60,6 +62,15 @@ public WriteableCommandLineImpl(final Option rootOption,
6062
final List arguments) {
6163
this.prefixes = rootOption.getPrefixes();
6264
this.normalised = arguments;
65+
setCurrentOption(rootOption);
66+
}
67+
68+
public Option getCurrentOption() {
69+
return currentOption;
70+
}
71+
72+
public void setCurrentOption(Option currentOption) {
73+
this.currentOption = currentOption;
6374
}
6475

6576
public void addOption(Option option) {
@@ -141,7 +152,7 @@ public List getValues(final Option option,
141152
}
142153
}
143154
}
144-
155+
145156
return valueList == null ? Collections.EMPTY_LIST : valueList;
146157
}
147158

@@ -216,16 +227,48 @@ public Set getProperties() {
216227
return getProperties(new PropertyOption());
217228
}
218229

219-
public boolean looksLikeOption(final String trigger) {
220-
for (final Iterator i = prefixes.iterator(); i.hasNext();) {
221-
final String prefix = (String) i.next();
230+
/**
231+
* Tests whether the passed in trigger looks like an option. This
232+
* implementation first checks whether the passed in string starts with a
233+
* prefix that indicates an option. If this is the case, it is also checked
234+
* whether an option of this name is known for the current option. (This can
235+
* lead to reentrant invocations of this method, so care has to be taken
236+
* about this.)
237+
*
238+
* @param trigger the command line element to test
239+
* @return a flag whether this element seems to be an option
240+
*/
241+
public boolean looksLikeOption(final String trigger)
242+
{
243+
if (checkForOption != null)
244+
{
245+
// this is a reentrant call
246+
return !checkForOption.equals(trigger);
247+
}
222248

223-
if (trigger.startsWith(prefix)) {
224-
return true;
249+
checkForOption = trigger;
250+
try
251+
{
252+
for (final Iterator i = prefixes.iterator(); i.hasNext();)
253+
{
254+
final String prefix = (String) i.next();
255+
256+
if (trigger.startsWith(prefix))
257+
{
258+
if (getCurrentOption().canProcess(this, trigger)
259+
|| getCurrentOption().findOption(trigger) != null)
260+
{
261+
return true;
262+
}
263+
}
225264
}
226-
}
227265

228-
return false;
266+
return false;
267+
}
268+
finally
269+
{
270+
checkForOption = null;
271+
}
229272
}
230273

231274
public String toString() {

src/java/org/apache/commons/cli2/option/GroupImpl.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public boolean canProcess(final WriteableCommandLine commandLine,
136136
}
137137
}
138138

139-
if (commandLine.looksLikeOption(arg)) {
139+
if (looksLikeOption(commandLine, arg)) {
140140
return false;
141141
}
142142

@@ -188,7 +188,7 @@ public void process(final WriteableCommandLine commandLine,
188188
else {
189189
// it might be an anonymous argument continue search
190190
// [START argument may be anonymous
191-
if (commandLine.looksLikeOption(arg)) {
191+
if (looksLikeOption(commandLine, arg)) {
192192
// narrow the search
193193
final Collection values = optionMap.tailMap(arg).values();
194194

@@ -498,6 +498,26 @@ public void defaults(final WriteableCommandLine commandLine) {
498498
option.defaults(commandLine);
499499
}
500500
}
501+
502+
/**
503+
* Helper method for testing whether an element of the command line looks
504+
* like an option. This method queries the command line, but sets the
505+
* current option first.
506+
*
507+
* @param commandLine the command line
508+
* @param trigger the trigger to be checked
509+
* @return a flag whether this element looks like an option
510+
*/
511+
private boolean looksLikeOption(final WriteableCommandLine commandLine,
512+
final String trigger) {
513+
Option oldOption = commandLine.getCurrentOption();
514+
try {
515+
commandLine.setCurrentOption(this);
516+
return commandLine.looksLikeOption(trigger);
517+
} finally {
518+
commandLine.setCurrentOption(oldOption);
519+
}
520+
}
501521
}
502522

503523

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
package org.apache.commons.cli2.bug;
18+
19+
import junit.framework.TestCase;
20+
21+
import org.apache.commons.cli2.Argument;
22+
import org.apache.commons.cli2.CommandLine;
23+
import org.apache.commons.cli2.Group;
24+
import org.apache.commons.cli2.Option;
25+
import org.apache.commons.cli2.OptionException;
26+
import org.apache.commons.cli2.builder.ArgumentBuilder;
27+
import org.apache.commons.cli2.builder.DefaultOptionBuilder;
28+
import org.apache.commons.cli2.builder.GroupBuilder;
29+
import org.apache.commons.cli2.commandline.Parser;
30+
import org.apache.commons.cli2.validation.NumberValidator;
31+
32+
/**
33+
* An argument whose value is a negative number is mistaken as an option.
34+
*
35+
* @author Oliver Heger
36+
* @version $Id$
37+
*/
38+
public class BugCLI150Test extends TestCase
39+
{
40+
public void testNegativeNumber() throws OptionException
41+
{
42+
final DefaultOptionBuilder oBuilder = new DefaultOptionBuilder();
43+
final ArgumentBuilder aBuilder = new ArgumentBuilder();
44+
final GroupBuilder gBuilder = new GroupBuilder();
45+
46+
final Argument numArg = aBuilder.withValidator(
47+
NumberValidator.getNumberInstance()).withMinimum(1)
48+
.withMaximum(1).create();
49+
final Option numOpt = oBuilder.withLongName("num").withArgument(numArg)
50+
.create();
51+
final Group options = gBuilder.withOption(numOpt).create();
52+
53+
final Parser parser = new Parser();
54+
parser.setGroup(options);
55+
56+
CommandLine cl = parser.parse(new String[] {
57+
"--num", "-42"
58+
});
59+
Number num = (Number) cl.getValue(numOpt);
60+
assertEquals("Wrong option value", -42, num.intValue());
61+
}
62+
}

0 commit comments

Comments
 (0)