286 lines
8.9 KiB
C++
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();
|
|
}
|