From a5951731e49a6884f42c2a07c9e65355831b4394 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Mon, 24 Feb 2025 20:51:43 +0100 Subject: WIP --- src/argparser.h | 77 +++++++++++++++++++++++++--------- test/argparser_test.cc | 112 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 158 insertions(+), 31 deletions(-) diff --git a/src/argparser.h b/src/argparser.h index 51d6745..ec7d5ce 100644 --- a/src/argparser.h +++ b/src/argparser.h @@ -10,8 +10,31 @@ #include #include +namespace arg +{ +struct noarg {}; +template +struct Opt +{ + std::string shortopt; + std::string longopt; + std::function cb; + std::string help; + T t{}; +}; + +template<> +struct Opt +{ + std::string shortopt; + std::string longopt; + std::function cb; + std::string help; + noarg t{}; +}; + template -class ArgParser +class Parser { public: struct missing_arg{}; @@ -22,6 +45,7 @@ public: { return 1; } + for(int i = 1; i < argc; ++i) // skip argv[0] which is program name { bool was_handled{false}; @@ -38,20 +62,33 @@ public: } try { - auto arg = convert(argc, argv, i, opt.t); - return {opt.cb(arg), state::handled}; + using T = std::decay_t; + if constexpr (std::is_same_v>) + { + return {opt.cb(), state::handled}; + } + else + { + auto arg = convert(argc, argv, i, opt.t); + return {opt.cb(arg), state::handled}; + } } catch(std::invalid_argument&) { - std::cout << "Invalid " << opt.shortopt << - " argument.\n"; - std::cout << opt.help << "\n"; + std::cout << argv[0] << + ": invalid argument for option '" << + opt.shortopt << "'\n"; + std::cout << "Type '" << argv[0] << + " -h' for more information.\n"; return {1, state::handled}; } catch(missing_arg&) { - std::cout << "Missing " << opt.shortopt << - " argument.\n"; + std::cout << argv[0] << + ": option requires and argument '" << + opt.shortopt << "'\n"; + std::cout << "Type '" << argv[0] << + " -h' for more information.\n"; std::cout << opt.help << "\n"; return {1, state::handled}; } @@ -69,7 +106,8 @@ public: } if(!was_handled) { - std::cout << "Unknown argument " << argv[i] << '\n'; + std::cout << argv[0] << ": invalid option '" << argv[i] << "'\n"; + std::cout << "Type '" << argv[0] << " -h' for more information.\n"; return 1; } } @@ -85,18 +123,15 @@ public: options.emplace_back(Opt{shortopt, longopt, cb, help}); } -private: - template - struct Opt + void add(const std::string& shortopt, + const std::string& longopt, + std::function cb, + const std::string& help) { - //using contained_type = T; - std::string shortopt; - std::string longopt; - std::function cb; - std::string help; - T t{}; - }; + options.emplace_back(Opt{shortopt, longopt, cb, help}); + } +private: template T convert(int argc, const char* const argv[], int& i, T) { @@ -148,6 +183,8 @@ private: return {}; } - using Opts = std::variant...>; + using Opts = std::variant, Opt...>; std::vector options; }; + +} // arg:: diff --git a/test/argparser_test.cc b/test/argparser_test.cc index 842b214..740add0 100644 --- a/test/argparser_test.cc +++ b/test/argparser_test.cc @@ -11,10 +11,103 @@ class ArgParserTest public: ArgParserTest() { - uTEST(ArgParserTest::test); + uTEST(ArgParserTest::test_zero); + uTEST(ArgParserTest::test_one); + uTEST(ArgParserTest::test_many); + uTEST(ArgParserTest::test_exceptional); + uTEST(ArgParserTest::test2); } - void test() + void test_zero() + { + const char* argv[] = { "app-name" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser args; + + auto res = args.parse(argc, argv); + uASSERT_EQUAL(0, res); + } + + void test_one() + { + const char* argv[] = { "app-name", "-x", "42" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser args; + + int x{}; + args.add("-x", "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + auto res = args.parse(argc, argv); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + } + + void test_many() + { + const char* argv[] = { "app-name", "-x", "42", "-y", "7", "-z" }; + int argc = sizeof(argv)/sizeof(*argv); + + arg::Parser args; + + int x{}; + int y{}; + bool z{false}; + + args.add("-x", "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add("-y", "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + args.add("-z", "--long-z", + std::function([&](){ z = true; return 0;}), "Help z"); + + auto res = args.parse(argc, argv); + uASSERT_EQUAL(0, res); + uASSERT_EQUAL(42, x); + uASSERT_EQUAL(7, y); + uASSERT_EQUAL(true, z); + } + + void test_exceptional() + { + arg::Parser args; + + int x{}; + int y{}; + + args.add("-x", "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help x"); + + args.add("-y", "--long-y", + std::function([&](int i){ y = i; return 0;}), "Help y"); + + { // Missing arg at trailing opt + const char* argv[] = { "app-name", "-x" }; + int argc = sizeof(argv)/sizeof(*argv); + auto res = args.parse(argc, argv); + uASSERT_EQUAL(1, res); + } + + { // Missing arg before other opt + const char* argv[] = { "app-name", "-x", "-y" }; + int argc = sizeof(argv)/sizeof(*argv); + auto res = args.parse(argc, argv); + uASSERT_EQUAL(1, res); + } + + { // Unknown arg + const char* argv[] = { "app-name", "-T" }; + int argc = sizeof(argv)/sizeof(*argv); + auto res = args.parse(argc, argv); + uASSERT_EQUAL(1, res); + } + } + + void test2() { const char* argv[] = { @@ -23,15 +116,11 @@ public: "42" }; int argc = sizeof(argv)/sizeof(*argv); - ArgParser> args; + arg::Parser> args; - args.add("-x", "--some-x", - std::function([](int i) - { - std::cout << "int: " << i << "\n"; - return 0; - }), - "Helptext for -x,--some-x"); + int x{}; + args.add("-x", "--long-x", + std::function([&](int i){ x = i; return 0;}), "Help"); args.add("-X", "--opt-x", std::function([](std::optional i) @@ -41,7 +130,8 @@ public: }), "Helptext for -X,--opt-x"); - args.parse(argc, argv); + auto res = args.parse(argc, argv); + uASSERT_EQUAL(0, res); } }; -- cgit v1.2.3