bfgroup / Lyra

A simple to use, composable, command line parser for C++ 11 and beyond
https://bfgroup.github.io/Lyra/
Boost Software License 1.0
471 stars 56 forks source link

Surprising behavior of lyra::group, is it a bug? #49

Closed lethal-guitar closed 2 years ago

lethal-guitar commented 3 years ago

Hi, I'm in the process of migrating my project to Lyra, and I've encountered some surprising behavior of the "group" feature where I'm not sure if it is intentional.

Specifically, it seems that as soon as I specify an option belonging to a group, the very next subsequently given option will always be treated as a positional argument even if it should instead be matched by another option. This is easiest to explain by example. Consider the following program (ready for copy & paste and build):

#include <lyra/lyra.hpp>

#include <iostream>
#include <string>

int main(int argc, char** argv) {
  bool someFlag = false;
  std::string requiredArgInGroup;
  std::string optionalArgInGroup;
  std::string positionalArg;

  auto optionsParser =
    lyra::opt(someFlag).name("-f")
    | lyra::group([](const lyra::group&){})
      .add_argument(
        lyra::opt(requiredArgInGroup, "req")
        .name("--req-in-group")
        .required())
      .add_argument(
        lyra::opt(optionalArgInGroup, "opt")
        .name("--opt-in-group"))
    | lyra::arg(positionalArg, "positional");

  if (auto result = optionsParser.parse({argc, argv})) {
    std::cout << std::boolalpha << someFlag << ", " << positionalArg << '\n';
  } else {
    std::cout << result.errorMessage() << '\n';
  }
}

Expected behavior

If I invoke the program and specify the -f option after --req-in-group, I would expect it to result in setting someFlag, and a subsequent positional argument to be assigned to positionalArg.

Expected output:

$ ./a.out --req-in-group test1 -f test2
true, test2

Expected output without positional arg:

$ ./a.out --req-in-group test1 -f
true, 

Actual behavior

What actually happens is that -f is assigned to positionalArg:

$ ./a.out --req-in-group test1 -f
false, -f

And adding a subsequent positional argument (i.e. --req-in-group test1 -f test2) results in a parse error (unrecognized token: test2).

Interestingly, specifying the positional argument first after the group argument results in the behavior I would expect:

$ ./a.out --req-in-group test1 test2 -f
true, test2

This seems like a bug to me, but maybe I'm misunderstanding something about how groups work? Any clarification would be appreciated 🙂 I really like the groups feature and would love to use it!

grafikrobot commented 3 years ago

This is unexpected.. i.e. a bug. Thanks for reporting it. I'll put this on the top of my queue for fixing.

lethal-guitar commented 3 years ago

Hi @grafikrobot, hope you're doing well. Was just wondering if you already had a chance to look into this?

grafikrobot commented 2 years ago

Hey @lethal-guitar .. I have been working on it since first posted. And I did have a solution early. Unfortunately I broke backwards compat along the way. Which I've been, rather slowly, working to fix.

lethal-guitar commented 2 years ago

Ah that's good to hear. Yeah backwards compatibility can be a tricky thing.. Thanks for the update!

grafikrobot commented 2 years ago

And it took way, way, longer than I expected. But I have it fixed. Just need to clean up code and such.

lethal-guitar commented 2 years ago

@grafikrobot awesome! 👍🏻🎉