Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ classes

#ignore checkstile files
.checkstyle

# ignore maven versionsBackup
*.versionsBackup
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ private static void processField(ProcessedCommand processedCommand, Field field)
.renderer(o.renderer())
.parser(o.parser())
.overrideRequired(o.overrideRequired())
.negatable(o.negatable())
.negationPrefix(o.negationPrefix())
.build()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public void addOption(ProcessedOption opt) throws OptionParserException {
this.options.add(new ProcessedOption(verifyThatNamesAreUnique(opt.shortName(), opt.name()), opt.name(),
opt.description(), opt.getArgument(), opt.isRequired(), opt.getValueSeparator(), opt.askIfNotSet(), opt.acceptNameWithoutDashes(), opt.selectorType(),
opt.getDefaultValues(), opt.type(), opt.getFieldName(), opt.getOptionType(), opt.converter(),
opt.completer(), opt.validator(), opt.activator(), opt.getRenderer(), opt.parser(), opt.doOverrideRequired()));
opt.completer(), opt.validator(), opt.activator(), opt.getRenderer(), opt.parser(), opt.doOverrideRequired(),
opt.isNegatable(), opt.getNegationPrefix()));

options.get(options.size()-1).setParent(this);
}
Expand All @@ -133,7 +134,7 @@ private void setOptions(List<ProcessedOption> options) throws OptionParserExcept
opt.description(), opt.getArgument(), opt.isRequired(), opt.getValueSeparator(), opt.askIfNotSet(), opt.acceptNameWithoutDashes(), opt.selectorType(),
opt.getDefaultValues(), opt.type(), opt.getFieldName(), opt.getOptionType(),
opt.converter(), opt.completer(), opt.validator(), opt.activator(), opt.getRenderer(),
opt.parser(), opt.doOverrideRequired()));
opt.parser(), opt.doOverrideRequired(), opt.isNegatable(), opt.getNegationPrefix()));

this.options.get(this.options.size()-1).setParent(this);
}
Expand Down Expand Up @@ -246,9 +247,14 @@ public ProcessedOption findOptionNoActivatorCheck(String name) {
*/
public ProcessedOption searchAllOptions(String input) {
if (input.startsWith("--")) {
ProcessedOption currentOption = findLongOptionNoActivatorCheck(input.substring(2));
String optionName = input.substring(2);
ProcessedOption currentOption = findLongOptionNoActivatorCheck(optionName);
if(currentOption == null && input.contains("="))
currentOption = startWithLongOptionNoActivatorCheck(input.substring(2));
currentOption = startWithLongOptionNoActivatorCheck(optionName);
// Check for negated options (e.g., --no-verbose)
if (currentOption == null) {
currentOption = findNegatedOptionNoActivatorCheck(optionName);
}
if (currentOption != null)
currentOption.setLongNameUsed(true);
//need to handle spaces in option names
Expand Down Expand Up @@ -299,6 +305,42 @@ public ProcessedOption findLongOptionNoActivatorCheck(String name) {
return null;
}

/**
* Find an option by its negated name (e.g., "no-verbose" for option "verbose").
* If found, marks the option as negated.
*
* @param name the negated name to search for
* @return the matching option, or null if not found
*/
public ProcessedOption findNegatedOption(String name) {
for (ProcessedOption option : getOptions()) {
if (option.isNegatable() && option.getNegatedName() != null &&
option.getNegatedName().equals(name) &&
option.activator().isActivated(new ParsedCommand(this))) {
option.setNegatedByUser(true);
return option;
}
}
return null;
}

/**
* Find an option by its negated name without checking activator.
*
* @param name the negated name to search for
* @return the matching option, or null if not found
*/
public ProcessedOption findNegatedOptionNoActivatorCheck(String name) {
for (ProcessedOption option : getOptions()) {
if (option.isNegatable() && option.getNegatedName() != null &&
option.getNegatedName().equals(name)) {
option.setNegatedByUser(true);
return option;
}
}
return null;
}

public ProcessedOption findBareLongOption(String name) {
for (ProcessedOption option : getOptions())
if(option.name() != null && option.name().equals(name) && option.acceptNameWithoutDashes())
Expand Down Expand Up @@ -429,15 +471,21 @@ public boolean isGenerateVersionOptionSet() {

/**
* Return all option names that not already have a value
* and is enabled
* and is enabled. For negatable options, also includes the negated form.
*/
public List<TerminalString> getOptionLongNamesWithDash() {
List<ProcessedOption> opts = getOptions();
List<TerminalString> names = new ArrayList<>(opts.size());
for (ProcessedOption o : opts) {
if(o.getValues().size() == 0 &&
o.activator().isActivated(new ParsedCommand(this)))
o.activator().isActivated(new ParsedCommand(this))) {
names.add(o.getRenderedNameWithDashes());
// Also add the negated form for negatable options
TerminalString negated = o.getRenderedNegatedNameWithDashes();
if (negated != null) {
names.add(negated);
}
}
}

return names;
Expand All @@ -452,6 +500,15 @@ public List<TerminalString> findPossibleLongNamesWithDash(String name) {
(o.name().startsWith(name) && o.getValues().size() == 0)) &&
o.activator().isActivated(new ParsedCommand(this)))
names.add(o.getRenderedNameWithDashes());
// Also check negated option names for negatable options
if (o.isNegatable() && o.getNegatedName() != null &&
o.getNegatedName().startsWith(name) && o.getValues().size() == 0 &&
o.activator().isActivated(new ParsedCommand(this))) {
TerminalString negated = o.getRenderedNegatedNameWithDashes();
if (negated != null) {
names.add(negated);
}
}
}
return names;
}
Expand All @@ -467,6 +524,11 @@ public List<String> findPossibleLongNames(String name) {
(o.name().startsWith(name) && o.getValues().size() == 0)) &&
o.activator().isActivated(new ParsedCommand(this)))
names.add(o.name());
// Also check negated option names for negatable options
if (o.isNegatable() && o.getNegatedName() != null &&
o.getNegatedName().startsWith(name) && o.getValues().size() == 0 &&
o.activator().isActivated(new ParsedCommand(this)))
names.add(o.getNegatedName());
}
return names;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ public final class ProcessedOption {
private boolean askIfNotSet = false;
private boolean acceptNameWithoutDashes = false;
private final SelectorType selectorType;
private boolean negatable = false;
private String negationPrefix = "no-";
private boolean negatedByUser = false;

public ProcessedOption(char shortName, String name, String description,
String argument, boolean required, char valueSeparator, boolean askIfNotSet, boolean acceptNameWithoutDashes,
Expand All @@ -95,7 +98,7 @@ public ProcessedOption(char shortName, String name, String description,
OptionValidator optionValidator,
OptionActivator activator,
OptionRenderer renderer, OptionParser parser,
boolean overrideRequired) throws OptionParserException {
boolean overrideRequired, boolean negatable, String negationPrefix) throws OptionParserException {

if(shortName != '\u0000')
this.shortName = String.valueOf(shortName);
Expand Down Expand Up @@ -127,6 +130,8 @@ public ProcessedOption(char shortName, String name, String description,
this.renderer = renderer;

this.defaultValues = PropertiesLookup.checkForSystemVariables(defaultValue);
this.negatable = negatable;
this.negationPrefix = negationPrefix != null ? negationPrefix : "no-";

properties = new HashMap<>();
values = new ArrayList<>();
Expand Down Expand Up @@ -285,6 +290,37 @@ public SelectorType selectorType() {
return selectorType;
}

public boolean isNegatable() {
return negatable;
}

public String getNegationPrefix() {
return negationPrefix;
}

/**
* Returns the negated form of this option's name.
* For example, if name is "verbose" and prefix is "no-", returns "no-verbose".
* Returns null if this option is not negatable.
*/
public String getNegatedName() {
return negatable && name != null ? negationPrefix + name : null;
}

/**
* Returns true if this option was specified in its negated form (e.g., --no-verbose).
*/
public boolean isNegatedByUser() {
return negatedByUser;
}

/**
* Sets whether this option was specified in its negated form.
*/
public void setNegatedByUser(boolean negatedByUser) {
this.negatedByUser = negatedByUser;
}

public void clear() {
if(values != null)
values.clear();
Expand All @@ -294,6 +330,7 @@ public void clear() {
endsWithSeparator = false;
cursorOption = false;
cursorValue = false;
negatedByUser = false;
}

public String getDisplayName() {
Expand All @@ -315,6 +352,22 @@ public TerminalString getRenderedNameWithDashes() {
return new TerminalString( hasValue() ? prefix+name+"=" : prefix+name, renderer.getColor(), renderer.getTextType());
}

/**
* Returns the negated form of the option name with dashes for completion.
* For example, for option "verbose" with prefix "no-", returns "--no-verbose".
* Returns null if this option is not negatable.
*/
public TerminalString getRenderedNegatedNameWithDashes() {
if (!negatable || name == null) {
return null;
}
// Negatable options are boolean, so they don't have a value suffix
if (renderer == null || !ansiMode)
return new TerminalString("--" + negationPrefix + name, true);
else
return new TerminalString("--" + negationPrefix + name, renderer.getColor(), renderer.getTextType());
}

public int getFormattedLength() {
StringBuilder sb = new StringBuilder();
if(shortName != null)
Expand All @@ -323,6 +376,10 @@ public int getFormattedLength() {
if(sb.toString().trim().length() > 0)
sb.append(", ");
sb.append("--").append(name);
// Add negated form for negatable options
if(negatable) {
sb.append(", --").append(negationPrefix).append(name);
}
}
if(argument != null && argument.length() > 0) {
sb.append("=<").append(argument).append(">");
Expand All @@ -344,6 +401,10 @@ public String getFormattedOption(int offset, int descriptionStart, int width) {
if(shortName != null)
sb.append(", ");
sb.append("--").append(name);
// Add negated form for negatable options
if(negatable) {
sb.append(", --").append(negationPrefix).append(name);
}
}
if(argument != null && argument.length() > 0) {
sb.append("=<").append(argument).append(">");
Expand Down Expand Up @@ -397,7 +458,11 @@ public void injectValueIntoField(Object instance, InvocationProviders invocation
constructor.setAccessible(true);
}
if(optionType == OptionType.NORMAL || optionType == OptionType.BOOLEAN || optionType == OptionType.ARGUMENT) {
if(getValue() != null)
// Handle negatable options - when used in negated form (e.g., --no-verbose), inject false
if(negatedByUser && optionType == OptionType.BOOLEAN) {
field.set(instance, doConvert("false", invocationProviders, instance, aeshContext, doValidation));
}
else if(getValue() != null)
field.set(instance, doConvert(getValue(), invocationProviders, instance, aeshContext, doValidation));
else if(defaultValues.size() > 0) {
field.set(instance, doConvert(defaultValues.get(0), invocationProviders, instance, aeshContext, doValidation));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public class ProcessedOptionBuilder {
private boolean askIfNotSet = false;
private boolean acceptNameWithoutDashes = false;
private SelectorType selectorType;
private boolean negatable = false;
private String negationPrefix = "no-";

private ProcessedOptionBuilder() {
defaultValues = new ArrayList<>();
Expand Down Expand Up @@ -299,6 +301,21 @@ private OptionParser initParser(Class<? extends OptionParser> parser) {
return null;
}

/**
* Set whether this option is negatable (supports --no-{name} form).
* Only valid for boolean options.
*/
public ProcessedOptionBuilder negatable(boolean negatable) {
return apply(c -> c.negatable = negatable);
}

/**
* Set the prefix used for negation. Default is "no-".
*/
public ProcessedOptionBuilder negationPrefix(String negationPrefix) {
return apply(c -> c.negationPrefix = negationPrefix);
}

public ProcessedOption build() throws OptionParserException {
if(optionType == null) {
if(!hasValue)
Expand Down Expand Up @@ -345,8 +362,13 @@ else if(hasMultipleValues)
//if(renderer == null)
// renderer = new NullOptionRenderer();

// Validate that negatable is only used with boolean types
if(negatable && type != Boolean.class && type != boolean.class) {
throw new OptionParserException("Option '" + name + "' is marked as negatable but is not a boolean type");
}

return new ProcessedOption(shortName, name, description, argument, required,
valueSeparator, askIfNotSet, acceptNameWithoutDashes, selectorType, defaultValues, type, fieldName, optionType, converter,
completer, validator, activator, renderer, parser, overrideRequired);
completer, validator, activator, renderer, parser, overrideRequired, negatable, negationPrefix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,14 @@ private void preProcessOption(ProcessedOption option, ParsedLineIterator iterato
word = Parser.switchSpacesToEscapedSpacesInWord(word);
if(option.isLongNameUsed()) {
String optionPart = word.startsWith("--") ? word.substring(2) : word;
if(optionPart.length() != option.name().length())
processOption(option, optionPart, option.name());
// For negatable options, we need to use the correct name for comparison
String nameToMatch = option.isNegatedByUser() && option.getNegatedName() != null
? option.getNegatedName() : option.name();
if(optionPart.length() != nameToMatch.length())
processOption(option, optionPart, nameToMatch);
else if(option.getOptionType() == OptionType.BOOLEAN) {
option.addValue("true");
// For negatable options, use "false" if specified in negated form
option.addValue(option.isNegatedByUser() ? "false" : "true");
status = Status.NULL;
}
else
Expand Down
15 changes: 15 additions & 0 deletions aesh/src/main/java/org/aesh/command/option/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,19 @@
*/
Class<? extends OptionParser> parser() default AeshOptionParser.class;

/**
* When set to true for boolean options, automatically supports --no-{name}
* to set the value to false. Only valid for boolean/Boolean field types.
* For example, if the option is named "verbose", both --verbose and --no-verbose
* will be recognized.
*/
boolean negatable() default false;

/**
* The prefix used for negation when negatable=true.
* Default is "no-". For example, with name="verbose" and default prefix,
* the negated form will be --no-verbose.
*/
String negationPrefix() default "no-";

}
Loading