Files
fpga_modem/tools/argparse.cpp

286 lines
8.9 KiB
C++

#include "argparse.hpp"
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <sstream>
ArgParser::ArgParser(std::string program_name)
: program_name_(std::move(program_name)) {}
void ArgParser::addString(const std::string &name,
const std::string &default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kString, help, required, short_name};
string_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addInt(const std::string &name,
int default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kInt, help, required, short_name};
int_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addFlag(const std::string &name,
const std::string &help,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kFlag, help, false, short_name};
flag_values_[name] = false;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
bool ArgParser::parse(int argc, char **argv, std::string *error) {
for (int i = 1; i < argc; ++i) {
std::string token(argv[i]);
if (token == "--help" || token == "-h") {
if (error) {
*error = "help";
}
return false;
}
if (token.rfind("--", 0) != 0 && token.rfind("-", 0) == 0) {
std::string short_key = token.substr(1);
std::string short_value;
size_t short_eq = short_key.find('=');
if (short_eq != std::string::npos) {
short_value = short_key.substr(short_eq + 1);
short_key = short_key.substr(0, short_eq);
}
auto sk = short_to_long_.find(short_key);
if (sk == short_to_long_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
auto m = meta_.find(sk->second);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (short_eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: -" + short_key;
}
return false;
}
flag_values_[sk->second] = true;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kString) {
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
string_values_[sk->second] = short_value;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kInt) {
long parsed;
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
errno = 0;
char *endp = nullptr;
parsed = std::strtol(short_value.c_str(), &endp, 0);
if (errno != 0 || endp == short_value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for -" + short_key + ": " + short_value;
}
return false;
}
int_values_[sk->second] = static_cast<int>(parsed);
provided_[sk->second] = true;
}
continue;
}
if (token.rfind("--", 0) != 0) {
if (error) {
*error = "Unexpected positional argument: " + token;
}
return false;
}
std::string key;
std::string value;
size_t eq = token.find('=');
if (eq == std::string::npos) {
key = token.substr(2);
} else {
key = token.substr(2, eq - 2);
value = token.substr(eq + 1);
}
auto m = meta_.find(key);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: --" + key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: --" + key;
}
return false;
}
flag_values_[key] = true;
provided_[key] = true;
continue;
}
if (eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for --" + key;
}
return false;
}
value = argv[++i];
}
if (m->second.type == OptionType::kString) {
string_values_[key] = value;
provided_[key] = true;
} else if (m->second.type == OptionType::kInt) {
errno = 0;
char *endp = nullptr;
long parsed = std::strtol(value.c_str(), &endp, 0);
if (errno != 0 || endp == value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for --" + key + ": " + value;
}
return false;
}
int_values_[key] = static_cast<int>(parsed);
provided_[key] = true;
}
}
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m != meta_.end() && m->second.required && !has(key)) {
if (error) {
*error = "Missing required option: --" + key;
}
return false;
}
}
return true;
}
bool ArgParser::has(const std::string &name) const {
auto p = provided_.find(name);
return p != provided_.end() && p->second;
}
std::string ArgParser::getString(const std::string &name) const {
auto it = string_values_.find(name);
if (it == string_values_.end()) {
return std::string();
}
return it->second;
}
int ArgParser::getInt(const std::string &name) const {
auto it = int_values_.find(name);
if (it == int_values_.end()) {
return 0;
}
return it->second;
}
bool ArgParser::getFlag(const std::string &name) const {
auto it = flag_values_.find(name);
if (it == flag_values_.end()) {
return false;
}
return it->second;
}
std::string ArgParser::helpText() const {
std::ostringstream oss;
oss << "Usage: " << program_name_ << " [options]\n\n";
oss << "Options:\n";
oss << " -h, --help Show this help\n";
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m == meta_.end()) {
continue;
}
oss << " ";
if (!m->second.short_name.empty()) {
oss << "-" << m->second.short_name << ", ";
} else {
oss << " ";
}
oss << "--" << key;
if (m->second.type != OptionType::kFlag) {
oss << " <value>";
}
if (m->second.required) {
oss << " (required)";
}
oss << "\n";
oss << " " << m->second.help;
if (m->second.type == OptionType::kString) {
auto s = string_values_.find(key);
if (s != string_values_.end()) {
oss << " [default: '" << s->second << "']";
}
} else {
if (m->second.type == OptionType::kInt) {
auto iv = int_values_.find(key);
if (iv != int_values_.end()) {
oss << " [default: " << iv->second << "]";
}
}
}
oss << "\n";
}
return oss.str();
}