rvesse / airline

Java annotation-based framework for parsing Git like command line structures with deep extensibility

Home Page:https://rvesse.github.io/airline/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow boolean or flag options such as --flag to be negated by prefixing with --no-flag

rkhaja opened this issue · comments

This is for an enhancement request to allow boolean or flag options to be negated.

For example if I had an option:

@Option(title = "clobber", 
        name = "--clobber", 
        description = "[Optional] (default=true) Clobber a directory.  If you wish to keep "
            + "the directory specify --no-clobber")
@Once
private boolean clobber = true;

I would like to be able to run the command as follows:

# The following should result in variable clobber=true
> my_command

# The following should result in variable clobber=true                                 
> my_command --clobber                

# The following should result in variable clobber=false
> my_command --no-clobber          

Here is how I accomplished negation of flags:

@Option(name = "--clobber",
        description = "[Optional] (default=true) Clobber a directory.")
@Once
@MutuallyExclusiveWith(tag = "clobberFlags")
private boolean clobber = true;

@Option(name = "--no-clobber", 
        description = "[Optional] (default=false) Don't clobber a directory.",
        hidden = true)
@Once
@MutuallyExclusiveWith(tag = "clobberFlags")
private boolean noClobber = false;

Then to detect if the option was negated, you would have an if condition as follows:

if (clobber == noClobber) {
    System.out.println("Option --no-clobber was specified\n");
}

It should work whether the default starting values for clobber=true and noClobber=false (as is the case in the definition above) or if the default values start off as the opposite as what I have defined above (ie. clobber=false and noClobber=true).

I think that it should be possible to implement this logic somehow into the airline framework, so that the programmer can more easily define negatable options/flags.

I think this is definitely something that could be added in the future. The workaround you are using currently is quite elegant but I agree this could be more tightly integrated.

My thinking on this currently is to include a new parser option flagNegationPrefix. Then when you have a negatable flag you would include both normal name and the negating name in a single option definition. Then the option parsers would be enhanced to recognise when a flag option starts with the specified negation prefix and set the value to false rather than true.

So then you would end up with declarations like the following:

@Command(name = "example")
@Parser(flagNegationPrefix = "--no-")
public class ExampleCommand {
  @Option(name = { "--clobber", "--no-clobber" })
  private boolean clobber = false;
}

Would that work for you?

Hello Rob,
Thank you for considering my request for a future release :)

Yes, your suggestion would definitely work.

I was previously using airlift/airline and switched over to your fork and would be happy to contribute either to the actual implementation of the feature I have requested and/or additionally documentation. Though I have used annotations for airline in my code, I have never coded an annotation so I would need some guidance. Please let me know if I can be of any help.

Best regards,
-Razi

I have implemented this today and pushed new snapshots to the OSSRH Maven repository

Ok, I will give it a try.

Hello Rob,

Just thought to give you an update.

With some difficulty :( I managed to add the snapshots repository to my maven pom and the 2.3.0-SNAPSHOT for airline. In my code, I briefly tried flag negation using the example you provided above on Feb 17. Unfortunately it did not work for me. I did see that you created test cases to test: --true/--no-true and --false/--no-false, so I must be doing something wrong.

My use case is exactly the one described in this ticket. I need to clobber a directory by default, so clobber is initialized to true. The problem I've encountered was that when I specified --no-clobber, the default value of true was still set.

If it's ok with you, I'd like to keep this ticket open, and report back here after I've had some more time to try it out again (which might be in a couple of weeks :( but this feature won't fall off my radar).

I'll write again soonish. :)

I have created a 2.3.0 release which contains the solution. I will close this thing now but feel free to reopen it once you are able to provide a new example which does not work.

That works. Thank you!

No problem, thanks again for your input

Hello Rob,
I've had a chance to try this out again.

If you open up this line in an IDE: https://github.com/rkhaja/airline/blob/01aec8bf636379cd631263411e898756fa125dd6/airline-core/src/main/java/com/github/rvesse/airline/annotations/Parser.java#L226

    String flagNegationPrefix() default "";

you will probably find that the method is never used.
As a result, when I define my Cli with an annotation as follows:

@Cli(
        name = "negationtestapp",
        defaultCommand = Help.class,
        commands = {
                Help.class,
                NegationTest.class
        },
        parserConfiguration = @Parser(flagNegationPrefix = "--no-")
)
public class NegationTestApp {
// class contents
}

or my command as follows:

@Command(
        name = "negationtest",
        description = "Test negation.",
)
@Parser(flagNegationPrefix = "--no-")
public class NegationTest implements Runnable {
// class contents
}

the parser does not know about the flagNegationPrefix="--no-".

I have submitted a pull request: #68