#pragma once #include #include #include "arguments.h" #include "exception.h" #include "utilities.h" #ifndef PROGRAM_ARGS_NOEXIT_ON_HELP #define PROGRAM_ARGS_EXIT(X) std::exit(X) #else #define PROGRAM_ARGS_EXIT(X) (void)X #endif namespace program { template template Arguments::Action::operator T() const { static_assert(std::is_base_of_v, T>, "Action can only bind to Arguments"); if (!primed()) { self->actions.emplace(name, [](int argc, char const * const *argv) { return [action = T(argc, argv)](Arguments const &args){ return typed_main(args, action); }; }); } return T(); } template Arguments::Action::operator bool() const { return primed() && self->arguments.size() > 0; } template bool Arguments::Action::primed() const { if (self->primed_) { return true; } if (!self->argument_names.empty()) { throw ArgumentMixingError(); } self->action_descriptions.emplace(name, description); return false; } } 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; if (description.size()) { using ::program::to_string; description += "\n Default Value: " + to_string(value); } return WithDefault{*this, std::forward(value)}; } template bool Arguments::Argument::primed() const { if (self->primed_) { return true; } if (!self->actions.empty()) { throw ArgumentMixingError(); } 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) { if (description.size()) { description += "\n Default Value: "; description += (value ? "true" : "false"); } default_value = value; return *this; } template bool Arguments::Flag::primed(bool inv) const { if (self->primed_) { return self->flags.count(name); } std::vector aliases; if (name.size() > 1 && inv) { aliases.push_back("no-" + name); } self->add_options(name, abbrev, description, aliases); 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) { if (description.size()) { using ::program::to_string; description += "\n Default Value: " + to_string(value); } return WithDefault{*this, std::forward(value)}; } template template auto Arguments::Option::operator=(std::initializer_list value) { if (description.size()) { using ::program::to_string; description += "\n Default Value: " + to_string(value); } return WithDefault>{*this, 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.size() && 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 const * const next = argv[i + 1]; char abbrev = arg[1]; if (arg == "--help") { usage(); PROGRAM_ARGS_EXIT(0); } else if (arg == "--") { arguments.insert(arguments.end(), &next, &argv[argc]); break; } else if (auto hook = actions[arg]) { main_callback = hook(argc - i, argv + i); return; } else if (arg == "help" && next && actions[next]) { char const * const help[] = {next, "--help"}; main_callback = actions[next](2, help); return; } else if (arg[0] != '-') { arguments.emplace_back(arg); } else if (is_flag(arg)) { if (arg.substr(0, 5) == "--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 (rest_name_.size()) { std::cout << " [" << rest_name_ << "...]"; } if (optional_from != no_optional_args) { 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; } if (name.size() > 1) { 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::action(LongArg name, std::string const & description) { return Action{this, name, description}; } template auto Arguments::argument(size_t index, LongArg name, std::string const & description) { return Argument{this, index, false, name, description}; } template auto Arguments::flag(LongArg name, std::string const & description) { return Flag{this, name, 0, description, false}; } template auto Arguments::flag(LongArg name, char abbrev, std::string const & description) { return Flag{this, name, abbrev, description, false}; } template auto Arguments::flag(char abbrev, std::string const & description) { return Flag{this, {abbrev}, abbrev, description, false}; } template auto Arguments::option(LongArg name, std::string const & description) { return Option{this, name, 0, description}; } template auto Arguments::option(LongArg name, char abbrev, std::string const & description) { return Option{this, name, abbrev, description}; } template auto Arguments::option(char abbrev, std::string const & description) { return Option{this, {abbrev}, abbrev, description}; } }