#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 [=](Impl const & args) { return std::make_shared(T(argc, argv, &args)); }; }); } else if (auto ptr = self->make_action(name)) { return *ptr; } 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->has_arguments()) { 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->has_actions()) { 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 template auto Arguments::Rest::operator=(T && value) { return WithDefault{*this, std::forward(value)}; } template Arguments::Rest::operator std::vector() const { return self->args(); } template Arguments::Rest::operator bool() const { return self->arguments.size() > self->argument_names.size(); } } namespace program { template template template Arguments::WithDefault::operator T() const { if (impl) { return impl; } if constexpr (std::is_invocable_r{}) { return T{default_value()}; } else { return T{default_value}; } } } namespace program { template template Arguments::Arguments(int argc, char const * const * const argv, T const * parent) { Impl generator(parent); *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]) { action_name_ = arg; make_action_ = hook(argc - i, argv + i); return; } else if (arg == "help" && next && actions[next]) { char const * const help[] = {next, "--help"}; action_name_ = next; make_action_ = hook(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::rest(LongArg name, std::string const & description) { if (has_actions()) { throw ArgumentMixingError(); } if (!rest_name.empty() && rest_name != name.str) { throw ArgumentStructureError("duplicate rest() parameter", name); } rest_name = name; argument_descriptions.emplace(name, description); return Rest{this}; } 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}; } template template int Arguments::invoke_action(DA const & default_action, T const & tuple, std::index_sequence, Ps const &... ps) const { // Only actions that were reached in the constructor will ever have the // primed_ flag set to true. Therefore, there is a guarantee that this // function will only call invoke() on a child one or zero times. auto invoke_one = [this, &ps...](auto const & action) { if (!action.primed_) { return std::make_pair(false, 0); } return std::make_pair(true, action.invoke(ps..., self())); }; // Because of that, it is safe to use a map from bool => status // Since we only care about the case where an action was invoked. std::map const rval{invoke_one(std::get(tuple))...}; if (rval.count(true)) { return rval.at(true); } // If no action was invoked, then we need to handle the fallthrough case // For certain circumstances, such as how git allows custom actions with // a plugin system, we allow you to specify a default action of self. if constexpr (std::is_same_v) { usage(); return 1; } else if constexpr (std::is_same_v) { return typed_main(ps..., self()); } else { return default_action.invoke(ps..., self()); } } } namespace program { template auto actions(Ts const &... ts) { return std::forward_as_tuple(ts...); } template auto count(Ts const &...) { return std::make_index_sequence{}; } }