Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Negatable boolean option #143

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions args4j/src/org/kohsuke/args4j/NamedOptionDef.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
/**
* Immutable run-time copy of {@link Option} annotation.
*/
public final class NamedOptionDef extends OptionDef {
public class NamedOptionDef extends OptionDef {
private final String name;
private final String[] aliases;
private final String[] depends;
private final String[] forbids;

/**
* @deprecated
* multi-valuedness as option definition does not make sense. It's driven by the setter.
Expand All @@ -27,7 +27,20 @@ public NamedOptionDef(Option o) {
this.depends = createZeroSizedArrayIfNull(o.depends());
this.forbids = createZeroSizedArrayIfNull(o.forbids());
}


/**
* Create a copy of another NamedOptionDef
* @param o The original option
*/
public NamedOptionDef(NamedOptionDef o) {
super(o.usage(),o.metaVar(),o.required(),o.help(),o.hidden(),o.handler(),false);

this.name = o.name();
this.aliases = o.aliases();
this.depends = o.depends();
this.forbids = o.forbids();
}

private static String[] createZeroSizedArrayIfNull(String in[]) {
if (in == null) {
return new String[0];
Expand All @@ -39,7 +52,7 @@ private static String[] createZeroSizedArrayIfNull(String in[]) {
public String name() {
return name;
}

public String[] aliases() {
return Arrays.copyOf(aliases, aliases.length);
}
Expand All @@ -51,12 +64,12 @@ public String[] depends() {
public String[] forbids() {
return Arrays.copyOf(forbids, forbids.length);
}

@Override
public String toString() {
if (aliases.length > 0) {
if (aliases().length > 0) {
String str = "";
for (String alias : aliases) {
for (String alias : aliases()) {
if (str.length() > 0) {
str += ", ";
}
Expand All @@ -66,7 +79,7 @@ public String toString() {
}
return name();
}

@Override
public boolean isArgument() {
return false;
Expand Down
100 changes: 100 additions & 0 deletions args4j/src/org/kohsuke/args4j/spi/NegatableBooleanOptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.kohsuke.args4j.spi;

import java.util.Arrays;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.IllegalAnnotationError;
import org.kohsuke.args4j.NamedOptionDef;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;

/**
* Boolean {@link OptionHandler} that allows values to be specified as
* --<option> and negated with --no<option>.
*
* Implicitly adds the negated version of the option to the list of aliases
* unless the user has already specified it.
*
* @Author Robin Garner
*/
public class NegatableBooleanOptionHandler extends OptionHandler<Boolean> {

private final String negatedName;

public NegatableBooleanOptionHandler(CmdLineParser parser,
OptionDef option, Setter<? super Boolean> setter) {
super(parser, addNegatedName(option), setter);

this.negatedName = negatedOption(((NamedOptionDef) option).name());
}

@Override
public int parseArguments(Parameters params) throws CmdLineException {
if (params.getParameter(-1).equals(negatedName)) {
setter.addValue(false);
} else {
setter.addValue(true);
}
return 0;
}

@Override
public String getDefaultMetaVariable() {
return null;
}

/**
* Return a new OptionDef that includes the negated name of the option in its
* aliases. If the user has already done this, return the original untouched.
* @param option Original option
* @return The option, with the negated name guaranteed to be in the aliases.
* @throws IllegalAnnotationError if option is not a NanedOptionDef
*/
private static OptionDef addNegatedName(final OptionDef option) {
if (!(option instanceof NamedOptionDef)) {
throw new IllegalAnnotationError("NegatableBooleanOptionHandler can only be used for named options.");
}
if (hasNegatedAlias((NamedOptionDef)option)) {
return option;
}
return new NamedOptionDef((NamedOptionDef)option) {

@Override
public String[] aliases() {
String[] aliases = super.aliases();
String[] result = Arrays.copyOf(aliases, aliases.length+1);
result[aliases.length] = negatedOption(name());
return result;
}

};
}

private static String negatedOption(String name) {
return name.replaceFirst("(-*)", "$1no");
}

private static boolean hasNegatedAlias(NamedOptionDef option) {
return find(negatedOption(option.name()), option.aliases());
}

/**
* Look for a string in an array of strings
* @param str String to look for
* @param strings Array of strings to search
* @return {@code true} if {@code str} is present in {@code strings}
*/
private static boolean find(String str, String[] strings) {
for (String alias : strings) {
if (alias.equals(str)) {
return true;
}
System.err.println(alias + " != " + str);
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.kohsuke.args4j.spi;

import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import junit.framework.TestCase;

/** Simple test for the {@link NegatableBooleanOptionHandler}.
* @author Robin Garner
*/
public class NegatableBooleanOptionHandlerTest extends TestCase {

private static class SimpleTestBean {
@Option(name = "--option",
handler=NegatableBooleanOptionHandler.class,
usage = "Standard option")
private boolean option;
}

private static class ExistingAliasBean {
@Option(name = "--option", aliases = { "--nooption" },
handler=NegatableBooleanOptionHandler.class,
usage = "Standard option")
private boolean option;
}

private static class NoNoTestBean {
@Option(name = "--notify",
handler=NegatableBooleanOptionHandler.class,
usage = "Option that naturally starts with --no")
private boolean option = true;
}


public void testSimpleOptionPresent() throws Exception {
SimpleTestBean args = new SimpleTestBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--option");
assertEquals(true, args.option);
}

public void testSimpleOptionNegated() throws Exception {
SimpleTestBean args = new SimpleTestBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--nooption");
assertEquals(false, args.option);
}

public void testExistingAliasPresent() throws Exception {
ExistingAliasBean args = new ExistingAliasBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--option");
assertEquals(true, args.option);
}

public void testExistingAliasNegated() throws Exception {
ExistingAliasBean args = new ExistingAliasBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--nooption");
assertEquals(false, args.option);
}

public void testNoOptionPresent() throws Exception {
NoNoTestBean args = new NoNoTestBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--notify");
assertEquals(true, args.option);
}

public void testNoOptionNegated() throws Exception {
NoNoTestBean args = new NoNoTestBean();
CmdLineParser parser = new CmdLineParser(args);
parser.parseArgument("--nonotify");
assertEquals(false, args.option);
}
}