picocli
picocli copied to clipboard
helpCommand attribute causes deeply-nested sub-command to behave differently than expected (in 4.0.0-beta-2)
The sample classes given below demonstrate the issue. As the code below is written now, it works as expected, when I run a nested-subcommand with options:
dkelkhoff@mac:> top middle1 bottom1b -v
In bottom1b.call. verbose: true
However, if I add helpCommand = true
to the @Command
annotation on the NestingTop
class, then I get this unexpected behavior from the same command-line run
dkelkhoff@mac:> top middle1 bottom1b -v
In top.call
Usage: top [-hV] [COMMAND]
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
middle1
middle2
Now, I agree it was wrong of me to use helpCommand
where I did, but it was fairly hard to trace it down. Also, if I only used 1-level of sub-commands, then everything always worked as expected - it was only when I added the nested sub-commands that i couldn't get them to run.
import java.util.concurrent.Callable;
import picocli.CommandLine;
import picocli.CommandLine.Command;
/*******************************************************************************
** Hierarchy of sub-commands looks like this:
**
** top
** middle1
** bottom1a
** bottom1b
** middle2
** bottom2a
** bottom2b
**
*******************************************************************************/
@Command(name = "top", subcommands = { NestingTop.Middle1.class, NestingTop.Middle2.class }, mixinStandardHelpOptions = true, version = "0")
public class NestingTop implements Callable<Void>
{
public static void main(String[] args)
{
new CommandLine(new NestingTop()).execute(args);
}
public Void call()
{
System.out.println("In top.call");
new CommandLine(this).usage(System.out);
return (null);
}
@Command(name = "middle1", subcommands = { Bottom1A.class, Bottom1B.class })
public static class Middle1 implements Callable<Void>
{
public Void call()
{
System.out.println("In middle1.call");
new CommandLine(this).usage(System.out);
return (null);
}
}
@Command(name = "middle2", subcommands = { Bottom2A.class, Bottom2B.class })
public static class Middle2 implements Callable<Void>
{
public Void call()
{
System.out.println("In middle2.call");
new CommandLine(this).usage(System.out);
return (null);
}
}
@Command(name = "bottom1a", subcommands = {})
public static class Bottom1A implements Callable<Void>
{
public Void call()
{
System.out.println("In bottom1a.call");
return (null);
}
}
@Command(name = "bottom1b", subcommands = {})
public static class Bottom1B implements Callable<Void>
{
@CommandLine.Option(names = { "-v", "--verbose" })
private boolean verbose;
public Void call()
{
System.out.println("In bottom1b.call. verbose: " + verbose);
return (null);
}
}
@Command(name = "bottom2a", subcommands = {})
public static class Bottom2A implements Callable<Void>
{
public Void call()
{
System.out.println("In bottom2a.call");
return (null);
}
}
@Command(name = "bottom2b", subcommands = {})
public static class Bottom2B implements Callable<Void>
{
public Void call()
{
System.out.println("In bottom2b.call");
return (null);
}
}
}
Thanks for raising this!
One idea to deal with this is to introduce a rule that commands with helpCommand = true
should not have subcommands.
(Can anyone think of other situations that should be prevented in a similar way? It would be good if we can formulate rules in a similar way to prevent such situations.)
The CommandLine
constructor can throw an InitializationException if it detects a command that violates the rule(s), and the annotation processor can be enhanced to emit a compilation error if the annotation processor detects a command that violates them.
@dkelkhoff Revisiting this ticket after a long while...
Stepping through the code with your example, I did notice that the picocli parser does not expect the top-level command to be a help command, so internally this is not registered, and validation for missing required args is still happening, which shouldn't be happening. So that is one thing that I'll fix.
On the topic of preventing unintended usage:
I'm thinking to improve the javadoc for the @Command(helpCommand = ...)
annotation.
Current:
Set this attribute to true if this subcommand is a help command, and required options and positional parameters of the parent command should not be validated. (...)
Proposed:
Set this attribute to true only if this subcommand is a
help
command that prints a usage help message, and therefore required options and positional parameters of the parent command should not be validated. (...)
Thoughts?