From 1acf1d62cea4d71ed68e621893ec101264fc42e9 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Thu, 30 Jun 2016 00:12:02 -0600 Subject: make the last of the v5 changes --- README.md | 293 ++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 153 insertions(+), 140 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 662d8ee..0ac02e4 100644 --- a/README.md +++ b/README.md @@ -52,16 +52,16 @@ It: * Lets you decide not to allow separate-argument argument flags or joined ones (like disallowing `--foo bar`, requiring `--foo=bar`, or the inverse, or the same for short options). +* Allows you to create subparsers somewhat like argparse, through the use of + kick-out flags (check the gitlike.cxx example program for a simple sample of + this) # What does it not do? There are tons of things this library does not do! - ## It will not ever: -* Allow you to create subparsers like argparse in a master parser (you can do - this yourself with iterators and multiple parsers) * Allow one argument flag to take a specific number of arguments (like `--foo first second`). You can instead split that with a flag list (`--foo first --foo second`) or a custom type extraction (`--foo first,second`) @@ -121,144 +121,18 @@ interpreted as a boolean to see if they've been matched. All variables can be pulled (including the boolean match status) with args::get. -# How fast is it? - -This should not really be a question you ask when you are looking for an -argument-parsing library, but every test I've done shows args as being about -65% faster than TCLAP and 220% faster than boost::program_options. - -The simplest benchmark I threw together is the following one, which parses the -command line `-i 7 -c a 2.7 --char b 8.4 -c c 8.8 --char d` with a parser that -parses -i as an int, -c as a list of chars, and the positional parameters as a -list of doubles (the command line was originally much more complex, but TCLAP's -limitations made me trim it down so I could use a common command line across -all libraries. I also have to copy in the arguments list with every run, -because TCLAP permutes its argument list as it runs (and comparison would have -been unfair without comparing all about equally), but that surprisingly didn't -affect much. Also tested is pulling the arguments out, but that was fast -compared to parsing, as would be expected. +# Group validation is weird. How do I get more helpful output for failed validation? -### The run: - -```shell -% g++ -obench bench.cxx -O2 -std=c++11 -lboost_program_options -% ./bench -args seconds to run: 0.895472 -tclap seconds to run: 1.45001 -boost::program_options seconds to run: 1.98972 -% -``` - -### The benchmark: - -```cpp -#undef NDEBUG -#include -#include -#include -#include "args.hxx" -#include -#include -namespace po = boost::program_options; -using namespace std::chrono; -inline bool doubleequals(const double a, const double b) -{ - static const double delta = 0.0001; - const double diff = a - b; - return diff < delta && diff > -delta; -} -int main() -{ - const std::vector carguments({"-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"}); - const std::vector pcarguments({"progname", "-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"}); - // args - { - high_resolution_clock::time_point start = high_resolution_clock::now(); - for (unsigned int x = 0; x < 100000; ++x) - { - std::vector arguments(carguments); - args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ValueFlag integer(parser, "integer", "The integer flag", {'i', "int"}); - args::ValueFlagList characters(parser, "characters", "The character flag", {'c', "char"}); - args::PositionalList numbers(parser, "numbers", "The numbers position list"); - parser.ParseArgs(arguments); - const int i = args::get(integer); - const std::vector c(args::get(characters)); - const std::vector n(args::get(numbers)); - assert(i == 7); - assert(c[0] == 'a'); - assert(c[1] == 'b'); - assert(c[2] == 'c'); - assert(c[3] == 'd'); - assert(doubleequals(n[0], 2.7)); - assert(doubleequals(n[1], 8.4)); - assert(doubleequals(n[2], 8.8)); - } - high_resolution_clock::duration runtime = high_resolution_clock::now() - start; - std::cout << "args seconds to run: " << duration_cast>(runtime).count() << std::endl; - } - // tclap - { - high_resolution_clock::time_point start = high_resolution_clock::now(); - for (unsigned int x = 0; x < 100000; ++x) - { - std::vector arguments(pcarguments); - TCLAP::CmdLine cmd("Command description message", ' ', "0.9"); - TCLAP::ValueArg integer("i", "int", "The integer flag", false, 0, "integer", cmd); - TCLAP::MultiArg characters("c", "char", "The character flag", false, "characters", cmd); - TCLAP::UnlabeledMultiArg numbers("numbers", "The numbers position list", false, "foo", cmd, false); - cmd.parse(arguments); - const int i = integer.getValue(); - const std::vector c(characters.getValue()); - const std::vector n(numbers.getValue()); - assert(i == 7); - assert(c[0] == 'a'); - assert(c[1] == 'b'); - assert(c[2] == 'c'); - assert(c[3] == 'd'); - assert(doubleequals(n[0], 2.7)); - assert(doubleequals(n[1], 8.4)); - assert(doubleequals(n[2], 8.8)); - } - high_resolution_clock::duration runtime = high_resolution_clock::now() - start; - std::cout << "tclap seconds to run: " << duration_cast>(runtime).count() << std::endl; - } - // boost::program_options - { - high_resolution_clock::time_point start = high_resolution_clock::now(); - for (unsigned int x = 0; x < 100000; ++x) - { - std::vector arguments(carguments); - po::options_description desc("This is a test program."); - desc.add_options() - ("int,i", po::value(), "The integer flag") - ("char,c", po::value>(), "The character flag") - ("numbers", po::value>(), "The numbers flag"); - po::positional_options_description p; - p.add("numbers", -1); - po::variables_map vm; - po::store(po::command_line_parser(carguments).options(desc).positional(p).run(), vm); - const int i = vm["int"].as(); - const std::vector c(vm["char"].as>()); - const std::vector n(vm["numbers"].as>()); - assert(i == 7); - assert(c[0] == 'a'); - assert(c[1] == 'b'); - assert(c[2] == 'c'); - assert(c[3] == 'd'); - assert(doubleequals(n[0], 2.7)); - assert(doubleequals(n[1], 8.4)); - assert(doubleequals(n[2], 8.8)); - } - high_resolution_clock::duration runtime = high_resolution_clock::now() - start; - std::cout << "boost::program_options seconds to run: " << duration_cast>(runtime).count() << std::endl; - } - return 0; -} -``` - -So, on top of being more flexible, smaller, and easier to read, it is faster -than the most common alternatives. +This is unfortunately not possible, given the power of the groups available. +For instance, if you have a group validation that works like +`(A && B) || (C && (D XOR E))`, how is this library going to be able to +determine what exactly when wrong when it fails? It only knows that the +entire expression evaluated false, not specifically what the user did wrong +(and this is doubled over by the fact that validation operations are ordinary +functions without any special meaning to the library). As you are the only one +who understands the logic of your program, if you want useful group messages, +you have to catch the ValidationError as a special case and check your own +groups and spit out messages accordingly. # Is it developed with regression tests? @@ -915,3 +789,142 @@ TEST_CASE("Mapping types work as needed", "[args]") REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--mf=YeLLoW"}), args::MapError); } ``` + +# How fast is it? + +This should not really be a question you ask when you are looking for an +argument-parsing library, but every test I've done shows args as being about +65% faster than TCLAP and 220% faster than boost::program_options. + +The simplest benchmark I threw together is the following one, which parses the +command line `-i 7 -c a 2.7 --char b 8.4 -c c 8.8 --char d` with a parser that +parses -i as an int, -c as a list of chars, and the positional parameters as a +list of doubles (the command line was originally much more complex, but TCLAP's +limitations made me trim it down so I could use a common command line across +all libraries. I also have to copy in the arguments list with every run, +because TCLAP permutes its argument list as it runs (and comparison would have +been unfair without comparing all about equally), but that surprisingly didn't +affect much. Also tested is pulling the arguments out, but that was fast +compared to parsing, as would be expected. + +### The run: + +```shell +% g++ -obench bench.cxx -O2 -std=c++11 -lboost_program_options +% ./bench +args seconds to run: 0.895472 +tclap seconds to run: 1.45001 +boost::program_options seconds to run: 1.98972 +% +``` + +### The benchmark: + +```cpp +#undef NDEBUG +#include +#include +#include +#include "args.hxx" +#include +#include +namespace po = boost::program_options; +using namespace std::chrono; +inline bool doubleequals(const double a, const double b) +{ + static const double delta = 0.0001; + const double diff = a - b; + return diff < delta && diff > -delta; +} +int main() +{ + const std::vector carguments({"-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"}); + const std::vector pcarguments({"progname", "-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"}); + // args + { + high_resolution_clock::time_point start = high_resolution_clock::now(); + for (unsigned int x = 0; x < 100000; ++x) + { + std::vector arguments(carguments); + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::ValueFlag integer(parser, "integer", "The integer flag", {'i', "int"}); + args::ValueFlagList characters(parser, "characters", "The character flag", {'c', "char"}); + args::PositionalList numbers(parser, "numbers", "The numbers position list"); + parser.ParseArgs(arguments); + const int i = args::get(integer); + const std::vector c(args::get(characters)); + const std::vector n(args::get(numbers)); + assert(i == 7); + assert(c[0] == 'a'); + assert(c[1] == 'b'); + assert(c[2] == 'c'); + assert(c[3] == 'd'); + assert(doubleequals(n[0], 2.7)); + assert(doubleequals(n[1], 8.4)); + assert(doubleequals(n[2], 8.8)); + } + high_resolution_clock::duration runtime = high_resolution_clock::now() - start; + std::cout << "args seconds to run: " << duration_cast>(runtime).count() << std::endl; + } + // tclap + { + high_resolution_clock::time_point start = high_resolution_clock::now(); + for (unsigned int x = 0; x < 100000; ++x) + { + std::vector arguments(pcarguments); + TCLAP::CmdLine cmd("Command description message", ' ', "0.9"); + TCLAP::ValueArg integer("i", "int", "The integer flag", false, 0, "integer", cmd); + TCLAP::MultiArg characters("c", "char", "The character flag", false, "characters", cmd); + TCLAP::UnlabeledMultiArg numbers("numbers", "The numbers position list", false, "foo", cmd, false); + cmd.parse(arguments); + const int i = integer.getValue(); + const std::vector c(characters.getValue()); + const std::vector n(numbers.getValue()); + assert(i == 7); + assert(c[0] == 'a'); + assert(c[1] == 'b'); + assert(c[2] == 'c'); + assert(c[3] == 'd'); + assert(doubleequals(n[0], 2.7)); + assert(doubleequals(n[1], 8.4)); + assert(doubleequals(n[2], 8.8)); + } + high_resolution_clock::duration runtime = high_resolution_clock::now() - start; + std::cout << "tclap seconds to run: " << duration_cast>(runtime).count() << std::endl; + } + // boost::program_options + { + high_resolution_clock::time_point start = high_resolution_clock::now(); + for (unsigned int x = 0; x < 100000; ++x) + { + std::vector arguments(carguments); + po::options_description desc("This is a test program."); + desc.add_options() + ("int,i", po::value(), "The integer flag") + ("char,c", po::value>(), "The character flag") + ("numbers", po::value>(), "The numbers flag"); + po::positional_options_description p; + p.add("numbers", -1); + po::variables_map vm; + po::store(po::command_line_parser(carguments).options(desc).positional(p).run(), vm); + const int i = vm["int"].as(); + const std::vector c(vm["char"].as>()); + const std::vector n(vm["numbers"].as>()); + assert(i == 7); + assert(c[0] == 'a'); + assert(c[1] == 'b'); + assert(c[2] == 'c'); + assert(c[3] == 'd'); + assert(doubleequals(n[0], 2.7)); + assert(doubleequals(n[1], 8.4)); + assert(doubleequals(n[2], 8.8)); + } + high_resolution_clock::duration runtime = high_resolution_clock::now() - start; + std::cout << "boost::program_options seconds to run: " << duration_cast>(runtime).count() << std::endl; + } + return 0; +} +``` + +So, on top of being more flexible, smaller, and easier to read, it is faster +than the most common alternatives. -- cgit v1.2.1