#ifndef ARGPARSE_H #define ARGPARSE_H #include #include #include #include #include #include #include #include "argparse_formatter.hpp" #include "argparse_default_converter.hpp" #include "argparse_error.hpp" #include "argparse_value.hpp" namespace argparse { class Argument; class ArgumentGroup; enum class Action { STORE, STORE_TRUE, STORE_FALSE, HELP, VERSION }; enum class ShowIn { USAGE_AND_HELP, HELP_ONLY }; class ArgumentParser { public: //Initializes an argument parser ArgumentParser(std::string prog_name, std::string description_str=std::string(), std::ostream& os=std::cout); //Overrides the program name ArgumentParser& prog(std::string prog, bool basename_only=true); //Sets the program version ArgumentParser& version(std::string version); //Specifies the epilog text at the bottom of the help description ArgumentParser& epilog(std::string prog); //Adds an argument or option with a single name (single value) template> Argument& add_argument(ArgValue& dest, std::string option); //Adds an option with a long and short option name (single value) template> Argument& add_argument(ArgValue& dest, std::string long_opt, std::string short_opt); //Adds an argument or option with a single name (multi value) template> Argument& add_argument(ArgValue>& dest, std::string option); //Adds an option with a long and short option name (multi value) template> Argument& add_argument(ArgValue>& dest, std::string long_opt, std::string short_opt); //Adds a group to collect related arguments ArgumentGroup& add_argument_group(std::string description_str); //Like parse_arg_throw(), but catches exceptions and exits the program void parse_args(int argc, const char* const* argv, int error_exit_code=1, int help_exit_code=0, int version_exit_code=0); //Parses the specified command-line arguments and sets the appropriat argument values // Returns a vector of Arguments which were specified. //If an error occurs throws ArgParseError //If an help is requested occurs throws ArgParseHelp void parse_args_throw(int argc, const char* const* argv); void parse_args_throw(std::vector args); //Reset the target values to their initial state void reset_destinations(); //Prints the basic usage void print_usage(); //Prints the usage and full help description for each option void print_help(); //Prints the version information void print_version(); public: //Returns the program name std::string prog() const; std::string version() const; //Returns the program description (after usage, but before option descriptions) std::string description() const; //Returns the epilog (end of help) std::string epilog() const; //Returns all the argument groups in this parser std::vector argument_groups() const; private: void add_help_option_if_unspecified(); struct ShortArgInfo { bool is_no_space_short_arg = false; std::shared_ptr arg; std::string value; }; ShortArgInfo no_space_short_arg(std::string str, const std::map>& str_to_option_arg) const; private: std::string prog_; std::string description_; std::string epilog_; std::string version_; std::vector argument_groups_; std::unique_ptr formatter_; std::ostream& os_; ArgValue show_help_dummy_; //Dummy variable used as destination for automatically generated help option }; class ArgumentGroup { public: //Adds an argument or option with a single name (single value) template> Argument& add_argument(ArgValue& dest, std::string option); //Adds an option with a long and short option name (single value) template> Argument& add_argument(ArgValue& dest, std::string long_opt, std::string short_opt); //Adds an argument or option with a multi name (multi value) template> Argument& add_argument(ArgValue>& dest, std::string option); //Adds an option with a long and short option name (multi value) template> Argument& add_argument(ArgValue>& dest, std::string long_opt, std::string short_opt); //Adds an epilog to the group ArgumentGroup& epilog(std::string str); public: //Returns the name of the group std::string name() const; //Returns the epilog std::string epilog() const; //Returns the arguments within the group const std::vector>& arguments() const; public: ArgumentGroup(const ArgumentGroup&) = default; ArgumentGroup(ArgumentGroup&&) = default; ArgumentGroup& operator=(const ArgumentGroup&) = delete; ArgumentGroup& operator=(const ArgumentGroup&&) = delete; private: friend class ArgumentParser; ArgumentGroup(std::string name_str=std::string()); private: std::string name_; std::string epilog_; std::vector> arguments_; }; class Argument { public: Argument(std::string long_opt, std::string short_opt); public: //Configuration Mutators //Sets the hlep text Argument& help(std::string help_str); //Sets the defuault value Argument& default_value(const std::string& default_val); Argument& default_value(const std::vector& default_val); Argument& default_value(const std::initializer_list& default_val); //Sets the action Argument& action(Action action); //Sets whether this argument is required Argument& required(bool is_required); //Sets the associated metavar (if not specified, inferred from argument name, or choices) Argument& metavar(std::string metavar_sr); //Sets the expected number of arguments Argument& nargs(char nargs_type); //Sets the valid choices for this option's value Argument& choices(std::vector choice_values); //Sets the group name this argument is associated with Argument& group_name(std::string grp); //Sets where this option appears in the help Argument& show_in(ShowIn show); public: //Option setting mutators //Sets the target value to the specified default virtual void set_dest_to_default() = 0; //Sets the target value to the specified value virtual void set_dest_to_value(std::string value) = 0; //Adds the specified value to the taget values virtual void add_value_to_dest(std::string value) = 0; //Set the target value to true virtual void set_dest_to_true() = 0; //Set the target value to false virtual void set_dest_to_false() = 0; virtual void reset_dest() = 0; public: //Accessors //Returns a discriptive name build from the long/short option std::string name() const; //Returns the long option name (or positional name) for this argument. //Note that this may be a single-letter option if only a short option name was specified std::string long_option() const; //Returns the short option name for this argument, note that this returns //the empty string if no short option is specified, or if only the short option //is specified. std::string short_option() const; //Returns the help description for this option std::string help() const; //Returns the number of arguments this option expects char nargs() const; //Returns the specified metavar for this option std::string metavar() const; //Returns the list of valid choices for this option std::vector choices() const; //Returns the action associated with this option Action action() const; //Returns whether this option is required bool required() const; //Returns the specified default value std::string default_value() const; //Returns the group name associated with this argument std::string group_name() const; //Indicates where this option should appear in the help ShowIn show_in() const; //Returns true if this is a positional argument bool positional() const; //Returns true if the default_value() was set bool default_set() const; //Returns true if the proposed value is legal virtual bool is_valid_value(std::string value) = 0; public: //Lifetime virtual ~Argument() {} Argument(const Argument&) = default; Argument(Argument&&) = default; Argument& operator=(const Argument&) = delete; Argument& operator=(const Argument&&) = delete; protected: virtual bool valid_action() = 0; std::vector default_value_; private: //Data std::string long_opt_; std::string short_opt_; std::string help_; std::string metavar_; char nargs_ = '1'; std::vector choices_; Action action_ = Action::STORE; bool required_ = false; std::string group_name_; ShowIn show_in_ = ShowIn::USAGE_AND_HELP; bool default_set_ = false; }; template class SingleValueArgument : public Argument { public: //Constructors SingleValueArgument(ArgValue& dest, std::string long_opt, std::string short_opt) : Argument(long_opt, short_opt) , dest_(dest) {} public: //Mutators void set_dest_to_default() override { dest_.set(Converter().from_str(default_value()), Provenance::DEFAULT); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void set_dest_to_value(std::string value) override { if (dest_.provenance() == Provenance::SPECIFIED && dest_.argument_name() == name()) { throw ArgParseError("Argument " + name() + " specified multiple times"); } dest_.set(Converter().from_str(value), Provenance::SPECIFIED); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void add_value_to_dest(std::string /*value*/) override { throw ArgParseError("Single value option can not have multiple values set"); } void set_dest_to_true() override { throw ArgParseError("Non-boolean destination can not be set true"); } void set_dest_to_false() override { throw ArgParseError("Non-boolean destination can not be set false"); } bool valid_action() override { //Sanity check that we aren't processing a boolean action with a non-boolean destination if (action() == Action::STORE_TRUE) { std::stringstream msg; msg << "Non-boolean destination can not have STORE_TRUE action (" << long_option() << ")"; throw ArgParseError(msg.str()); } else if (action() == Action::STORE_FALSE) { std::stringstream msg; msg << "Non-boolean destination can not have STORE_FALSE action (" << long_option() << ")"; throw ArgParseError(msg.str()); } else if (action() != Action::STORE) { throw ArgParseError("Unexpected action (expected STORE)"); } return true; } void reset_dest() override { dest_ = ArgValue(); } bool is_valid_value(std::string value) override { auto converted_value = Converter().from_str(value); if (!converted_value) { return false; } return is_valid_choice(value, choices()); } private: //Data ArgValue& dest_; }; //bool specialization for STORE_TRUE/STORE_FALSE template class SingleValueArgument : public Argument { public: //Constructors SingleValueArgument(ArgValue& dest, std::string long_opt, std::string short_opt) : Argument(long_opt, short_opt) , dest_(dest) {} public: //Mutators void set_dest_to_default() override { dest_.set(Converter().from_str(default_value()), Provenance::DEFAULT); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void add_value_to_dest(std::string /*value*/) override { throw ArgParseError("Single value option can not have multiple values set"); } void set_dest_to_value(std::string value) override { if (dest_.provenance() == Provenance::SPECIFIED && dest_.argument_name() == name()) { throw ArgParseError("Argument " + name() + " specified multiple times"); } dest_.set(Converter().from_str(value), Provenance::SPECIFIED); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void set_dest_to_true() override { ConvertedValue val; val.set_value(true); dest_.set(val, Provenance::SPECIFIED); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void set_dest_to_false() override { ConvertedValue val; val.set_value(false); dest_.set(val, Provenance::SPECIFIED); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } bool valid_action() override { //Any supported action is valid on a boolean destination return true; } void reset_dest() override { dest_ = ArgValue(); } bool is_valid_value(std::string value) override { auto converted_value = Converter().from_str(value); if (!converted_value) { return false; } return is_valid_choice(value, choices()); } private: //Data ArgValue& dest_; }; template class MultiValueArgument : public Argument { public: //Constructors MultiValueArgument(ArgValue& dest, std::string long_opt, std::string short_opt) : Argument(long_opt, short_opt) , dest_(dest) {} public: //Mutators void set_dest_to_default() override { auto& target = dest_.mutable_value(Provenance::DEFAULT); for (auto default_str : default_value_) { auto val = Converter().from_str(default_str); target.insert(std::end(target), val.value()); } dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void set_dest_to_value(std::string /*value*/) override { throw ArgParseError("Multi-value option can not be set to a single value"); } void add_value_to_dest(std::string value) override { if (dest_.provenance() == Provenance::SPECIFIED && dest_.argument_name() != name()) { throw ArgParseError("Argument destination already set by " + dest_.argument_name() + " (trying to set from " + name() + ")"); } auto previous_provenance = dest_.provenance(); auto& target = dest_.mutable_value(Provenance::SPECIFIED); if (previous_provenance == Provenance::DEFAULT) { target.clear(); } //Insert is more general than push_back auto converted_value = Converter().from_str(value); if (!converted_value) { throw ArgParseConversionError(converted_value.error()); } target.insert(std::end(target), converted_value.value()); dest_.set_argument_name(name()); dest_.set_argument_group(group_name()); } void set_dest_to_true() override { throw ArgParseError("Non-boolean destination can not be set true"); } void set_dest_to_false() override { throw ArgParseError("Non-boolean destination can not be set false"); } bool valid_action() override { //Sanity check that we aren't processing a boolean action with a non-boolean destination if (action() != Action::STORE) { throw ArgParseError("Unexpected action (expected STORE)"); } return true; } void reset_dest() override { dest_ = ArgValue(); } bool is_valid_value(std::string value) override { auto converted_value = Converter().from_str(value); if (!converted_value) { return false; } return is_valid_choice(value, choices()); } private: //Data ArgValue& dest_; }; } //namespace #include "argparse.tpp" #endif