From 3a9c02aa7404e20a0c1126616ac86a61cc7f2d00 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Sat, 7 May 2016 12:05:44 -0600 Subject: fix ADL. Add unified initializer list --- args.hxx | 50 ++++++++++++++++++++++++++++++++++++++++---------- test.cxx | 35 +++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/args.hxx b/args.hxx index 7be47af..f079f29 100644 --- a/args.hxx +++ b/args.hxx @@ -151,6 +151,42 @@ namespace args } }; + struct EitherOpt + { + bool isShort; + char shortOpt; + std::string longOpt; + EitherOpt(const std::string &opt) : isShort(false), longOpt(opt) {} + EitherOpt(const char *opt) : isShort(false), longOpt(opt) {} + EitherOpt(const char opt) : isShort(true), shortOpt(opt) {} + }; + + std::unordered_set GetLong(std::initializer_list opts) + { + std::unordered_set longOpts; + for (const EitherOpt &opt: opts) + { + if (!opt.isShort) + { + longOpts.insert(opt.longOpt); + } + } + return longOpts; + } + + std::unordered_set GetShort(std::initializer_list opts) + { + std::unordered_set 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. @@ -179,20 +215,14 @@ namespace args /** Specify short and long opts as initializer lists */ - Matcher(const std::initializer_list &shortIn, const std::initializer_list &longIn) : + Matcher(std::initializer_list shortIn, std::initializer_list 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 &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 */ - Matcher(const std::initializer_list &longIn) : - longOpts(std::begin(longIn), std::end(longIn)) + Matcher(std::initializer_list in) : + shortOpts(GetShort(in)), longOpts(GetLong(in)) {} Matcher(Matcher &&other) : shortOpts(std::move(other.shortOpts)), longOpts(std::move(other.longOpts)) diff --git a/test.cxx b/test.cxx index 3ad5533..84cd6ce 100644 --- a/test.cxx +++ b/test.cxx @@ -4,6 +4,14 @@ #include +std::istream& operator>>(std::istream& is, std::tuple& ints) +{ + is >> std::get<0>(ints); + is.get(); + is >> std::get<1>(ints); + return is; +} + #include #define CATCH_CONFIG_MAIN @@ -59,6 +67,25 @@ TEST_CASE("Argument flags work as expected, with clustering", "[args]") 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 foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", args::Matcher{"bar", 'b'}); + args::ArgFlag baz(parser, "BAZ", "test flag", args::Matcher{'a', "baz"}); + args::ArgFlag bim(parser, "BAZ", "test flag", args::Matcher{'B', "bim"}); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher{"bix"}); + parser.ParseArgs(std::vector{"-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("Invalid argument parsing throws parsing exceptions", "[args]") { args::ArgumentParser parser("This is a test program.", "This goes after the options."); @@ -173,14 +200,6 @@ TEST_CASE("Argument groups should nest", "[args]") #include -std::istream& operator>>(std::istream& is, std::tuple& 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 &destination) { size_t commapos = 0; -- cgit v1.2.1 From 5ebc21cc2f6c762073317d5e18e944b778908728 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Sat, 7 May 2016 12:23:58 -0600 Subject: Improve use and documentation --- README.md | 33 ++++++++++++++++++++--------- args.hxx | 71 +++++++++++++++++++++++++++++++++++---------------------------- test.cxx | 26 +++++++++++------------ 3 files changed, 75 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 3a68dc3..6e1c9a1 100644 --- a/README.md +++ b/README.md @@ -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::ParseArguments 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 arguments(carguments); args::ArgumentParser parser("This is a test program.", "This goes after the options."); - args::ArgFlag integer(parser, "integer", "The integer flag", args::Matcher({'i'}, {"int"})); - args::ArgFlagList characters(parser, "characters", "The character flag", args::Matcher({'c'}, {"char"})); + args::ArgFlag integer(parser, "integer", "The integer flag", args::Matcher{'i', "int"}); + args::ArgFlagList characters(parser, "characters", "The character flag", args::Matcher{'c', "char"}); args::PosArgList 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 integer(parser, "integer", "The integer flag", args::Matcher({'i'})); args::ArgFlagList characters(parser, "characters", "The character flag", args::Matcher({'c'})); args::PosArg 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 foo(parser, "FOO", "The foo flag.", args::Matcher({'a', 'b', 'c'}, {"a", "b", "c", "the-foo-flag"})); - args::ArgFlag 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 foo(parser, "FOO", "The foo flag.", args::Matcher{'a', 'b', 'c', "a", "b", "c", "the-foo-flag"}); + args::ArgFlag 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 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 pos1(parser, "POS1", "The pos1 argument."); args::PosArgList 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); diff --git a/args.hxx b/args.hxx index f079f29..e388aa6 100644 --- a/args.hxx +++ b/args.hxx @@ -151,41 +151,49 @@ namespace args } }; + /** A simple unified option type for unified initializer lists + */ struct EitherOpt { - bool isShort; - char shortOpt; - std::string longOpt; - EitherOpt(const std::string &opt) : isShort(false), longOpt(opt) {} - EitherOpt(const char *opt) : isShort(false), longOpt(opt) {} - EitherOpt(const char opt) : isShort(true), shortOpt(opt) {} - }; - - std::unordered_set GetLong(std::initializer_list opts) - { - std::unordered_set longOpts; - for (const EitherOpt &opt: opts) + 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 GetLong(std::initializer_list opts) { - if (!opt.isShort) + std::unordered_set longOpts; + for (const EitherOpt &opt: opts) { - longOpts.insert(opt.longOpt); + if (!opt.isShort) + { + longOpts.insert(opt.longOpt); + } } + return longOpts; } - return longOpts; - } - std::unordered_set GetShort(std::initializer_list opts) - { - std::unordered_set shortOpts; - for (const EitherOpt &opt: opts) + /** Get just the short options from an initializer list of EitherOpts + */ + static std::unordered_set GetShort(std::initializer_list opts) { - if (opt.isShort) + std::unordered_set shortOpts; + for (const EitherOpt &opt: opts) { - shortOpts.insert(opt.shortOpt); + if (opt.isShort) + { + shortOpts.insert(opt.shortOpt); + } } + return shortOpts; } - return shortOpts; - } + }; + + /** A class of "matchers", specifying short and long options that can possibly be matched * @@ -199,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 Matcher(ShortIt shortOptsStart, ShortIt shortOptsEnd, LongIt longOptsStart, LongIt longOptsEnd) : @@ -207,23 +217,20 @@ namespace args {} /** Specify short and long opts separately as iterables + * + * ex: `args::Matcher(shortOpts, longOpts)` */ template 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(std::initializer_list shortIn, std::initializer_list longIn) : - shortOpts(std::begin(shortIn), std::end(shortIn)), longOpts(std::begin(longIn), std::end(longIn)) - {} - /** 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(std::initializer_list in) : - shortOpts(GetShort(in)), longOpts(GetLong(in)) - {} + shortOpts(EitherOpt::GetShort(in)), longOpts(EitherOpt::GetLong(in)) {} Matcher(Matcher &&other) : shortOpts(std::move(other.shortOpts)), longOpts(std::move(other.longOpts)) {} diff --git a/test.cxx b/test.cxx index 84cd6ce..f94f928 100644 --- a/test.cxx +++ b/test.cxx @@ -20,7 +20,7 @@ std::istream& operator>>(std::istream& is, std::tuple& ints) 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{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--help"}), args::Help); } @@ -28,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{})); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--Help"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"-H"}), args::ParseError); @@ -37,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{"--baz", "-fb"}); REQUIRE(foo); REQUIRE(bar); @@ -51,11 +51,11 @@ 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 foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); - args::Flag bar(parser, "BAR", "test flag", args::Matcher({'b'}, {"bar"})); - args::ArgFlag baz(parser, "BAZ", "test flag", args::Matcher({'a'}, {"baz"})); - args::ArgFlag bim(parser, "BAZ", "test flag", args::Matcher({'B'}, {"bim"})); - args::Flag bix(parser, "BAZ", "test flag", args::Matcher({'x'}, {"bix"})); + args::ArgFlag foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); + args::Flag bar(parser, "BAR", "test flag", args::Matcher{'b', "bar"}); + args::ArgFlag baz(parser, "BAZ", "test flag", args::Matcher{'a', "baz"}); + args::ArgFlag bim(parser, "BAZ", "test flag", args::Matcher{'B', "bim"}); + args::Flag bix(parser, "BAZ", "test flag", args::Matcher{'x', "bix"}); parser.ParseArgs(std::vector{"-bftest", "--baz=7.555e2", "--bim", "c"}); REQUIRE(foo); REQUIRE(foo.value == "test"); @@ -89,7 +89,7 @@ TEST_CASE("Unified argument lists for match work", "[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 foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::ArgFlag foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo=7.5"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7a"}), args::ParseError); REQUIRE_THROWS_AS(parser.ParseArgs(std::vector{"--foo", "7e4"}), args::ParseError); @@ -98,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 foo(parser, "FOO", "test flag", args::Matcher({'f'}, {"foo"})); + args::ArgFlagList foo(parser, "FOO", "test flag", args::Matcher{'f', "foo"}); parser.ParseArgs(std::vector{"--foo=7", "-f2", "-f", "9", "--foo", "42"}); REQUIRE((foo.values == std::vector{7, 2, 9, 42})); } -- cgit v1.2.1 From d8a764176f8445e79e8ab42fb95210d751b6f538 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Sat, 7 May 2016 12:24:41 -0600 Subject: bump version number --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index c3dd00e..9fe6504 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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 -- cgit v1.2.1 From 63b56c8afbb96e17b4eef9276a048509415dfe0a Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Sat, 7 May 2016 12:30:55 -0600 Subject: improve documentation --- README.md | 2 +- args.hxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e1c9a1..1f97f93 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ 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::ParseArguments with +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. diff --git a/args.hxx b/args.hxx index e388aa6..3a43f18 100644 --- a/args.hxx +++ b/args.hxx @@ -227,7 +227,7 @@ namespace args /** 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"}` + * This is the fancy one: args::Matcher{'a'}, or args::Matcher{"foo"}, or args::Matcher{"foo", 'f', 'F', "FoO"} */ Matcher(std::initializer_list in) : shortOpts(EitherOpt::GetShort(in)), longOpts(EitherOpt::GetLong(in)) {} -- cgit v1.2.1