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

helpCommand attribute causes deeply-nested sub-command to behave differently than expected (in 4.0.0-beta-2) #764

Open
dkelkhoff opened this issue Jul 5, 2019 · 2 comments
Milestone

Comments

@dkelkhoff
Copy link

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);
      }
   }

}
@dkelkhoff dkelkhoff changed the title helpCommand attribute causes deeply-nested sub-command to behave differently than expected helpCommand attribute causes deeply-nested sub-command to behave differently than expected (in 4.0.0-beta-2) Jul 5, 2019
@remkop
Copy link
Owner

remkop commented Jul 5, 2019

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.

@remkop remkop added this to the 4.6 milestone Jun 28, 2020
@remkop
Copy link
Owner

remkop commented Feb 21, 2022

@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?

@remkop remkop modified the milestones: 4.7, 4.8 Jun 28, 2022
@remkop remkop modified the milestones: 4.8, 4.9 Aug 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants