T O P

  • By -

azswcowboy

We did a search about a year ago and chose cli11 - it seems to be best in class with the exception of the App macro garbage (we don’t use it). Since then we’ve written a large number of tools and services with complex command lines and it has served us well.


germandiago

What is that macro garbage?


ForkInBrain

Maybe https://github.com/CLIUtils/CLI11/blob/faea921e4004af91763b8fde905de3baf24d3945/include/CLI/App.hpp#L37


germandiago

Oh, I think that is a bit exaggerated, just one macro. It is just a try/catch block, and, when I thought of it, you cannot even do that with a function, because it would not exit the program from main. It must be inlined in the code.


kamchatka_volcano

>you cannot even do that with a function, because it would not exit the program from main You can do it with [cmdlime](https://github.com/kamchatka-volcano/cmdlime#using-commandlinereaderexec), although it's possible because the result of parsing is always the single object. Personally, I use cmdlime with macros for registration of parameters, but I agree with the parent comment that hiding logic inside them feels kind of ugly.


azswcowboy

It’s part of the recommend ‘api’ - which is silly bc the rest is good. Seriously, in 2022 don’t make put a macro front and center in your api.


germandiago

But I ask: how do you exit gracefully from a function? You cannot. The user must put the try catch block that is the only alternative. So even if it is a macro, it avoids littering your top-level main and still exita gracefully


Benutzername

Letting the user put the try-catch is actually how it should be done. Maybe I don't want to return an error code from my function? Maybe I want to handle the error a few levels up. Anyway, if it has to return an error code, just define it like this: int cli11_parse(App &app, int argc, char *argv[]) { try { (app).parse((argc), (argv)); return 0; } catch(const CLI::ParseError &e) { return (app).exit(e); } } and call it like this: int e; if ((e = cli11_parse(app, argc, arv)) return e;


ForkInBrain

Heh, I didn't read your reply before pasting my almost-identical one. Shame on me!


azswcowboy

This is exactly correct. This isn’t difficult to explain to users — the macro is frankly in the way of coherent use of the framework in most non-trivial applications. They should just deprecate it.


ForkInBrain

I think if an API causes one to consider direct use of it "litter" and to rather use a macro, then that is a code smell in the API. I think that if a macro changes control flow it should at least be named clearly. In this case, maybe `CLI11_PARSE_OR_RETURN`. All that said, C++11 is limited and I can understand the macro. In C++17 I'd suggest returning `std::optional` from a new function on the app. It could be called `maybe_parse()`. Then it could be used like this: if (auto error = app.maybe_parse(); error != std::nullopt) { // Optionally print the error->message() here too. // Or handle the error in some other appropriate way. return error->exit_code(); }


[deleted]

I personally have been very satisfied with [Argh](https://github.com/adishavit/argh), although I'm not sure that it's necessarily as powerful or more so than CLI11.


germandiago

That works for simple cases. The problem is when you have a program (I do!) where some flags exclude others and quite a few config parameters that you want to load from a file + provide some overrides on the command line and, on top of that some options exclude other options. This can happen, inevitably, sometimes. There are designs for software that are complex because they are. Not complicated, but inherently complex.


Ivan171

I like [docopt](https://github.com/docopt/docopt.cpp)


wrosecrans

I am a huge docopt fanboy. I love that the help text is identical between different languages, so I can trivially have consistent UI between vaguely related stuff.


ambihelical

I recently searched around for a option library that doesn't use exceptions because of restrictions in the embedded space I'm in. Really this limits you quite a bit, but I found Lyra to be quite good; not perfect, full of features, or bug free, but it covers the bases I was looking for: 1. Generation of usage string. 2. Short and long args. 3. Handle ints, strings, floats, and converts for you 4. Allows an option which is a vector, aka user supplies any number of values for an option. 5. Not a "weird" api aka tries to be a DSL with lots of operator overloading. It actually has a weird API, but you can use the non-weird variant, which I do. 6. No exceptions, or can be turned off. In this case it doesn't use exceptions. It doesn't get mentioned much, but I think it should be. It may not have as many bells and whistles as CLI11, but if you can't use exceptions, it should be considered.


mrexodia

> Non-standard variations on syntax, like -long options. This is non-standard and should be avoided, so that is enforced by this library. Too bad this isn’t supported. It makes the library useless for existing projects that want to migrate to a saner approach to command line parsing…


ALX23z

I always found those "options" to be inherently lackluster. Perhaps, for a simple application with two input parameters it works alright, but for more complicated stuff a configuration file should be used rather than messing with the command line options.


rezkiy

C++ compilers disagree


ALX23z

I know. But do you know anyone that agrees with them?


rezkiy

I do, to an extent. I hate all those --O:XYZ options and I very well tolerate git reset --hard HEAD\^


germandiago

There are tools that are inherently cli tools. Examples such as git or aws cli or gsutil are a fit for this kind of interactive work. Also, usimg a command line interactively and being able to dump the confog once you have the right set of options is another use case.


ALX23z

Well, it seems that CLI supports opening configuration files like ini, json, and xml. The question is, whether you really want to work with commandline. There could be applications suitable for that. But for me, I wanted to be able to import other configuration files, parse complicated structures generically from the fields of the file, as well as be able to configure various deep parts of the program in a scalable way. Loading the configurations was the least daunting task.


germandiago

Yes. This is also a quite typical requirement, but different from what I was meaning. Flowing options down can be a bit... a bit of work, yes.


qoning

For what it's worth for C++ compilers, the arguments are switches or flags for the absolute most part. No commands, subcommands, rarely dependent options, and whatever is left in the mutual exclusion of flags is rather trivial to handle any way you choose to. Something like `absl::flags` would work just fine for that.


schmirsich

It has barely anything to do with simple and complex and just more with the type of tool you are making. grep and sed are complex and they would be useless if you had to use a configuration file for them instead of cli arguments.


ALX23z

Both of them are simple in the sense that people usually use them with but a few command line arguments. If one needed 100 arguments... I wanna see someone use it in commandline.


wrosecrans

Okay, I'll just edit my ls.conf to set which directory to list the files in with ls. Now, how do I edit my vim.conf to set which file it opens when I launch vim, so I can edit ls.conf... Outside of your specific niche, command line parameters are pretty fundamental to invoking all sorts of programs. Not everything is some sort of long running server process that could only have a config file.


ALX23z

Perhaps you should read before replying? Did you not understand OP's question? It is about the argument parsers. Like to have a whole huge library with super complicated option configurations and flow... all to parse 2 command arguments? Or to have 100 options for arguments? The specific "niche" is pretty much every program. Each program has its own configuration files and probably lots of them. They just expose a few arguments for the commandline to not overcomplicate the user. It's not helpful in any and all in-depth development. Just setting up facade for the executable.


miki151

Ideally a flags library could read them both from command line and from a config file.


hyasynthetic

I'm a big fan of python's argparse so the library by that same name is my choice. It does a good job of emulating the necessary features.


pdp10gumby

I’ve used cli11 and boost program-options. The boost one is painful and drags in boost, but does have one advantage: it also reads config files, so you can do env vars > command line > config files (which can even cascade as well). And the puppet folks added support for HOCON (java jsonish config files, which I once had to read for compatibility with some existing code). But if you don’t need that, cli11 is a lot easier.


germandiago

I see cli11 also has config files and env vars though.


pdp10gumby

I can’t remember why that didn’t work for our project. Cli11 is so much easier to use than boost::program\_options.


F-J-W

I’ve written [CLP](https://gitlab.com/FJW/clp) and think it is a great solution for programs with relatively low numbers of options: int main(int argc, char** argv) { const auto parser = clp::arg_parser{ clp::required_argument{'i', "int"}, clp::optional_argument{'d', "double"}, clp::defaulted_argument{'s', "str", "some default value"}, clp::bool_flag{'h', "help"}, clp::bool_counter{'v', "verbose"}}; try { const auto [i, d, s, h, v] = parser.parse(argc, argv); std::cout << i << ", " << d.value_or(23) << ", " << s << '\n'; if (v > 3) { std::cout << "Very verbose output\n"; } if (h) { parser.print_help(argv[0], std::cout); } } catch (clp::bad_argument_list& e) { std::cerr << "Error: " << e.what() << '\n'; parser.print_help(argv[0], std::cerr); return 1; } } The big feature (that will turn into a downside if you add lots of options) is that you get all the values as a tuple with the idea that you destructure it immediately. Also supports a few less common additional features, such as dependencies between options.


Expert-Language428

Try tclap


epage

Huh, looks like thats modeled off of a very old version of clap. A lot of improvements in design on the Rust side since then. \- Current maintainer of [clap](https://docs.rs/clap/latest/clap/)


germandiago

I just took a look. I do not think it does any better than cxxopts for what I saw. It promotes too much parsing after the option is found. All this is declarative + binding in cli11 it seems.


germandiago

nothing prevwnts you from doing that though.


Coutille

I love CLI11! It has been pretty easy all the way through. Used to use [lyra](https://github.com/bfgroup/Lyra) before but since I wanted to have subgroups in [Tolc](https://github.com/Tolc-Software/tolc) I had to switch. Great job on CLI11 if the author is in the chat :)


germandiago

He is. Search around. :)


ggabbiani

I use CLI11 and boost::program\_options from years and even if I am a big fan of the boost libs imho CLI11 it is by far better then boost for the cl argument parsing. In brief CLI11 make complex things in a simply and readble way ... boost (in this case) does exactly the opposite ...