Skip to content

Instantly share code, notes, and snippets.

@scturtle
Created October 30, 2022 14:15
Show Gist options
  • Save scturtle/3018fa8e2f51866f7deb624ed2cbfe95 to your computer and use it in GitHub Desktop.
Save scturtle/3018fa8e2f51866f7deb624ed2cbfe95 to your computer and use it in GitHub Desktop.
c++ argparser (adapted from Corrade)
#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