summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2025-02-23 21:20:47 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2025-02-23 21:35:09 +0100
commit7107ac464f3bf1b89d6b759c20a09ecc323f8cb0 (patch)
treedfb824c6e1ca66a32a16f2a6358695b58ca656a8
parent5809172695f0b174ae036dc829e6d3176d926342 (diff)
-rw-r--r--src/argparser.h153
-rw-r--r--test/argparser_test.cc49
-rw-r--r--test/ctor.cc18
3 files changed, 220 insertions, 0 deletions
diff --git a/src/argparser.h b/src/argparser.h
new file mode 100644
index 0000000..51d6745
--- /dev/null
+++ b/src/argparser.h
@@ -0,0 +1,153 @@
+// -*- c++ -*-
+// Distributed under the BSD 2-Clause License.
+// See accompanying file LICENSE for details.
+#pragma once
+
+#include <functional>
+#include <variant>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <iostream>
+
+template<typename... Ts>
+class ArgParser
+{
+public:
+ struct missing_arg{};
+
+ int parse(int argc, const char* const argv[])
+ {
+ if(argc < 1)
+ {
+ return 1;
+ }
+ for(int i = 1; i < argc; ++i) // skip argv[0] which is program name
+ {
+ bool was_handled{false};
+ for(const auto& option : options)
+ {
+ enum class state { handled, unhandled };
+ std::pair<int, state> ret =
+ std::visit([&](auto&& opt) -> std::pair<int, state>
+ {
+ if(opt.shortopt != argv[i] &&
+ opt.longopt != argv[i])
+ {
+ return {0, state::unhandled};
+ }
+ try
+ {
+ 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";
+ return {1, state::handled};
+ }
+ catch(missing_arg&)
+ {
+ std::cout << "Missing " << opt.shortopt <<
+ " argument.\n";
+ std::cout << opt.help << "\n";
+ return {1, state::handled};
+ }
+ },
+ option);
+ if(ret.second == state::handled && ret.first != 0)
+ {
+ return ret.first;
+ }
+ was_handled |= ret.second == state::handled;
+ if(was_handled)
+ {
+ break;
+ }
+ }
+ if(!was_handled)
+ {
+ std::cout << "Unknown argument " << argv[i] << '\n';
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ template<typename T>
+ void add(const std::string& shortopt,
+ const std::string& longopt,
+ std::function<int(T)> cb,
+ const std::string& help)
+ {
+ options.emplace_back(Opt<T>{shortopt, longopt, cb, help});
+ }
+
+private:
+ template<typename T>
+ struct Opt
+ {
+ //using contained_type = T;
+ std::string shortopt;
+ std::string longopt;
+ std::function<int(T)> cb;
+ std::string help;
+ T t{};
+ };
+
+ template<typename T>
+ T convert(int argc, const char* const argv[], int& i, T)
+ {
+ auto opt = convert(argc, argv, i, std::optional<T>{});
+ if(!opt)
+ {
+ throw missing_arg{};
+ }
+ return *opt;
+ }
+
+ template<typename T>
+ std::optional<T> convert(int argc, const char* const argv[], int& i,
+ std::optional<T>)
+ {
+ std::string arg;
+ bool has_arg{false};
+ if(i+1 < argc)
+ {
+ arg = argv[i+1];
+ has_arg = !arg.starts_with("-");
+ if(has_arg)
+ {
+ ++i;
+ }
+ }
+
+ if(!has_arg)
+ {
+ return {};
+ }
+
+ if constexpr (std::is_same_v<T, int>)
+ {
+ return std::stoi(arg);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::stod(arg);
+ }
+ else if constexpr (std::is_same_v<T, std::string>)
+ {
+ return arg;
+ }
+ else
+ {
+ static_assert(std::is_same_v<T, void>, "missing");
+ }
+ return {};
+ }
+
+ using Opts = std::variant<Opt<Ts>...>;
+ std::vector<Opts> options;
+};
diff --git a/test/argparser_test.cc b/test/argparser_test.cc
new file mode 100644
index 0000000..842b214
--- /dev/null
+++ b/test/argparser_test.cc
@@ -0,0 +1,49 @@
+#include <uunit.h>
+
+#include <argparser.h>
+
+#include <iostream>
+#include <string>
+
+class ArgParserTest
+ : public uUnit
+{
+public:
+ ArgParserTest()
+ {
+ uTEST(ArgParserTest::test);
+ }
+
+ void test()
+ {
+ const char* argv[] =
+ {
+ "app-name",
+ "-x",
+ "42"
+ };
+ int argc = sizeof(argv)/sizeof(*argv);
+ ArgParser<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");
+
+ args.add("-X", "--opt-x",
+ std::function([](std::optional<int> i)
+ {
+ std::cout << "opt<int>: " << (i?std::to_string(*i):"none") << "\n";
+ return 0;
+ }),
+ "Helptext for -X,--opt-x");
+
+ args.parse(argc, argv);
+ }
+};
+
+// Registers the fixture into the 'registry'
+static ArgParserTest test;
diff --git a/test/ctor.cc b/test/ctor.cc
index fa53802..b8c5c01 100644
--- a/test/ctor.cc
+++ b/test/ctor.cc
@@ -12,6 +12,24 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)
{
.type = ctor::target_type::unit_test,
.system = ctor::output_system::build,
+ .target = "argparser_test",
+ .sources = {
+ "argparser_test.cc",
+ "testmain.cc",
+ },
+ .flags = {
+ .cxxflags = {
+ "-std=c++20", "-O3", "-Wall", "-Werror",
+ "-I../src", "-Iuunit",
+ "-DOUTPUT=\"argparser\"",
+ {ctor::toolchain::msvc, ctor::cxx_opt::custom, "/EHsc"},
+ {ctor::toolchain::msvc, ctor::cxx_opt::custom, "/D_CRT_SECURE_NO_WARNINGS"},
+ },
+ },
+ },
+ {
+ .type = ctor::target_type::unit_test,
+ .system = ctor::output_system::build,
.target = "argsplit_test",
.sources = {
"argsplit_test.cc",