summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2025-02-24 20:51:43 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2025-02-24 20:51:43 +0100
commita5951731e49a6884f42c2a07c9e65355831b4394 (patch)
tree47710c23df7a2aa799c4fff469f21e40c97d9e68
parent7107ac464f3bf1b89d6b759c20a09ecc323f8cb0 (diff)
-rw-r--r--src/argparser.h77
-rw-r--r--test/argparser_test.cc112
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 <type_traits>
#include <iostream>
+namespace arg
+{
+struct noarg {};
+template<typename T>
+struct Opt
+{
+ std::string shortopt;
+ std::string longopt;
+ std::function<int(T)> cb;
+ std::string help;
+ T t{};
+};
+
+template<>
+struct Opt<noarg>
+{
+ std::string shortopt;
+ std::string longopt;
+ std::function<int()> cb;
+ std::string help;
+ noarg t{};
+};
+
template<typename... Ts>
-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<decltype(opt)>;
+ if constexpr (std::is_same_v<T, Opt<noarg>>)
+ {
+ 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<T>{shortopt, longopt, cb, help});
}
-private:
- template<typename T>
- struct Opt
+ void add(const std::string& shortopt,
+ const std::string& longopt,
+ std::function<int()> cb,
+ const std::string& help)
{
- //using contained_type = T;
- std::string shortopt;
- std::string longopt;
- std::function<int(T)> cb;
- std::string help;
- T t{};
- };
+ options.emplace_back(Opt<noarg>{shortopt, longopt, cb, help});
+ }
+private:
template<typename T>
T convert(int argc, const char* const argv[], int& i, T)
{
@@ -148,6 +183,8 @@ private:
return {};
}
- using Opts = std::variant<Opt<Ts>...>;
+ using Opts = std::variant<Opt<noarg>, Opt<Ts>...>;
std::vector<Opts> 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<int> 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<int> 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<int> 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<int> 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<int, std::optional<int>> args;
+ arg::Parser<int, std::optional<int>> 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<int> 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);
}
};