Created
October 30, 2022 14:15
-
-
Save scturtle/3018fa8e2f51866f7deb624ed2cbfe95 to your computer and use it in GitHub Desktop.
c++ argparser (adapted from Corrade)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <algorithm> | |
#include <iostream> | |
#include <sstream> | |
#include <string> | |
#include <vector> | |
#include <optional> | |
#include <iomanip> | |
enum class Type { | |
PositionalArgument, | |
NamedArgument, | |
BooleanOption, | |
}; | |
struct Entry { | |
Type type; | |
size_t id; | |
std::optional<char> short_key; | |
std::string key; | |
std::optional<std::string> value, help, default_value; | |
Entry(Type type, size_t id, char short_key, std::string key); | |
std::string key_name() const; | |
}; | |
Entry::Entry(Type type, size_t id, char short_key, std::string key) | |
: type(type), id(id), | |
short_key(short_key == '\0' ? std::nullopt : std::optional(short_key)), | |
key(key) {} | |
std::string Entry::key_name() const { | |
if (type == Type::PositionalArgument) | |
return key; | |
std::ostringstream os; | |
if (short_key) { | |
os << '-' << *short_key << '/'; | |
} | |
os << "--" << key; | |
return os.str(); | |
} | |
struct Arguments { | |
std::string _command; | |
std::vector<Entry> _entries; | |
Arguments &add_argument(std::string key); | |
Arguments &add_named_argument(char short_key, std::string key); | |
Arguments &add_named_argument(std::string key); | |
Arguments &add_boolean_option(char short_key, std::string key); | |
Arguments &add_boolean_option(std::string key); | |
Arguments &set_default(std::string default_value); | |
Arguments &set_help(std::string help); | |
Entry &_find_entry(const std::string_view key); | |
std::string_view _get_value(const std::string_view key); | |
template <typename T> T get(const std::string_view key); | |
bool _try_parse(int argc, char **argv); | |
void parse(int argc, char **argv); | |
std::string usage(); | |
}; | |
Arguments &Arguments::add_argument(std::string key) { | |
_entries.emplace_back(Type::PositionalArgument, _entries.size(), '\0', key); | |
return *this; | |
} | |
Arguments &Arguments::add_named_argument(char short_key, std::string key) { | |
_entries.emplace_back(Type::NamedArgument, _entries.size(), short_key, key); | |
return *this; | |
} | |
Arguments &Arguments::add_named_argument(std::string key) { | |
return add_named_argument('\0', key); | |
} | |
Arguments &Arguments::add_boolean_option(char short_key, std::string key) { | |
_entries.emplace_back(Type::BooleanOption, _entries.size(), short_key, key); | |
return *this; | |
} | |
Arguments &Arguments::add_boolean_option(std::string key) { | |
return add_boolean_option('\0', key); | |
} | |
Arguments &Arguments::set_default(std::string default_value) { | |
_entries.back().default_value = default_value; | |
return *this; | |
} | |
Arguments &Arguments::set_help(std::string help) { | |
_entries.back().help = help; | |
return *this; | |
} | |
Entry &Arguments::_find_entry(std::string_view key) { | |
for (Entry &e : _entries) | |
if (e.key == key || (key.size() == 1 && e.short_key == key[0])) | |
return e; | |
std::cerr << "Cannot find argument " << key << std::endl; | |
std::terminate(); | |
} | |
std::string_view Arguments::_get_value(std::string_view key) { | |
auto& value = _find_entry(key).value; | |
if (!value) { | |
std::cerr << "No value for " << key << std::endl; | |
std::terminate(); | |
} | |
return *value; | |
} | |
template <> int Arguments::get<int>(std::string_view key) { | |
return std::stoi(std::string(_get_value(key))); | |
} | |
template <> bool Arguments::get<bool>(std::string_view key) { | |
std::string_view value = _get_value(key); | |
return value == "1" || value == "y" || value == "t" || value == "yes" || value == "true" || value == "True" || value == "TRUE"; | |
} | |
template <> std::string Arguments::get<std::string>(std::string_view key) { | |
return std::string(_get_value(key)); | |
} | |
bool Arguments::_try_parse(int argc, char **argv) { | |
_command = argv[0]; | |
std::vector<std::string> positional_values; | |
Entry *value_for = nullptr; | |
for (int i = 1; i < argc; ++i) { | |
// set value from next argv | |
if (value_for) { | |
value_for->value = argv[i]; | |
value_for = nullptr; | |
continue; | |
} | |
size_t len = std::strlen(argv[i]); | |
if (len > 1 && argv[i][0] == '-') { | |
// non-positional argument | |
const char *eq = nullptr; | |
const char *st = argv[i][1] == '-' ? argv[i] + 2 : argv[i] + 1; | |
std::string key; | |
eq = std::strchr(argv[i], '='); | |
if (eq) { | |
key = std::string(st, eq - st); | |
} else { | |
key = std::string(st); | |
} | |
Entry &entry = _find_entry(key); | |
if (eq) { | |
entry.value = eq + 1; | |
} else if (entry.type == Type::BooleanOption) { | |
entry.value = "true"; | |
} else { | |
value_for = &entry; | |
} | |
} else { | |
positional_values.push_back(argv[i]); | |
} | |
} | |
if (value_for) { | |
std::cerr << "No value for " << value_for->key_name() << std::endl; | |
return false; | |
} | |
// set positional or default value | |
size_t index = 0; | |
for (Entry &e : _entries) { | |
if (e.type == Type::PositionalArgument) { | |
if (index < positional_values.size()) { | |
e.value = positional_values[index++]; | |
} else if (e.default_value) { | |
std::cerr << "No default value for " << e.key_name() << std::endl; | |
return false; | |
} | |
} | |
if (!e.value) { | |
if (!e.default_value) { | |
std::cerr << "No default value for " << e.key_name() << std::endl; | |
return false; | |
} | |
e.value = *e.default_value; | |
} | |
} | |
if (index < positional_values.size()) { | |
for (; index < positional_values.size(); ++index) { | |
std::cerr << "unused argument " << positional_values[index] << std::endl; | |
} | |
return false; | |
} | |
return true; | |
} | |
void Arguments::parse(int argc, char **argv) { | |
if (!_try_parse(argc, argv)) { | |
std::cerr << '\n' << usage() << std::endl; | |
std::terminate(); | |
} | |
} | |
std::string Arguments::usage() { | |
std::ostringstream os; | |
os << "Usage:\n " << _command; | |
for (const Entry& e : _entries) { | |
os << ' '; | |
if (e.default_value || e.type == Type::BooleanOption) | |
os << '['; | |
os << e.key_name(); | |
if (e.default_value || e.type == Type::BooleanOption) | |
os << ']'; | |
} | |
os << "\n\nArguments:\n"; | |
size_t key_col_width = 0; | |
for (const Entry& e : _entries) | |
key_col_width = std::max(key_col_width, e.key_name().size()); | |
for (const Entry& e : _entries) { | |
os << " " << std::left << std::setw(key_col_width + 4) << e.key_name(); | |
os << (e.help ? *e.help : e.key); | |
if (e.default_value) | |
os << " (default: " << *e.default_value << ')'; | |
os << '\n'; | |
} | |
return os.str(); | |
} | |
int main(int argc, char **argv) { | |
Arguments args; | |
args.add_argument("filename") | |
.set_help("filename") | |
.add_named_argument('v', "verbose") | |
.set_default("1") | |
.set_help("verbose") | |
.add_boolean_option('t', "test") | |
.set_help("test"); | |
args.parse(argc, argv); | |
std::cout << args.get<std::string>("filename") << std::endl; | |
std::cout << args.get<int>("verbose") << std::endl; | |
std::cout << args.get<bool>("test") << std::endl; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment