518 lines
19 KiB
C++
518 lines
19 KiB
C++
|
#ifndef ARGPARSE_H
|
||
|
#define ARGPARSE_H
|
||
|
#include <iosfwd>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
#include <iostream>
|
||
|
#include <sstream>
|
||
|
#include <memory>
|
||
|
#include <map>
|
||
|
|
||
|
#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<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<T>& dest, std::string option);
|
||
|
|
||
|
//Adds an option with a long and short option name (single value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<T>& dest, std::string long_opt, std::string short_opt);
|
||
|
|
||
|
//Adds an argument or option with a single name (multi value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<std::vector<T>>& dest, std::string option);
|
||
|
|
||
|
//Adds an option with a long and short option name (multi value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<std::vector<T>>& 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<std::string> 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<ArgumentGroup> argument_groups() const;
|
||
|
|
||
|
private:
|
||
|
void add_help_option_if_unspecified();
|
||
|
|
||
|
struct ShortArgInfo {
|
||
|
bool is_no_space_short_arg = false;
|
||
|
std::shared_ptr<argparse::Argument> arg;
|
||
|
std::string value;
|
||
|
};
|
||
|
ShortArgInfo no_space_short_arg(std::string str, const std::map<std::string, std::shared_ptr<Argument>>& str_to_option_arg) const;
|
||
|
private:
|
||
|
std::string prog_;
|
||
|
std::string description_;
|
||
|
std::string epilog_;
|
||
|
std::string version_;
|
||
|
std::vector<ArgumentGroup> argument_groups_;
|
||
|
|
||
|
std::unique_ptr<Formatter> formatter_;
|
||
|
std::ostream& os_;
|
||
|
ArgValue<bool> 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<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<T>& dest, std::string option);
|
||
|
|
||
|
//Adds an option with a long and short option name (single value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<T>& dest, std::string long_opt, std::string short_opt);
|
||
|
|
||
|
//Adds an argument or option with a multi name (multi value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<std::vector<T>>& dest, std::string option);
|
||
|
|
||
|
//Adds an option with a long and short option name (multi value)
|
||
|
template<typename T, typename Converter=DefaultConverter<T>>
|
||
|
Argument& add_argument(ArgValue<std::vector<T>>& 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<std::shared_ptr<Argument>>& 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<std::shared_ptr<Argument>> 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<std::string>& default_val);
|
||
|
Argument& default_value(const std::initializer_list<std::string>& 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<std::string> 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<std::string> 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<std::string> default_value_;
|
||
|
private: //Data
|
||
|
std::string long_opt_;
|
||
|
std::string short_opt_;
|
||
|
|
||
|
std::string help_;
|
||
|
std::string metavar_;
|
||
|
char nargs_ = '1';
|
||
|
std::vector<std::string> choices_;
|
||
|
Action action_ = Action::STORE;
|
||
|
bool required_ = false;
|
||
|
|
||
|
|
||
|
std::string group_name_;
|
||
|
ShowIn show_in_ = ShowIn::USAGE_AND_HELP;
|
||
|
bool default_set_ = false;
|
||
|
};
|
||
|
|
||
|
template<typename T, typename Converter>
|
||
|
class SingleValueArgument : public Argument {
|
||
|
public: //Constructors
|
||
|
SingleValueArgument(ArgValue<T>& 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<T>();
|
||
|
}
|
||
|
|
||
|
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<T>& dest_;
|
||
|
};
|
||
|
|
||
|
//bool specialization for STORE_TRUE/STORE_FALSE
|
||
|
template<typename Converter>
|
||
|
class SingleValueArgument<bool,Converter> : public Argument {
|
||
|
public: //Constructors
|
||
|
SingleValueArgument(ArgValue<bool>& 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<bool> 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<bool> 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>();
|
||
|
}
|
||
|
|
||
|
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<bool>& dest_;
|
||
|
};
|
||
|
|
||
|
template<typename T, typename Converter>
|
||
|
class MultiValueArgument : public Argument {
|
||
|
public: //Constructors
|
||
|
MultiValueArgument(ArgValue<T>& 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<T>();
|
||
|
}
|
||
|
|
||
|
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<T>& dest_;
|
||
|
};
|
||
|
|
||
|
|
||
|
} //namespace
|
||
|
|
||
|
#include "argparse.tpp"
|
||
|
|
||
|
#endif
|