Apache CLI: Required options contradicts with help

2020-08-10 07:32发布

If I have 2 options defined as required such as:

 public static void main(String [] args){
      Options options= new Options();
      Option  inputFileOp=Option.builder("i").longOpt("input").hasArg().desc("Input file").argName("file").required().build();
        options.addOption(inputFileOp);

      Option outputFileOp=Option.builder("o").longOpt("output").hasArg().desc("Output file").argName("file").required().build();
        options.addOption(outputFileOp);

and a help option

    Option helpOp =new Option("h",false,"Show Help");
    helpOp.setLongOpt("help");
    helpOptions.addOption(helpOp);

and parser

DefaultParser parser = new DefaultParser();
CommandLine cmd=parser.parse(options,args);

if(cmd.hasOption(helpOp.getOpt())){
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp( "MyApp.sh", options );
        System.exit(0);
    }

}

When the user input for example myApp -h .. An exception is raised in the parsing step that there are missing required options, While I want to print the help data.

How to allow calling the help while keeping these options declared as required?

3条回答
我只想做你的唯一
2楼-- · 2020-08-10 08:11

Add another method, as the previous poster said, but make sure you stop at the first unrecognized argument.

private static boolean hasHelp(final Option help, final String[] args) throws ParseException
{
    Options options = new Options();
    options.addOption(help);
    CommandLineParser parser = new DefaultParser();
    CommandLine cmd = parser.parse(options, args, true);
    return cmd.hasOption(help.getOpt());
}

If stopAtNonOption is set to false, then this function would throw for valid arguments, like java -jar app.jar -doStuff

查看更多
我命由我不由天
3楼-- · 2020-08-10 08:17

The code for DefaultParser appears to always call the checkRequiredArgs() method. Which seems to indicate that you cannot, in one fell swoop, avoid the problem.

The way we have addressed this situation in the past, perhaps in a suboptimal fashion, is to parse the command line twice. The parsing is fast, so the overhead it minimal.

We created a method checkForHelp(String[] args) that takes the (String[] args). It adds only the help option to an options, parses the command line, and then ascertains whether help is specified. If so, the help is printed, and the program exits. Otherwise, the full set of options is processed. This approach allows for the required fields to be processed as expected. Note that the help option must also be added in the main list.

  public static Option helpOption = Option.builder("h")
          .longOpt("help")
          .required(false)
          .hasArg(false)
          .build();

  public static boolean checkForHelp(String[] args) throws ParseException  { 
    boolean hasHelp = false;

    Options options = new Options();


    try {
      options.addOption(helpOption);

      CommandLineParser parser = new DefaultParser();

      CommandLine cmd = parser.parse(options, args);

      if (cmd.hasOption(helpOption.getOpt())) {
          hasHelp = true;
      }

    }
    catch (ParseException e) {
      throw e;
    }

    return hasHelp;
  }

Then in the main method, something akin to:

    options.addOption(hostOption);
    options.addOption(portOption);
    options.addOption(serviceNameOption);
    options.addOption(helpOption); // <-- must also be here to avoid exception

    try {
        if (checkForHelp(args)) {
            HelpFormatter fmt = new HelpFormatter();
            fmt.printHelp("Help", options);
            return;
        }

        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = parser.parse(options, args);


        if (cmd.hasOption("host")) {
            host = cmd.getOptionValue("host");
            System.out.println(host); // gets in here but prints null
        }
        if (cmd.hasOption("port")) {
            port = ((Number) cmd.getParsedOptionValue("port")).intValue();
            System.out.println(port); // gets in here but throws a null
                                      // pointer exception

        }
        if (cmd.hasOption("service_name")) {
            serviceName = cmd.getOptionValue("service_name");
            System.out.println(serviceName); // gets in here but prints null
        }
    }
    catch (Exception e) {
        e.printStackTrace();
    }

EDIT: as it turns out, this approach is similar to the answer provided here: Commons CLI required groups. I guess I feel better that our approach has others supporting what we believed.

EDIT2: In a quick test, I believe the problem of having required options may be avoided by using an "OptionGroup". Here is a revised checkForHelp that works by adding all of the options to an OptionGroup. In my quick testing, it avoids the problem that was present if one did, e.g., ("--arg1 --help").

public static boolean checkForHelp(String[] args) throws ParseException
{
    boolean hasHelp = false;

    Options options = new Options();


    try {
        options.addOption(hostOption);  //has required set
        options.addOption(portOption);
        options.addOption(serviceNameOption);
        options.addOption(helpOption);            

        // create an option group
        OptionGroup og = new OptionGroup();
        og.addOption(hostOption);
        og.addOption(portOption);
        og.addOption(serviceNameOption);
        og.addOption(helpOption);

        CommandLineParser parser = new DefaultParser();

        CommandLine cmd = parser.parse(options, args, false);

        if (cmd.hasOption(helpOption.getOpt()) || cmd.hasOption(helpOption.getLongOpt())) {
            hasHelp = true;
        }

    }
    catch (ParseException e) {
        throw e;
    }

    return hasHelp;
}
查看更多
▲ chillily
4楼-- · 2020-08-10 08:19

Wouldn't it be easier to just parse raw args and check for --help/-h or help.getOpt()/help.getLongOpt() keyword before parsing using DefaultParser? This way you avoid double parsing overhead.

for (String s : args) {
    if (s.equals("-h") || s.equals("--help")) {  // or use help.getOpt() || help.getLongOpt()
        formatter.printHelp("ApplicationName", arguments);
        System.exit(1);
    }
}
查看更多
登录 后发表回答