argparser: refactor the entire argparser
This commit is contained in:
parent
41a2c6d621
commit
64b682c4ea
|
@ -1,63 +1,117 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <format>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <print>
|
#include <print>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class ArgParser {
|
class ArgParser {
|
||||||
public:
|
public:
|
||||||
void add(const std::string& shortOpt, const std::string& longOpt, const std::string& description, bool requiresValue = true) {
|
void add(std::string_view shortOpt, std::string_view longOpt, const std::string_view desc, const bool hasValue = true, const bool isRequired = false,
|
||||||
Argument arg{shortOpt, longOpt, description, std::nullopt, requiresValue, false};
|
std::string_view defaultValue = "") {
|
||||||
m_arguments.push_back(arg);
|
const Argument arg{shortOpt, longOpt, desc, defaultValue, hasValue, isRequired};
|
||||||
if (!shortOpt.empty())
|
m_arguments.emplace_back(arg);
|
||||||
m_optMap[shortOpt] = &m_arguments.back();
|
|
||||||
if (!longOpt.empty())
|
m_opts.emplace(shortOpt, std::ref(m_arguments.back()));
|
||||||
m_optMap[longOpt] = &m_arguments.back();
|
m_opts.emplace(longOpt, std::ref(m_arguments.back()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parse(int argc, char* argv[]) {
|
[[nodiscard]] bool parse(const int argc, const char* argv[]) const {
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
printHelp();
|
printHelp();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
std::string arg = argv[i];
|
std::string_view arg = argv[i];
|
||||||
|
|
||||||
if (arg == "-h" || arg == "--help") {
|
if (arg == "-h" || arg == "--help") {
|
||||||
printHelp();
|
printHelp();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* argument = findArgument(arg); argument) {
|
if (auto argument = findArgument(arg); argument.has_value()) {
|
||||||
argument->exists = true;
|
auto& argRef = argument->get();
|
||||||
if (argument->requiresValue) {
|
argRef.isPresent = true;
|
||||||
if (i + 1 >= argc)
|
|
||||||
throw std::invalid_argument("Missing value for " + arg);
|
if (argRef.requiresValue) {
|
||||||
argument->value = argv[++i];
|
if (i + 1 >= argc) {
|
||||||
|
throw std::invalid_argument(std::format("Missing value for: {}", arg));
|
||||||
|
}
|
||||||
|
argRef.value = argv[++i];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw std::invalid_argument("Unknown option: " + arg);
|
throw std::invalid_argument(std::format("Unknown option: {}", arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& arg : m_opts) {
|
||||||
|
const auto& argRef = arg.second.get();
|
||||||
|
if (argRef.isRequired && !argRef.isPresent) {
|
||||||
|
throw std::invalid_argument(std::format("Required argument: [{}, {}] not provided", argRef.shortOpt, argRef.longOpt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T get(const std::string& arg) const {
|
T get(const std::string_view arg) const {
|
||||||
const Argument* argument = findArgument(arg);
|
if (const auto argument = findArgument(arg); argument.has_value()) {
|
||||||
if (!argument || !argument->value)
|
return convertToType<T>(argument->get().value.value());
|
||||||
throw std::runtime_error(arg + " has no value");
|
} else {
|
||||||
|
throw std::runtime_error(std::format("{} has no value", arg));
|
||||||
return convertToType<T>(*argument->value);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has(const std::string& arg) const {
|
bool has(std::string_view arg) const {
|
||||||
const Argument* argument = findArgument(arg);
|
if (const auto argument = findArgument(arg); argument.has_value()) {
|
||||||
return argument && argument->exists;
|
return argument->get().isPresent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Argument {
|
||||||
|
std::string_view shortOpt;
|
||||||
|
std::string_view longOpt;
|
||||||
|
std::string_view description;
|
||||||
|
std::optional<std::string_view> value;
|
||||||
|
bool requiresValue;
|
||||||
|
bool isRequired = false;
|
||||||
|
bool isPresent = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T convertToType(const std::string_view value) const {
|
||||||
|
if constexpr (std::is_same_v<T, bool>) {
|
||||||
|
if (value == "true")
|
||||||
|
return true;
|
||||||
|
if (value == "false")
|
||||||
|
return false;
|
||||||
|
throw std::runtime_error("Invalid boolean string");
|
||||||
|
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||||
|
T result{};
|
||||||
|
auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), result);
|
||||||
|
if (ec != std::errc()) {
|
||||||
|
throw std::runtime_error(std::format("Failed to convert the value: {}", value));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
return std::string(value);
|
||||||
|
} else if constexpr (std::is_same_v<T, std::string_view>) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Unsupported type for conversion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::reference_wrapper<Argument>> findArgument(const std::string_view opt) const {
|
||||||
|
if (const auto it = m_opts.find(opt); it != m_opts.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void printHelp() const {
|
void printHelp() const {
|
||||||
|
@ -67,41 +121,6 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
std::vector<Argument> m_arguments;
|
||||||
struct Argument {
|
std::unordered_map<std::string_view, std::reference_wrapper<Argument>> m_opts;
|
||||||
std::string shortOpt;
|
|
||||||
std::string longOpt;
|
|
||||||
std::string description;
|
|
||||||
std::optional<std::string> value;
|
|
||||||
bool requiresValue;
|
|
||||||
bool exists = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
Argument* findArgument(const std::string& opt) {
|
|
||||||
if (auto it = m_optMap.find(opt); it != m_optMap.end())
|
|
||||||
return it->second;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Argument* findArgument(const std::string& opt) const {
|
|
||||||
if (auto it = m_optMap.find(opt); it != m_optMap.end())
|
|
||||||
return it->second;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T convertToType(const std::string& value) const {
|
|
||||||
if constexpr (std::is_same_v<T, std::string>) {
|
|
||||||
return value;
|
|
||||||
} else if constexpr (std::is_same_v<T, int>) {
|
|
||||||
return std::stoi(value);
|
|
||||||
} else if constexpr (std::is_same_v<T, double>) {
|
|
||||||
return std::stod(value);
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Unsupported type for argument conversion");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Argument> m_arguments;
|
|
||||||
std::unordered_map<std::string, Argument*> m_optMap;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
#include "Helpers/WolframAlpha.hpp"
|
#include "Helpers/WolframAlpha.hpp"
|
||||||
#include <print>
|
#include <print>
|
||||||
|
|
||||||
int main(const int argc, char* argv[]) {
|
int main(const int argc, const char* argv[]) {
|
||||||
try {
|
try {
|
||||||
ArgParser args;
|
ArgParser args;
|
||||||
args.add("-h", "--help", "Print help", false);
|
args.add("-h", "--help", "Print help", false);
|
||||||
args.add("-f", "--food", "Specify the food item");
|
args.add("-f", "--food", "Specify the food item", true, true);
|
||||||
args.add("-a", "--amount", "Specify the amount (in grams)");
|
args.add("-a", "--amount", "Specify the amount (in grams)", true, false, "100");
|
||||||
|
|
||||||
if (!args.parse(argc, argv)) {
|
if (!args.parse(argc, argv)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Reference in New Issue