357 lines
10 KiB
C++
357 lines
10 KiB
C++
//-----------------------------------------------------------------------------
|
|
// ___ __ _ _
|
|
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
|
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
|
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
|
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// Author: Kurt Sassenrath
|
|
// Module: Utility
|
|
//
|
|
// Command line argument parser. Pretty rough-n-ready, but gets the job done
|
|
// for starting the server.
|
|
//
|
|
// Copyright (c) 2023 Kurt Sassenrath.
|
|
//
|
|
// License TBD.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef argparse_d2ddac0dab0d7b88
|
|
#define argparse_d2ddac0dab0d7b88
|
|
|
|
#include <chrono>
|
|
#include <charconv>
|
|
#include <initializer_list>
|
|
#include <optional>
|
|
#include <map>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
// Simple command line parser for testing executables.
|
|
|
|
namespace argparse {
|
|
|
|
namespace custom {
|
|
template <typename T>
|
|
struct argument_parser {};
|
|
|
|
template <typename T>
|
|
concept has_parser = requires {
|
|
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) }
|
|
-> std::same_as<T*>;
|
|
};
|
|
}
|
|
|
|
template <typename T>
|
|
struct argument_parser {};
|
|
|
|
template <>
|
|
struct argument_parser<bool> {
|
|
static bool* parse(std::string_view value) noexcept {
|
|
if (value == "1" || value == "true") {
|
|
return new bool{true};
|
|
} else if (value == "0" || value == "false") {
|
|
return new bool{false};
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
inline constexpr std::initializer_list<
|
|
std::tuple<char const*, std::chrono::nanoseconds>> unit_map = {
|
|
{"ns", std::chrono::nanoseconds{1}},
|
|
{"us", std::chrono::microseconds{1}},
|
|
{"ms", std::chrono::milliseconds{1}},
|
|
{"s", std::chrono::seconds{1}},
|
|
{"m", std::chrono::minutes{1}},
|
|
{"h", std::chrono::hours{1}},
|
|
};
|
|
|
|
template <typename rep, typename period>
|
|
struct argument_parser<std::chrono::duration<rep, period>> {
|
|
using duration = std::chrono::duration<rep, period>;
|
|
|
|
static duration* parse(std::string_view value) noexcept {
|
|
rep result;
|
|
auto err = std::from_chars(value.begin(), value.end(), result);
|
|
if (err.ec == std::errc{}) {
|
|
auto this_unit = std::string_view{err.ptr, value.end()};
|
|
for (auto const& [unit, dura] : unit_map) {
|
|
if (std::string_view{unit} == this_unit) {
|
|
auto v =
|
|
std::chrono::duration_cast<duration>(result * dura);
|
|
return new duration{v};
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
requires requires (T& t) {
|
|
std::from_chars(nullptr, nullptr, t);
|
|
}
|
|
struct argument_parser<T> {
|
|
static T* parse(std::string_view value) noexcept {
|
|
T result;
|
|
auto err = std::from_chars(value.begin(), value.end(), result);
|
|
return err.ec == std::errc{} ? new T{result} : nullptr;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct argument_parser<std::string> {
|
|
static std::string* parse(std::string_view value) noexcept {
|
|
return new std::string{value};
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
concept has_parser = requires {
|
|
{ argument_parser<std::decay_t<T>>::parse(std::string_view{}) }
|
|
-> std::same_as<T*>;
|
|
};
|
|
|
|
static_assert(has_parser<int>);
|
|
|
|
namespace detail {
|
|
|
|
// This wrapper acts similar to std::any, but also provides a method to
|
|
// parse a string_view for the value as well. Parsers can be implemented
|
|
// by creating an argparse::custom::argument_parser template specialization
|
|
// for a given type.
|
|
struct any_arg {
|
|
|
|
constexpr any_arg() = default;
|
|
|
|
template <typename T>
|
|
any_arg(T&& value) : iface(&dispatcher<std::decay_t<T>>::table) {
|
|
dispatcher<std::decay_t<T>>::create(*this, std::forward<T>(value));
|
|
}
|
|
|
|
any_arg(any_arg const& other) : iface(other.iface) {
|
|
if (other.iface) {
|
|
data = other.iface->copy(other.data);
|
|
}
|
|
}
|
|
|
|
any_arg(any_arg&& other) : data(std::move(other.data)), iface(other.iface) {
|
|
other.iface = nullptr;
|
|
}
|
|
|
|
any_arg& operator=(any_arg const& other) {
|
|
if (has_value()) {
|
|
iface->destroy(data);
|
|
}
|
|
iface = other.iface;
|
|
if (other.iface) {
|
|
data = other.iface->copy(other.data);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
~any_arg() {
|
|
if (has_value()) {
|
|
iface->destroy(data);
|
|
}
|
|
}
|
|
|
|
bool parse(std::string_view sv) {
|
|
return iface->parse(*this, sv);
|
|
}
|
|
|
|
bool has_value() const noexcept {
|
|
return static_cast<bool>(iface);
|
|
}
|
|
|
|
template <typename T>
|
|
bool holds() const noexcept {
|
|
return iface == &dispatcher<std::decay_t<T>>::table;
|
|
}
|
|
|
|
template <typename T>
|
|
friend T const* arg_cast(any_arg const*) noexcept;
|
|
|
|
void* data = nullptr;
|
|
struct interface {
|
|
void (*destroy)(void*);
|
|
void *(*copy)(void*);
|
|
bool (*parse)(any_arg&, std::string_view);
|
|
};
|
|
|
|
interface const* iface = nullptr;
|
|
|
|
template <typename T>
|
|
struct dispatcher {
|
|
template <typename... Args>
|
|
static void create(any_arg& self, Args&&... args) {
|
|
self.data = new T{std::forward<Args>(args)...};
|
|
}
|
|
|
|
static void* copy(void *ptr) {
|
|
return new T{*static_cast<T*>(ptr)};
|
|
}
|
|
|
|
static bool parse(any_arg& self, std::string_view sv) {
|
|
if constexpr (custom::has_parser<T>) {
|
|
self.data = custom::argument_parser<T>::parse(sv);
|
|
return static_cast<bool>(self.data);
|
|
} else if constexpr (has_parser<T>) {
|
|
self.data = argument_parser<T>::parse(sv);
|
|
return static_cast<bool>(self.data);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void destroy(void* ptr) {
|
|
delete static_cast<T*>(ptr);
|
|
}
|
|
|
|
static T const* cast(void* ptr) {
|
|
return static_cast<T const*>(ptr);
|
|
}
|
|
|
|
static constexpr struct interface table {
|
|
&dispatcher::destroy, &dispatcher::copy, &dispatcher::parse };
|
|
};
|
|
};
|
|
|
|
template <typename T>
|
|
T const* arg_cast(any_arg const* ar) noexcept {
|
|
if (ar->holds<T>()) {
|
|
return any_arg::dispatcher<std::decay_t<T>>::cast(ar->data);
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
class command_line_parser {
|
|
public:
|
|
|
|
using argument = detail::any_arg;
|
|
constexpr static char delimiter = '=';
|
|
using opt_list = std::initializer_list<
|
|
std::tuple<std::string_view, argument>>;
|
|
|
|
struct result {
|
|
enum class code {
|
|
no_error,
|
|
unknown_option,
|
|
bad_option_value,
|
|
};
|
|
|
|
code ec = code::no_error;
|
|
std::string error_value;
|
|
std::map<std::string, argument, std::less<>> opts;
|
|
std::vector<std::string> arguments;
|
|
|
|
explicit constexpr operator bool() const noexcept {
|
|
return ec == code::no_error;
|
|
}
|
|
|
|
template <typename T>
|
|
T const* maybe_opt(std::string_view name) const noexcept {
|
|
auto entry = opts.find(name);
|
|
return entry != opts.end() ?
|
|
detail::arg_cast<T>(&entry->second) : nullptr;
|
|
}
|
|
|
|
template <typename T>
|
|
T const& opt(std::string_view opt_name) const {
|
|
auto const* v = maybe_opt<T>(opt_name);
|
|
if (!v) {
|
|
throw std::bad_cast{};
|
|
}
|
|
return *v;
|
|
}
|
|
|
|
std::string_view operator[](std::size_t index) const {
|
|
return arguments[index];
|
|
}
|
|
};
|
|
|
|
command_line_parser() noexcept = default;
|
|
|
|
explicit command_line_parser(opt_list opts) {
|
|
for (auto const& [name, value_type] : opts) {
|
|
add_option(name, value_type);
|
|
}
|
|
}
|
|
|
|
bool add_option(std::string_view name, argument value) {
|
|
options_[std::string{name}] = value;
|
|
return true;
|
|
}
|
|
|
|
result parse(std::span<std::string_view> arglist) {
|
|
return parse_inner(arglist);
|
|
}
|
|
|
|
template <typename T>
|
|
T const* get_option(std::string_view name) {
|
|
auto entry = options_.find(name);
|
|
if (entry != options_.end()) {
|
|
return detail::arg_cast<T>(&entry->second);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
|
|
static auto split_option(std::string_view kv) {
|
|
auto delim = kv.find(delimiter);
|
|
if (delim != kv.npos) {
|
|
return std::make_tuple(kv.substr(0, delim), kv.substr(delim + 1));
|
|
} else {
|
|
return std::make_tuple(kv, std::string_view{});
|
|
}
|
|
}
|
|
|
|
result parse_inner(std::span<std::string_view> arglist) {
|
|
result res;
|
|
|
|
for (auto const& [key, value] : options_) {
|
|
res.opts[key] = value;
|
|
}
|
|
|
|
for (auto ar : arglist) {
|
|
if (ar.empty()) continue; // ???
|
|
if (ar.substr(0, 2) == "--") {
|
|
auto [key, value] = split_option(ar.substr(2));
|
|
auto opt_entry = res.opts.find(key);
|
|
if (opt_entry == res.opts.end()) {
|
|
res.ec = result::code::unknown_option;
|
|
res.error_value = std::string{ar};
|
|
return res;
|
|
}
|
|
|
|
if (!opt_entry->second.parse(value)) {
|
|
res.ec = result::code::bad_option_value;
|
|
res.error_value = std::string{value};
|
|
return res;
|
|
}
|
|
} else {
|
|
res.arguments.emplace_back(ar);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::map<std::string, argument, std::less<>> options_;
|
|
std::vector<std::string> arguments_;
|
|
};
|
|
|
|
}
|
|
|
|
|
|
#endif // argparse_d2ddac0dab0d7b88
|