parselink-old/source/include/parselink/utility/argparse.h

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