diff options
author | Bent Bisballe Nyeng <deva@aasimon.org> | 2025-02-23 21:20:47 +0100 |
---|---|---|
committer | Bent Bisballe Nyeng <deva@aasimon.org> | 2025-02-23 21:35:09 +0100 |
commit | 7107ac464f3bf1b89d6b759c20a09ecc323f8cb0 (patch) | |
tree | dfb824c6e1ca66a32a16f2a6358695b58ca656a8 | |
parent | 5809172695f0b174ae036dc829e6d3176d926342 (diff) |
WIPcli11
-rw-r--r-- | src/argparser.h | 153 | ||||
-rw-r--r-- | test/argparser_test.cc | 49 | ||||
-rw-r--r-- | test/ctor.cc | 18 |
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", |