#pragma once #include #include #include "arguments.h" #include "exception.h" #include "utilities.h" namespace program { template template Arguments::Argument::operator T() const { if (!primed()) { return T(); } else if (self->arguments.size() > index) { return convert(name, self->arguments.at(index)); } throw IllegalPositionError("No argument provided", index); } template Arguments::Argument::operator bool() const { return primed() && self->arguments.size() > index; } template template auto Arguments::Argument::operator=(T && value) { is_optional = true; return WithDefault{*this, std::forward(value)}; } template bool Arguments::Argument::primed() const { if (self->primed_) { return true; } if (is_optional) { self->optional_from = std::min(self->optional_from, index); } else if (self->optional_from < index) { throw ArgumentStructureError{"Required positional after optional", name}; } if (!self->argument_names.emplace(index, name).second) { throw IllegalPositionError("Duplicate positional", index); } self->argument_descriptions.emplace(name, description); return false; } } namespace program { template template Arguments::Flag::operator T() const { return primed(false) ? static_cast(self->flags.at(name)) : T(); } template Arguments::Flag::operator bool() const { return primed(true) ? static_cast(self->flags.at(name)) : default_value; } template auto Arguments::Flag::operator=(bool && value) { default_value = value; return *this; } template bool Arguments::Flag::primed(bool inv) const { if (self->primed_) { return self->flags.count(name); } self->add_options(name, abbrev, description, std::vector(inv, "no-" + name)); self->flag_names.emplace(name); return false; } } namespace program { template template Arguments::Option::operator T() const { return (*this) ? convert(name, self->options.at(name)) : T(); } template Arguments::Option::operator bool() const { return primed() && self->options.count(name); } template template auto Arguments::Option::operator=(T && value) { return WithDefault{*this, std::forward(value)}; } template bool Arguments::Option::primed() const { if (self->primed_) { return true; } self->add_options(name, abbrev, description); return false; } } namespace program { template Arguments::Arguments(int argc, char const * const * const argv) { Impl generator; *this = static_cast(generator); if (argument_names.rbegin()->first != argument_names.size() - 1) { throw IllegalPositionError("Higher positional than number recorded", argument_names.rbegin()->first); } primed_ = true; program = argv[0]; for (size_t i = 1; i < argc; ++i) { std::string arg = argv[i]; char abbrev = arg[1]; if (arg == "--help") { usage(); std::exit(0); } else if (arg == "--") { arguments.insert(arguments.end(), &argv[i + 1], &argv[argc]); break; } else if (arg[0] != '-') { arguments.emplace_back(arg); } else if (is_flag(arg)) { if (arg.substr(0, 4) == "--no-") { flags[id(arg)] = 0; } else { ++flags[id(arg)]; } } else if (is_option(arg)) { options[id(arg)].emplace_back(argv[++i]); } else if (is_flag(abbrev) && arg.find_last_not_of("0123456789") == 1) { flags[id(abbrev)] = std::stoi(arg.substr(2)); } else if (is_flag(abbrev)) { for (auto c : arg.substr(1)) { if (!is_flag(c)) { throw NotAnArgumentError({'-', c}); } ++flags[id(c)]; } } else if (is_option(abbrev)) { options[id(abbrev)].emplace_back(arg.substr(2)); } else { throw NotAnArgumentError(arg); } } } template void Arguments::usage() const { std::cout << program << " [options...]"; for (auto & [index, name] : argument_names) { std::cout << " " << (index == optional_from ? "[" : "") << name; } if (optional_from != std::numeric_limits::max()) { std::cout << "]"; } std::cout << "\nArgument Arguments:\n"; for (auto & [name, desc] : argument_descriptions) { std::cout << " " << name << ": " << desc << "\n"; } std::cout << "Options:\n"; for (auto & [opt, desc] : option_descriptions) { std::cout << " " << opt << ": " << desc << "\n"; } } template void Arguments::add_options(std::string const & name, char abbrev, std::string const & description, std::vector aliases) { for (auto & str : aliases) { str = "--" + str; } aliases.push_back("--" + name); if (abbrev) { aliases.push_back(std::string{'-', abbrev}); } for (auto & str : aliases) { if (!option_names.emplace(str, name).second) { throw ArgumentStructureError("Duplicate option string", str); } } option_descriptions.emplace(join(",", aliases), description); } template auto Arguments::id(std::string const & arg) const -> option_id { if (!is_option(arg)) { throw NotAnArgumentError(arg); } return option_names.at(arg); } template bool Arguments::is_option(std::string const & arg) const { return option_names.count(arg); } template bool Arguments::is_flag(std::string const & arg) const { return is_option(arg) && flag_names.count(id(arg)); } template auto Arguments::argument(size_t index, std::string const & name, std::string const & description) { return Argument{this, index, false, name, description}; } template auto Arguments::flag(std::string const & name, std::string const & description) { return Flag{this, name, 0, description, false}; } template auto Arguments::flag(std::string const & name, char abbrev, std::string const & description) { return Flag{this, name, abbrev, description, false}; } template auto Arguments::option(std::string const & name, std::string const & description) { return Option{this, name, 0, description}; } template auto Arguments::option(std::string const & name, char abbrev, std::string const & description) { return Option{this, name, abbrev, description}; } }