aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md293
1 files changed, 153 insertions, 140 deletions
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 <iostream>
-#include <chrono>
-#include <cassert>
-#include "args.hxx"
-#include <tclap/CmdLine.h>
-#include <boost/program_options.hpp>
-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<std::string> carguments({"-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"});
- const std::vector<std::string> 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<std::string> arguments(carguments);
- args::ArgumentParser parser("This is a test program.", "This goes after the options.");
- args::ValueFlag<int> integer(parser, "integer", "The integer flag", {'i', "int"});
- args::ValueFlagList<char> characters(parser, "characters", "The character flag", {'c', "char"});
- args::PositionalList<double> numbers(parser, "numbers", "The numbers position list");
- parser.ParseArgs(arguments);
- const int i = args::get(integer);
- const std::vector<char> c(args::get(characters));
- const std::vector<double> 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<duration<double>>(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<std::string> arguments(pcarguments);
- TCLAP::CmdLine cmd("Command description message", ' ', "0.9");
- TCLAP::ValueArg<int> integer("i", "int", "The integer flag", false, 0, "integer", cmd);
- TCLAP::MultiArg<char> characters("c", "char", "The character flag", false, "characters", cmd);
- TCLAP::UnlabeledMultiArg<double> numbers("numbers", "The numbers position list", false, "foo", cmd, false);
- cmd.parse(arguments);
- const int i = integer.getValue();
- const std::vector<char> c(characters.getValue());
- const std::vector<double> 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<duration<double>>(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<std::string> arguments(carguments);
- po::options_description desc("This is a test program.");
- desc.add_options()
- ("int,i", po::value<int>(), "The integer flag")
- ("char,c", po::value<std::vector<char>>(), "The character flag")
- ("numbers", po::value<std::vector<double>>(), "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<int>();
- const std::vector<char> c(vm["char"].as<std::vector<char>>());
- const std::vector<double> n(vm["numbers"].as<std::vector<double>>());
- 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<duration<double>>(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<std::string>{"--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 <iostream>
+#include <chrono>
+#include <cassert>
+#include "args.hxx"
+#include <tclap/CmdLine.h>
+#include <boost/program_options.hpp>
+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<std::string> carguments({"-i", "7", "-c", "a", "2.7", "--char", "b", "8.4", "-c", "c", "8.8", "--char", "d"});
+ const std::vector<std::string> 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<std::string> arguments(carguments);
+ args::ArgumentParser parser("This is a test program.", "This goes after the options.");
+ args::ValueFlag<int> integer(parser, "integer", "The integer flag", {'i', "int"});
+ args::ValueFlagList<char> characters(parser, "characters", "The character flag", {'c', "char"});
+ args::PositionalList<double> numbers(parser, "numbers", "The numbers position list");
+ parser.ParseArgs(arguments);
+ const int i = args::get(integer);
+ const std::vector<char> c(args::get(characters));
+ const std::vector<double> 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<duration<double>>(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<std::string> arguments(pcarguments);
+ TCLAP::CmdLine cmd("Command description message", ' ', "0.9");
+ TCLAP::ValueArg<int> integer("i", "int", "The integer flag", false, 0, "integer", cmd);
+ TCLAP::MultiArg<char> characters("c", "char", "The character flag", false, "characters", cmd);
+ TCLAP::UnlabeledMultiArg<double> numbers("numbers", "The numbers position list", false, "foo", cmd, false);
+ cmd.parse(arguments);
+ const int i = integer.getValue();
+ const std::vector<char> c(characters.getValue());
+ const std::vector<double> 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<duration<double>>(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<std::string> arguments(carguments);
+ po::options_description desc("This is a test program.");
+ desc.add_options()
+ ("int,i", po::value<int>(), "The integer flag")
+ ("char,c", po::value<std::vector<char>>(), "The character flag")
+ ("numbers", po::value<std::vector<double>>(), "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<int>();
+ const std::vector<char> c(vm["char"].as<std::vector<char>>());
+ const std::vector<double> n(vm["numbers"].as<std::vector<double>>());
+ 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<duration<double>>(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.