diff options
author | Taylor C. Richberger <Taywee@gmx.com> | 2016-05-08 21:43:42 -0600 |
---|---|---|
committer | Taylor C. Richberger <Taywee@gmx.com> | 2016-05-08 21:43:42 -0600 |
commit | 980488f117364b76a7a1f14ed93a4aa750c72ae0 (patch) | |
tree | 2901fea02c9634228cae2bde51f899d18ccf9bf9 | |
parent | small improvement (diff) | |
parent | improve documentation (diff) | |
download | args.hxx-980488f117364b76a7a1f14ed93a4aa750c72ae0.tar.xz |
Merge branch 'master' of ssh.gitgud.io:Taywee/args
-rw-r--r-- | Doxyfile | 2 | ||||
-rw-r--r-- | README.md | 33 | ||||
-rw-r--r-- | args.hxx | 69 | ||||
-rw-r--r-- | test.cxx | 61 |
4 files changed, 117 insertions, 48 deletions
@@ -38,7 +38,7 @@ PROJECT_NAME = "args" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 1.2.4 +PROJECT_NUMBER = 2.0.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -73,7 +73,7 @@ There are tons of things this library does not do! assigning them, rather than what we currently do, which is assiging them as we go for better simplicity and speed. -# How do I use it? +# How do I install it? ```shell sudo make install @@ -96,6 +96,19 @@ sudo make installman This requires Doxygen +# How do I use it? + +Create an ArgumentParser, modify its attributes to fit your needs, add +arguments through regular argument objects (or create your own), and match them +with an args::Matcher object (check its construction details in the doxygen +documentation. + +Then you can either call it with args::ArgumentParser::ParseCLI for the full +command line with program name, or args::ArgumentParser::ParseArgs with +just the arguments to be parsed. The argument and group variables can then be +interpreted as a boolean to see if they've been matched, and their arguments +can be pulled from their value and values attributes, if applicable. + # How fast is it? This should not really be a question you ask when you are looking for an @@ -150,8 +163,8 @@ int main() { std::vector<std::string> arguments(carguments); args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ArgFlag<int> integer(parser, "integer", "The integer flag", args::Matcher({'i'}, {"int"})); - args::ArgFlagList<char> characters(parser, "characters", "The character flag", args::Matcher({'c'}, {"char"})); + args::ArgFlag<int> integer(parser, "integer", "The integer flag", args::Matcher{'i', "int"}); + args::ArgFlagList<char> characters(parser, "characters", "The character flag", args::Matcher{'c', "char"}); args::PosArgList<double> numbers(parser, "numbers", "The numbers position list"); parser.ParseArgs(arguments); const int i = integer.value; @@ -263,7 +276,7 @@ All the code examples here will be complete code examples, with some output. int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher{'h', "help"}); try { parser.ParseCLI(argc, argv); @@ -307,7 +320,7 @@ int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program.", "This goes after the options."); args::Group group(parser, "This group is all exclusive:", args::Group::Validators::Xor); - args::Flag foo(group, "foo", "The foo flag", args::Matcher({'f'}, {"foo"})); + args::Flag foo(group, "foo", "The foo flag", args::Matcher{'f', "foo"}); args::Flag bar(group, "bar", "The bar flag", args::Matcher({'b'})); args::Flag baz(group, "baz", "The baz flag", args::Matcher({"baz"})); try @@ -384,7 +397,7 @@ Group validation failed somewhere! int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher{'h', "help"}); args::ArgFlag<int> integer(parser, "integer", "The integer flag", args::Matcher({'i'})); args::ArgFlagList<char> characters(parser, "characters", "The character flag", args::Matcher({'c'})); args::PosArg<std::string> foo(parser, "foo", "The foo position"); @@ -564,9 +577,9 @@ there are unextracted characters left in the stream. int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program with a really long description that is probably going to have to be wrapped across multiple different lines. This is a test to see how the line wrapping works", "This goes after the options. This epilog is also long enough that it will have to be properly wrapped to display correctly on the screen"); - args::HelpFlag help(parser, "HELP", "Show this help menu.", args::Matcher({'h'}, {"help"})); - args::ArgFlag<std::string> foo(parser, "FOO", "The foo flag.", args::Matcher({'a', 'b', 'c'}, {"a", "b", "c", "the-foo-flag"})); - args::ArgFlag<std::string> bar(parser, "BAR", "The bar flag. This one has a lot of options, and will need wrapping in the description, along with its long flag list.", args::Matcher({'d', 'e', 'f'}, {"d", "e", "f"})); + args::HelpFlag help(parser, "HELP", "Show this help menu.", args::Matcher{'h', "help"}); + args::ArgFlag<std::string> foo(parser, "FOO", "The foo flag.", args::Matcher{'a', 'b', 'c', "a", "b", "c", "the-foo-flag"}); + args::ArgFlag<std::string> bar(parser, "BAR", "The bar flag. This one has a lot of options, and will need wrapping in the description, along with its long flag list.", args::Matcher{'d', 'e', 'f', "d", "e", "f"}); args::ArgFlag<std::string> baz(parser, "FOO", "The baz flag. This one has a lot of options, and will need wrapping in the description, even with its short flag list.", args::Matcher({"baz"})); args::PosArg<std::string> pos1(parser, "POS1", "The pos1 argument."); args::PosArgList<std::string> poslist1(parser, "POSLIST1", "The poslist1 argument."); @@ -783,7 +796,7 @@ int main(int argc, char **argv) args::Group atleastone(xorgroup, "this group provides at-least-one validation:", args::Group::Validators::AtLeastOne); args::Flag g(atleastone, "g", "test flag", args::Matcher({'g'})); args::Flag o(atleastone, "o", "test flag", args::Matcher({'o'})); - args::HelpFlag help(parser, "help", "Show this help menu", args::Matcher({'h'}, {"help"})); + args::HelpFlag help(parser, "help", "Show this help menu", args::Matcher{'h', "help"}); try { parser.ParseCLI(argc, argv); @@ -151,6 +151,50 @@ namespace args } }; + /** A simple unified option type for unified initializer lists + */ + struct EitherOpt + { + const bool isShort; + const char shortOpt; + const std::string longOpt; + EitherOpt(const std::string &opt) : isShort(false), shortOpt(), longOpt(opt) {} + EitherOpt(const char *opt) : isShort(false), shortOpt(), longOpt(opt) {} + EitherOpt(const char opt) : isShort(true), shortOpt(opt), longOpt() {} + + /** Get just the long options from an initializer list of EitherOpts + */ + static std::unordered_set<std::string> GetLong(std::initializer_list<EitherOpt> opts) + { + std::unordered_set<std::string> longOpts; + for (const EitherOpt &opt: opts) + { + if (!opt.isShort) + { + longOpts.insert(opt.longOpt); + } + } + return longOpts; + } + + /** Get just the short options from an initializer list of EitherOpts + */ + static std::unordered_set<char> GetShort(std::initializer_list<EitherOpt> opts) + { + std::unordered_set<char> shortOpts; + for (const EitherOpt &opt: opts) + { + if (opt.isShort) + { + shortOpts.insert(opt.shortOpt); + } + } + return shortOpts; + } + }; + + + /** A class of "matchers", specifying short and long options that can possibly be matched * * This is supposed to be constructed and then passed in, not used directly from user code. @@ -163,6 +207,8 @@ namespace args public: /** Specify short and long opts separately as iterators + * + * ex: `args::Matcher(shortOpts.begin(), shortOpts.end(), longOpts.begin(), longOpts.end())` */ template <typename ShortIt, typename LongIt> Matcher(ShortIt shortOptsStart, ShortIt shortOptsEnd, LongIt longOptsStart, LongIt longOptsEnd) : @@ -171,29 +217,20 @@ namespace args {} /** Specify short and long opts separately as iterables + * + * ex: `args::Matcher(shortOpts, longOpts)` */ template <typename Short, typename Long> Matcher(Short &&shortIn, Long &&longIn) : shortOpts(std::begin(shortIn), std::end(shortIn)), longOpts(std::begin(longIn), std::end(longIn)) {} - /** Specify short and long opts as initializer lists - */ - Matcher(const std::initializer_list<char> &shortIn, const std::initializer_list<std::string> &longIn) : - shortOpts(std::begin(shortIn), std::end(shortIn)), longOpts(std::begin(longIn), std::end(longIn)) - {} - - /** Specify short opts only as initializer lists - */ - Matcher(const std::initializer_list<char> &shortIn) : - shortOpts(std::begin(shortIn), std::end(shortIn)) - {} - - /** Specify long opts only as initializer lists + /** Specify a mixed single initializer-list of both short and long opts + * + * This is the fancy one: args::Matcher{'a'}, or args::Matcher{"foo"}, or args::Matcher{"foo", 'f', 'F', "FoO"} */ - Matcher(const std::initializer_list<std::string> &longIn) : - longOpts(std::begin(longIn), std::end(longIn)) - {} + Matcher(std::initializer_list<EitherOpt> in) : + shortOpts(EitherOpt::GetShort(in)), longOpts(EitherOpt::GetLong(in)) {} Matcher(Matcher &&other) : shortOpts(std::move(other.shortOpts)), longOpts(std::move(other.longOpts)) {} @@ -4,6 +4,14 @@ #include <iostream> +std::istream& operator>>(std::istream& is, std::tuple<int, int>& ints) +{ + is >> std::get<0>(ints); + is.get(); + is >> std::get<1>(ints); + return is; +} + #include <args.hxx> #define CATCH_CONFIG_MAIN @@ -12,7 +20,7 @@ TEST_CASE("Help flag throws Help exception", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher{'h', "help"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--help"}), args::Help); } @@ -20,7 +28,7 @@ TEST_CASE("Help flag throws Help exception", "[args]") TEST_CASE("Unknown flags throw exceptions", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher({'h'}, {"help"})); + args::HelpFlag help(parser, "help", "Display this help menu", args::Matcher{'h', "help"}); REQUIRE_NOTHROW(parser.ParseArgs(std::vector<std::string>{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--Help"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"-H"}), args::ParseError); @@ -29,10 +37,10 @@ TEST_CASE("Unknown flags throw exceptions", "[args]") TEST_CASE("Boolean flags work as expected, with clustering", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::Flag foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); - args::Flag bar(parser, "BAR", "test flag", args::Matcher({'b'}, {"bar"})); - args::Flag baz(parser, "BAZ", "test flag", args::Matcher({'a'}, {"baz"})); - args::Flag bix(parser, "BAZ", "test flag", args::Matcher({'x'}, {"bix"})); + args::Flag foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", args::Matcher{'b', "bar"}); + args::Flag baz(parser, "BAZ", "test flag", args::Matcher{'a', "baz"}); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher{'x', "bix"}); parser.ParseArgs(std::vector<std::string>{"--baz", "-fb"}); REQUIRE(foo); REQUIRE(bar); @@ -43,11 +51,30 @@ TEST_CASE("Boolean flags work as expected, with clustering", "[args]") TEST_CASE("Argument flags work as expected, with clustering", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ArgFlag<std::string> foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); - args::Flag bar(parser, "BAR", "test flag", args::Matcher({'b'}, {"bar"})); - args::ArgFlag<double> baz(parser, "BAZ", "test flag", args::Matcher({'a'}, {"baz"})); - args::ArgFlag<char> bim(parser, "BAZ", "test flag", args::Matcher({'B'}, {"bim"})); - args::Flag bix(parser, "BAZ", "test flag", args::Matcher({'x'}, {"bix"})); + args::ArgFlag<std::string> foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", args::Matcher{'b', "bar"}); + args::ArgFlag<double> baz(parser, "BAZ", "test flag", args::Matcher{'a', "baz"}); + args::ArgFlag<char> bim(parser, "BAZ", "test flag", args::Matcher{'B', "bim"}); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher{'x', "bix"}); + parser.ParseArgs(std::vector<std::string>{"-bftest", "--baz=7.555e2", "--bim", "c"}); + REQUIRE(foo); + REQUIRE(foo.value == "test"); + REQUIRE(bar); + REQUIRE(baz); + REQUIRE((baz.value > 755.49 && baz.value < 755.51)); + REQUIRE(bim); + REQUIRE(bim.value == 'c'); + REQUIRE_FALSE(bix); +} + +TEST_CASE("Unified argument lists for match work", "[args]") +{ + args::ArgumentParser parser("This is a test program.", "This goes after the options."); + args::ArgFlag<std::string> foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", args::Matcher{"bar", 'b'}); + args::ArgFlag<double> baz(parser, "BAZ", "test flag", args::Matcher{'a', "baz"}); + args::ArgFlag<char> bim(parser, "BAZ", "test flag", args::Matcher{'B', "bim"}); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher{"bix"}); parser.ParseArgs(std::vector<std::string>{"-bftest", "--baz=7.555e2", "--bim", "c"}); REQUIRE(foo); REQUIRE(foo.value == "test"); @@ -62,7 +89,7 @@ TEST_CASE("Argument flags work as expected, with clustering", "[args]") TEST_CASE("Invalid argument parsing throws parsing exceptions", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ArgFlag<int> foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::ArgFlag<int> foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--foo=7.5"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--foo", "7a"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--foo", "7e4"}), args::ParseError); @@ -71,7 +98,7 @@ TEST_CASE("Invalid argument parsing throws parsing exceptions", "[args]") TEST_CASE("Argument flag lists work as expected", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ArgFlagList<int> foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::ArgFlagList<int> foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); parser.ParseArgs(std::vector<std::string>{"--foo=7", "-f2", "-f", "9", "--foo", "42"}); REQUIRE((foo.values == std::vector<int>{7, 2, 9, 42})); } @@ -173,14 +200,6 @@ TEST_CASE("Argument groups should nest", "[args]") #include <tuple> -std::istream& operator>>(std::istream& is, std::tuple<int, int>& ints) -{ - is >> std::get<0>(ints); - is.get(); - is >> std::get<1>(ints); - return is; -} - void DoublesReader(const std::string &name, const std::string &value, std::tuple<double, double> &destination) { size_t commapos = 0; |