#pragma once #include #include #include #include #include "program_args/any.h" namespace program { /** * @brief A string-literal object that requires input arguments to be two or * more characters */ struct LongArg { template constexpr LongArg(char const (&str)[N]) : str(str) { static_assert(N > 1, "cannot create longargs with 0 or 1 character"); } operator std::string const &() const { return str; } std::string str; }; constexpr struct require_action_t { } require_action; template class Arguments { private: using option_id = std::string; struct Action; struct Argument; struct Flag; struct Option; struct Rest; template struct WithDefault; public: Arguments() = default; Arguments(int argc, char const * const * const argv) : Arguments(argc, argv, (void const *)nullptr) {} std::vector args() const { size_t const offset = std::min(arguments.size(), argument_names.size()); return {arguments.begin() + offset, arguments.end()}; } protected: template Arguments(T const * parent) : parent_(parent) {} template Arguments(int argc, char const * const * const argv, T const * parent); template T const * parent() const { return parent_.get(); } template int invoke_action(DA const & default_action, T const & tuple, std::index_sequence, Ps const &... ps) const; protected: /** * @brief Define an "action"/"subcommand"/"verb" to be executed. An example of * this pattern would be how git visualizes sub-commands in its interface, * even though in implementation it appears different. * @invariant Will fail to compile if bound to something that isn't a subtype * of Arguments * @param name A name of the action for usage messages * @param description An optional description object * @return A binding object that can implicitly cast to a subtype of Arguments * * @throws ArgumentMixingError if both argument() and action() have been * invoked in this */ auto action(LongArg name, std::string const & description = ""); /** * @brief Define an argument to be passed in to this program * * @param index The 0-based index of this argument in the program args array. * @param name A name of the argument for usage messages * @param description An optional description object * @return A binding object that will write its data in to an object of arity * 1 * * @throws ArgumentMixingError if both argument() and action() have been * invoked in this * @throws ArgumentStructureError if this argument is required, but a prior * argument was optional * @throws IllegalPositionError if index is already in use in this * @throws IllegalPositionError if no argument is available at index */ auto argument(size_t index, LongArg name, std::string const & description = ""); /** * @brief Return all unbound arguments * @param name An optional name to describe these arguments * @param description An optional description object * @returns All unbound arguments in {@sa Arguments::arguments} * @throws ArgumentMixingError if both argument() and action() have been invoked in this * @throws ArgumentStructureError if rest is called with multiple names */ auto rest(LongArg name = "args", std::string const & description = ""); /** * @brief Define a flag (option with an arity of zero). * If a flag binds to a boolean and has a name, then t will be invertable with * "--no-" prefix. If a flag binds to an integer, then it increments the value * with each repetition. * @invariant One or both of name and abbrev must be provided * @param name The name of the flag, to be parsed from the commandline with a * "--" prefix * @param abbrev A single-character version of a flag, to be parsed from the * commandline with a '-' prefix * @param description An optional description object */ auto flag(LongArg name, char abbrev, std::string const & description = ""); auto flag(LongArg name, std::string const & description = ""); auto flag(char abbrev, std::string const & description = ""); /** * @brief Define an option. If an option is repeated, then it can be used to * fill out multiple entries in a map or vector. * @invariant One or both of name and abbrev must be provided * @param name The name of the option, to be parsed from the commandline with * a "--" prefix * @param abbrev A single-character version of an option, to be parsed from * the commandline with a '-' prefix * @param description An optional description object */ auto option(LongArg name, char abbrev, std::string const & description = ""); auto option(LongArg name, std::string const & description = ""); auto option(char abbrev, std::string const & description = ""); private: void usage() const; void add_options(std::string const & name, char abbrev, std::string const & description, std::vector aliases = {}); bool has_actions() const { return !action_descriptions.empty(); } bool has_arguments() const { return !(rest_name.empty() && argument_names.empty()); } option_id id(std::string const & arg) const; option_id id(char arg) const { return id({'-', arg}); } bool is_option(std::string const & arg) const; bool is_option(char arg) const { return is_option({'-', arg}); } bool is_flag(std::string const & arg) const; bool is_flag(char arg) const { return is_flag({'-', arg}); } private: // A helper for what limited introspection is required for handling actions in // test friend struct ArgumentTestHelper; template friend class Arguments; friend int main(int, char const * const *); operator bool() const { return primed_; } auto const & self() const { return static_cast(*this); } template int invoke(Parents const &... parents) const { return typed_main(parents..., self()); } template std::shared_ptr make_action(std::string const & name) const { if (action_name_ != name) { return nullptr; } auto ptr = make_action_(static_cast(*this)); return std::static_pointer_cast(ptr); } private: using make_action_t = std::function(Impl const &)>; using action_hook_t = std::function; constexpr static size_t const no_optional_args{~0ul}; // Metadata variables bool primed_{false}; detail::Any parent_; std::string rest_name; std::map argument_descriptions; std::map argument_names; size_t optional_from{no_optional_args}; std::map action_descriptions; std::map actions; std::map option_descriptions; std::map option_names; std::set flag_names; // Data/Output variables std::string program; std::vector arguments; std::string action_name_{""}; make_action_t make_action_{nullptr}; std::map> options; std::map flags; }; template struct Arguments::Action { Arguments * self; std::string name; std::string description; template operator T() const; bool primed() const; explicit operator bool() const; }; template struct Arguments::Argument { Arguments * self; size_t index; bool is_optional; std::string name; std::string description; template auto operator=(T && value); template operator T() const; bool primed() const; explicit operator bool() const; }; template struct Arguments::Flag { Arguments * self; std::string name; char abbrev; std::string description; bool default_value; auto operator=(bool && value); operator bool() const; template operator T() const; bool primed(bool inv) const; }; template struct Arguments::Option { Arguments * self; std::string name; char abbrev; std::string description; template auto operator=(std::initializer_list value); template auto operator=(T && value); template operator T() const; bool primed() const; explicit operator bool() const; }; template struct Arguments::Rest { Arguments * self; template auto operator=(T && value); operator std::vector() const; explicit operator bool() const; }; template template struct Arguments::WithDefault { B impl; V default_value; template operator T() const; }; } #include "arguments_impl.hpp" template int typed_main(Args const &, Actions const &...); #define PROGRAM_ARGS_INVOKE_WITH_DEFAULT_P(super, default, ...) \ template \ int invoke(Parents const &... parents) const { \ return invoke_action(default, program::actions(__VA_ARGS__), \ program::count(__VA_ARGS__), parents...); \ } \ using super::super #define PROGRAM_ARGS_INVOKE_WITH_DEFAULT(default, ...) \ PROGRAM_ARGS_INVOKE_WITH_DEFAULT_P(Arguments, default, __VA_ARGS__) #define PROGRAM_ARGS_INVOKE_P(super, ...) \ PROGRAM_ARGS_INVOKE_WITH_DEFAULT_P(super, program::require_action, \ __VA_ARGS__) #define PROGRAM_ARGS_INVOKE(...) \ PROGRAM_ARGS_INVOKE_WITH_DEFAULT(program::require_action, __VA_ARGS__) #define PROGRAM_ARGS_MAIN(tname) \ int main(int argc, char const * const * const argv) try { \ return tname(argc, argv).invoke(); \ } catch (program::ProgramArgumentsError const & pae) { \ std::cerr << "Error in program argument handling: " << pae.what() << "\n"; \ } #define TYPED_MAIN(...) template <> int typed_main(__VA_ARGS__) #define PROGRAM_DEFER(...) [this]() { return __VA_ARGS__; }