-
Notifications
You must be signed in to change notification settings - Fork 7
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
[MISC] Subcommands alternative syntax #234
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
17e7727
to
33f3f2c
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #234 +/- ##
==========================================
+ Coverage 91.90% 91.93% +0.03%
==========================================
Files 17 17
Lines 1618 1662 +44
==========================================
+ Hits 1487 1528 +41
- Misses 131 134 +3 ☔ View full report in Codecov by Sentry. |
Thanks for picking this up so quickly! A few points:
I don't completely understand the code, yet. Does a successful If successful Your example would then look like this: sharg::parser git_parser{"git", argc, argv, sharg::update_notifications::on};
if (auto pull_parser = git_parser.add_subcommand("pull"))
{
std::string repository{};
pull_parser->add_positional_option(repository, sharg::config{.description = "pull"});
pull_parser->parse();
}
else if (auto push_parser = git_parser.add_subcommand("push"))
{
std::string repository{};
push_parser->add_positional_option(repository, sharg::config{.description = "push"});
push_parser->parse();
}
else if (auto remote_parser = git_parser.add_subcommand("remote"))
{
remote_parser->add_section("Remote options");
// remote_parser->parse(); // Caveat
if (auto recursive_sub_parser = remote_parser->add_subcommand("set-url"))
{
std::string repository{};
recursive_sub_parser->add_positional_option(repository, sharg::config{});
recursive_sub_parser->parse();
}
else if (auto recursive_sub_parser = remote_parser->add_subcommand("show"))
{
recursive_sub_parser->parse();
}
else if (auto recursive_sub_parser = remote_parser->add_subcommand("remote"))
{
recursive_sub_parser->parse();
}
remote_parser->parse();
}
git_parser.parse();
return 0; ¹ I actually think that there is merit to having parent-parse happen on successful add_subcommand, because that way top-level options are already evaluated and can be used to influence setup of the sub-command. P.S.: I strongly suggest not adding the old subcommands-as-constructor-args syntax to the new constructor-config, as it is confusing to have two mechanisms that are so different. |
One more thing: if subcommands get Maybe this is too much, though 🤔 |
I am just brainstorming here, but I think subcommands are more similar to positional options — or at least most people perceive them as such. If you want that reflected in the design, you could also do something like this: sharg::parser git_parser{"git", argc, argv, sharg::update_notifications::on};
std::string dir;
std::string subcommand;
git_parser.add_option(dir, sharg::config{.short_id = 'C'});
// add_subcommand always comes last and always invokes parse() on the current parser - independent of return value
auto subparser = git_parser.add_subcommand(subcommand,
sharg::config{.required = true, // this is easy to do now!
.validator = sharg::value_list_validator{"push", "pull", "remote"}});
if (!subparser) // this won't be reached, because set to required; but otherwise it would be OK to return, because parse() happend already
return;
if (subcommand == "pull")
{
std::string repository{};
subparser->add_positional_option(repository, sharg::config{.description = "pull"});
}
else if (subcommand == "push")
{
std::string repository{};
subparser->add_positional_option(repository, sharg::config{.description = "push"});
}
else if (subcommand == "remote"))
{
remote_parser->add_section("Remote options");
std::string subsubcommand;
auto subsubparser = subparser->add_subcommand(subsubcommand);
if (subsubparser)
{
if (subsubcommand == "set-url")
{
std::string repository{};
recursive_sub_parser->add_positional_option(repository, sharg::config{});
recursive_sub_parser->parse();
}
else if (subsubcommand == "show")
{
//...
}
else if (subsubcommand == "remote")
{
//...
}
}
// there is no else-case even if the subcommand is not set to required
// because `add_subcommand()` already invoked parse() and we are done
} Advantages of this design IMHO:
Disadvantages of this design:
|
Well, it's currently a bit convoluted. I think, the
In the example:
(Calling
It doesn't, but is does call
If we were to add multiple subcommand subsequently, calling parser.add_subcommand("pull"); // parser.parse() would throw for `./app push`
parser.add_subcommand("push");
No, we just have to do it before calling
Yes, any changes after
Yeah, with always calling
It shouldn't be a problem to allow Actually, calling it twice shouldn't be a problem if we just make the second call a NOP. Depends on the final design whether we need it. Maybe it's even required and solves some weird behaviour I saw with
From the documentation, only flags are allowed for the top-level, not sure if that is what actually happens. But in case we need to access top-level-options in the subparser, we would need to call
My plan was to mark it as deprecated and remove it after the next release. Edit So, currently we process the arguments and store/copy what we may need when If we instead copy the arguments, like this PR does, we could do all the evaluation when parse is called. |
This would also work.
I don't think Whether I do parser.add_option(...);
parser.add_subcommand(...);
parser.parse(); or parser.add_subcommand(...);
parser.add_option(...);
parser.parse(); shouldn't matter. What would Unless we handle it like positional arguments, which are always required. But his also depends on whether we call parse on the parent, etc. |
Yes, IMHO it would be a lot better to have all processing happen on I think this is the most important design decision and will influence how other things can be solved. |
We didn't have time for a In-depth meeting today. But we think that doing everything in parse would be nice. So I'll try that (as separate PR) first. The functions that delegate to the format will be trouble :D |
Yeah, some fun involved, because you cannot keep the other parameters generic. I would start with something like this: template <typename option_type, typename validator_type>
void add_option(option_type & value, config<validator_type> const & config)
{
verify_option_config(config);
auto fn = [&format, &value, &config]()
{
auto inner_visitor = [&value, &config](auto & f)
{
f.add_option(value, config);
};
std::visit(inner_visitor, format);
};
operations.push_back(fn);
} and add |
Same as #233 but with alternative syntax (see snippet).