diff --git a/argh.h b/argh.h index 4007ba3..c4d9d18 100644 --- a/argh.h +++ b/argh.h @@ -5,274 +5,746 @@ #include #include #include +#include #include -class Option { +/// An abstract template class that represents an option. +/// +/// \tparam CharT is the data type of the characters used for the +/// option name and description. +template +class Option +{ + using string = std::basic_string; public: - Option() : parsed(false), required(false) {} - virtual ~Option() {}; - virtual std::string getDefault() = 0; - virtual std::string getMessage() = 0; - virtual std::string getName() = 0; - virtual void setValue(std::string const& val) = 0; + /// Constructs an empty option. + Option() + : m_parsed(false), m_required(false) + {} + + virtual ~Option() = default; + + /// Get a text version of the option's default value. + virtual string getDefault() = 0; + + /// Get a message describing the option. + virtual string getMessage() = 0; + + /// Get the option's name. + virtual string getName() = 0; + + /// Set the option's value. + /// + /// \param val is the value to assign. + virtual void setValue(string const& val) = 0; + + /// Was the option parsed, i.e., seen? + /// + /// \returns `true` if the option was parsed, `false` otherwise. + bool + getParsed() + { + return m_parsed; + } + + /// Is the option required? + /// + /// \returns `true` if the option is required, `false` otherwise. + bool + getRequired() + { + return m_required; + } - bool getParsed() { return parsed; } - bool getRequired() { return required; } - - virtual void setParsed(bool parsed) { this->parsed = parsed; } + /// Change the option's "was it parsed?"-status. + /// + /// \param parsed is the new status. + virtual void + setParsed(bool parsed) + { + m_parsed = parsed; + } protected: - bool parsed, required; + bool m_parsed; //!< was the option parsed (i.e., seen)? + bool m_required; //!< is the option required? }; -template -class OptionImpl : public Option { +/// A template class that represents an option with none or a single +/// value. +/// +/// \tparam T is the data type of the option value. +/// +/// \tparam CharT is the data type of the characters. +template +class OptionImpl + : public Option +{ + using string = std::basic_string; + using stringstream = std::basic_stringstream; public: - OptionImpl(T& var, T default_val, std::string const& name, bool required, std::string const& msg) : var(var) + /// Constructor. + /// + /// \param var is a reference to the variable that receives the + /// option's value. + /// + /// \param default_val is the option's default value. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is a description of the option. + OptionImpl(T& var, T default_val, string const& name, bool required, + string const& msg) + : m_var(var) + { + // TODO: refactor into initializer list + m_default_val = default_val; + m_name = name; + // MSVC 2022 requires m_required to be prefixed by this-> + this->m_required = required; + m_msg = msg; + m_var = default_val; + } + + virtual string + getDefault() { - this->default_val = default_val; - this->name = name; - this->required = required; - this->msg = msg; - this->var = default_val; - } + stringstream ss; + ss << m_default_val; + return ss.str(); + } - virtual std::string getDefault() { std::stringstream ss; ss << default_val; return ss.str(); } - std::string getName() { return name; } - std::string getMessage() { return msg; } - virtual void setValue(std::string const& val) { std::stringstream ss(val); ss >> var; } + string + getName() + { + return m_name; + } + + string + getMessage() + { + return m_msg; + } + + /// Converts the option's text value to the target type and writes + /// it to the configured variable. + /// + /// \param val is the text value representing the option's value. + virtual void + setValue(string const& val) + { + if constexpr (std::is_same_v) + { + // If the target is a string then we must not use the + // stringstream (i.e., the "else" clause of this if-statement) + // because that extracts only the text up to the first + // whitespace. + m_var = val; + } + else { + stringstream ss(val); + ss >> m_var; + } + } protected: - T default_val; - T& var; - std::string name; - std::string msg; + T m_default_val; //!< the option's default value + T& m_var; //!< a reference to the variable that receives the option's value + string m_name; //!< the option's name + string m_msg; //!< the option's description }; -class OptionStringImpl : public OptionImpl +/// A template class that represents a text option. +/// +/// \tparam CharT is the data type of the characters. +template +class OptionStringImpl + : public OptionImpl, CharT> { + using string = std::basic_string; + using stringstream = std::basic_stringstream; public: - OptionStringImpl(std::string& var, std::string const& default_val, std::string const& name, bool required, std::string const& msg) : - OptionImpl(var, default_val, name, required, msg) + OptionStringImpl(string& var, string const& default_val, string const& name, + bool required, string const& msg) + : OptionImpl(var, default_val, name, required, msg) {} - std::string getDefault() { std::stringstream ss; ss << "\"" << default_val << "\""; return ss.str(); } - void setValue(std::string const& val) { var = val; } + string + getDefault() + { + stringstream ss; + // MSVC 2022 doesn't accept m_default_val without this-> before it + // in the next statement + ss << "\"" << this->m_default_val << "\""; + return ss.str(); + } + + void + setValue(string const& val) + { + // MSVC 2022 doesn't accept m_var without this-> before it in the + // next statement + this->m_var = val; + } }; -template -class MultiOptionImpl : public Option +/// A template class that represents an option with multiple values. +/// +/// \tparam T is the data type of the option values. +/// +/// \tparam CharT is the data type of the characters. +template +class MultiOptionImpl + : public Option { + using string = std::basic_string; + using stringstream = std::basic_stringstream; public: - MultiOptionImpl(std::vector& var, std::string const& default_vals, std::string const& name, bool required, std::string const& msg, char delim) : var(var) + + /// Constructor. + /// + /// \param var is a reference to the vector where the converted + /// option values get written. + /// + /// \param default_vals is the default value for the option values. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. + /// + /// \param delim is the delimiter that separates the option values + /// in the text. + MultiOptionImpl(std::vector& var, string const& default_vals, + string const& name, bool required, + string const& msg, CharT delim) + : m_var(var) { - this->default_vals = default_vals; - this->name = name; - this->required = required; - this->msg = msg; - this->delim = delim; + // TODO: refactor into initializer list + m_default_vals = default_vals; + m_name = name; + // MSVC 2022 requires m_required to be prefixed by this-> + this->m_required = required; + m_msg = msg; + m_delim = delim; setValue(default_vals); } - std::string getDefault() + string + getDefault() { - std::stringstream ss; + stringstream ss; ss << "\""; - ss << default_vals; + ss << m_default_vals; ss << "\""; return ss.str(); } - std::string getName() { return name; } - std::string getMessage() { return msg; } - virtual void setValue(std::string const& val) { - var.clear(); - std::stringstream ss(val); - T elem; - for (std::string val_str; std::getline(ss, val_str, delim);) { - std::stringstream st(val_str); - st >> elem; - var.push_back(elem); + string + getName() + { + return m_name; + } + + string + getMessage() + { + return m_msg; + } + + /// Converts the option's text value to zero or more values of the + /// target type and writes them to the configured vector. + /// + /// \param val is the text value representing the option's value or + /// values. + virtual void + setValue(string const& val) + { + m_var.clear(); + stringstream ss(val); + if constexpr (std::is_same_v) + { + // If the target is a vector of strings then we must not use + // stringstream st (i.e., the "else" clause of this + // if-statement) because that extracts only the text up to the + // first whitespace. + for (string val_str; std::getline(ss, val_str, m_delim);) + { + m_var.push_back(val_str); + } + } + else + { + T elem; + for (string val_str; std::getline(ss, val_str, m_delim);) + { + stringstream st(val_str); + st >> elem; + m_var.push_back(elem); + } } } protected: - std::string default_vals; - std::vector& var; - std::string name; - std::string msg; - char delim; + string m_default_vals; //!< the option's default value or values + std::vector& m_var; //!< a reference to the vector that receives + //!< the values + string m_name; //!< the option's name + string m_msg; //!< the option's description + CharT m_delim; //!< the delimiter for the option values }; -class MultiOptionStringImpl : public MultiOptionImpl +/// A template class that represents an option with multiple text +/// values. +/// +/// \tparam CharT is the data type of the characters. +template +class MultiOptionStringImpl + : public MultiOptionImpl, CharT> { + using string = std::basic_string; + using stringstream = std::basic_stringstream; public: - MultiOptionStringImpl(std::vector& var, std::string const& default_vals, std::string const& name, bool required, std::string const& msg, char delim) : - MultiOptionImpl(var, default_vals, name, required, msg, delim) + /// Constructor. + /// + /// \param var is a reference to the vector where the converted + /// option values get written. + /// + /// \param default_vals is the default value for the option values. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. + /// + /// \param delim is the delimiter that separates the option values + /// in the text. + MultiOptionStringImpl(std::vector& var, + string const& default_vals, + string const& name, bool required, + string const& msg, CharT delim) + : MultiOptionImpl(var, default_vals, name, required, msg, delim) {} - void setValue(std::string const& val) { - var.clear(); - std::stringstream ss(val); - for (std::string val_str; std::getline(ss, val_str, delim);) { - var.push_back(val_str); + /// Splits the option's text value into zero or more individual text + /// values and writes them to the configured vector. + /// + /// \param val is the text value representing the option's value or + /// values. + void + setValue(string const& val) + { + // MSVC 2022 does not accept m_var and m_delim without this-> + // before them. + this->m_var.clear(); + stringstream ss(val); + for (string val_str; std::getline(ss, val_str, this->m_delim);) + { + this->m_var.push_back(val_str); } } }; -class FlagImpl : public Option { +/// A template class that represents a flag, an option without a +/// value. +template +class FlagImpl + : public Option +{ + using string = std::basic_string; public: - FlagImpl(bool& flag, std::string const& name, std::string const& msg) : - flag(flag), - name(name), - msg(msg) + /// Constructor + /// + /// \param flag is a reference to the boolean variable that gets set + /// to `true` when the option is seen. + /// + /// \param name is the option's name. + /// + /// \param msg is the option's description. + FlagImpl(bool& flag, string const& name, string const& msg) + : m_flag(flag), m_name(name), m_msg(msg) + { + m_flag = false; // false by default, changes to true when the option is seen + } + + string + getDefault() + { + return {}; + } + + string + getName() + { + return m_name; + } + + string + getMessage() { - flag = false; + return m_msg; } - std::string getDefault() { return ""; } - std::string getName() { return name; } - std::string getMessage() { return msg; } - void setParsed(bool parsed) { Option::setParsed(parsed); flag = parsed; } - void setValue(std::string const& val) {} + /// Changes the "was it seen?" status of the option. + /// + /// \param parsed says whether the option was seen. + void + setParsed(bool parsed) + { + Option::setParsed(parsed); + m_flag = parsed; + } + + /// Implementation required to placate the compiler. It does + /// nothing and is never called. + void + setValue(string const& val) + {} protected: - bool& flag; - std::string name; - std::string msg; + bool& m_flag; //!< a reference to the variable that receives the + //!< flag's status + string m_name; //!< the option's name + string m_msg; //!< the option's description }; -class Argh { +/// A template class to parse command line options. +/// +/// \tparam CharT is the data type of the characters. +template +class Argh +{ + using string = std::basic_string; + using stringstream = std::basic_stringstream; public: - Argh(char delim = ',') : delim(delim) {} - ~Argh() { for (auto o : options) { delete o; } options.clear(); } + /// Constructor. + /// + /// \param delim is the delimiter that separates the text + /// representing multiple values. + Argh(CharT delim = ',') + : m_delim(delim) + {} - void parse(int argc, char const* argv[]) { - for (int i = 0; i < argc; ++i) { - for (auto o : options) { - if (std::string(argv[i]) == o->getName()) { + ~Argh() + { + for (auto o : m_options) + { + delete o; + } + m_options.clear(); + } + + /// Parse the command line arguments. + /// + /// \param argc is the count of command line arguments. + /// + /// \param argv points at the first of the command line arguments. + void + parse(int argc, CharT const* const argv[]) + { + std::vector is_option(argc); + for (int i = 0; i < argc; ++i) + { + for (auto o : m_options) + { + if (string(argv[i]) == o->getName()) + { o->setParsed(true); - if (i + 1 < argc) { + is_option[i] = true; + if (i + 1 < argc) + { o->setValue(argv[i + 1]); + is_option[i + 1] = true; } } } } + for (int i = 0; i < argc; ++i) + { + if (!is_option[i]) + m_remaining_args.push_back(argv[i]); + } } - - void parseEnv() { - for (auto o : options) { - auto str = getenv(o->getName().c_str()); - if (str) { - o->setParsed(true); - o->setValue(str); - } - } - } + /// Get a reference to a vector containing the command line + /// arguments (from the argv passed to parse()) that remain when the + /// recognized options are removed. That vector is filled by + /// parse() so remains empty until parse() is called. + /// + /// \returns the reference + const std::vector& + getRemainingArguments() const + { + return m_remaining_args; + } + + /// Seek the configured options in the process environment. Each + /// option's name is sought in the process environment. If an + /// environment variable has the name of an option then that + /// option's value is set on the environment variable's value. + void + parseEnv() + { + for (auto o : m_options) + { + auto str = getenv(o->getName().c_str()); + if (str) + { + o->setParsed(true); + o->setValue(str); + } + } + } + + /// Define an option. + /// + /// \param var is a reference to the variable that receives the + /// option's value. + /// + /// \param default_val is the text representation of the option's + /// default value. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. template - void addOption(T& var, T const& default_val, std::string const& name, bool required = false, std::string const& msg = "") { - options.push_back(new OptionImpl(var, default_val, name, required, msg)); + void + addOption(T& var, T const& default_val, string const& name, + bool required = false, string const& msg = {}) + { + m_options.push_back(new OptionImpl(var, default_val, name, + required, msg)); } - void addOption(std::string& var, std::string const& default_val, std::string const& name, bool required = false, std::string const& msg = "") { - options.push_back(new OptionStringImpl(var, default_val, name, required, msg)); + /// Define a text option. + /// + /// \param var is a reference to the variable that receives the + /// option's value. + /// + /// \param default_val is the option's default value. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. + void + addOption(string& var, string const& default_val, + string const& name, bool required = false, + string const& msg = {}) + { + m_options.push_back(new OptionStringImpl(var, default_val, name, + required, msg)); } + /// Define a multi-value option. + /// + /// \param var is a reference to the vector that receives the + /// option's values. + /// + /// \param default_vals is the text representation of the option's + /// default values. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. template - void addMultiOption(std::vector& var, std::string const& default_vals, std::string const& name, bool required = false, std::string const& msg = "") { - options.push_back(new MultiOptionImpl(var, default_vals, name, required, msg, delim)); + void + addMultiOption(std::vector& var, string const& default_vals, + string const& name, bool required = false, + string const& msg = {}) + { + m_options.push_back(new MultiOptionImpl(var, default_vals, name, + required, msg, m_delim)); } - void addMultiOption(std::vector& var, std::string const& default_vals, std::string const& name, bool required = false, std::string const& msg = "") { - options.push_back(new MultiOptionStringImpl(var, default_vals, name, required, msg, delim)); + /// Define a multi-value text option. + /// + /// \param var is a reference to the vector that receives the + /// option's values. + /// + /// \param default_vals is the text representation of the option's + /// default values. + /// + /// \param name is the option's name. + /// + /// \param required says whether the option is required. + /// + /// \param msg is the option's description. + void + addMultiOption(std::vector& var, + string const& default_vals, + string const& name, bool required = false, + string const& msg = "") + { + m_options.push_back(new MultiOptionStringImpl(var, default_vals, + name, required, msg, + m_delim)); } - void addFlag(bool& flag, std::string const& name, std::string const& msg = "") { - options.push_back(new FlagImpl(flag, name, msg)); + /// Define a flag, an option without a value. + /// + /// \param flag is a reference to the variable that receives the + /// option's status. + /// + /// \param name is the option's name. + /// + /// \param msg is the option's description. + void + addFlag(bool& flag, string const& name, string const& msg = {}) + { + m_options.push_back(new FlagImpl(flag, name, msg)); } - std::string getUsage() { + /// Get text that describes the options. + /// + /// \returns the text. + string + getUsage() + { size_t name_space = getLongestName() + 1; size_t default_space = getLongestDefault() + 1; size_t msg_space = getLongestMessage() + 1; - std::stringstream ret; - ret << std::left; - for (auto o : options) { + stringstream ret; + ret << std::left + << std::setw(static_cast(name_space)) << "Option" + << std::setw(static_cast(default_space)) << "Default" + << std::setw(static_cast(msg_space)) << "Description" + << "Required\n"; + for (auto o : m_options) + { ret << std::setw(static_cast(name_space)) << o->getName() << std::setw(static_cast(default_space)) << o->getDefault() << std::setw(static_cast(msg_space)) << o->getMessage() - << (o->getRequired() ? "REQUIRED" : "NOT REQUIRED") - << std::endl; + << (o->getRequired() ? "Yes" : "No") + << "\n"; } return ret.str(); } - bool isParsed(std::string const& name) { - for (auto o : options) { - if (name == o->getName() && o->getParsed()) { + /// Was the specified option seen? + /// + /// \param name is the name of the option to query. + /// + /// \returns `true` if the option was seen, `false` otherwise. + bool + isParsed(string const& name) + { + for (auto o : m_options) + { + if (name == o->getName() && o->getParsed()) + { return true; } } return false; } - - std::vector missingRequired() { - std::vector missing; - for (auto o : options) { + + /// Which required options were not seen? + /// + /// \returns a vector containing the names of the options that + /// weren't seen. + std::vector + missingRequired() + { + std::vector missing; + for (auto o : m_options) + { if (o->getRequired() && !o->getParsed()) - missing.push_back(o->getName()); + missing.push_back(o->getName()); } - return missing; - } + return missing; + } - bool load(std::string const& filename) { + /// Load the options from a file. + /// + /// \param filename is the name of the file to read. + /// + /// \returns `true` for success, `false` for failure. + bool + load(string const& filename) + { std::ifstream ifs(filename); - if (!ifs.good()) { return false; } + if (!ifs.good()) + { + return false; + } int argc = 0; - std::vector argv_str; - std::vector argv; - std::string arg; - while (std::getline(ifs, arg)) { + std::vector argv_str; + std::vector argv; + string arg; + while (std::getline(ifs, arg)) + { argv_str.push_back(arg); ++argc; } - for (int i = 0; i < argc; ++i) { + for (int i = 0; i < argc; ++i) + { argv.push_back(argv_str[i].c_str()); } parse(argc, &*argv.begin()); - return true; + return true; } protected: - size_t getLongestName() { + /// Get the greatest length of any of the option names. + /// + /// \returns the greatest length, or 0 if there are no options. + size_t + getLongestName() + { size_t ret = 0; - for (auto o : options) { + for (auto o : m_options) + { ret = std::max(ret, o->getName().length()); } return ret; } - size_t getLongestDefault() { + /// Get the greatest length of any of the options' default values + /// (converted to text). + /// + /// \returns the greatest length, or 0 if there are no options. + size_t + getLongestDefault() + { size_t ret = 0; - for (auto o : options) { + for (auto o : m_options) + { ret = std::max(ret, o->getDefault().length()); } return ret; } - size_t getLongestMessage() { + /// Get the greatest length of any of the options' descriptions. + /// + /// \returns the greatest length, or 0 if there are no options. + size_t + getLongestMessage() + { size_t ret = 0; - for (auto o : options) { + for (auto o : m_options) + { ret = std::max(ret, o->getMessage().length()); } return ret; } - std::vector options; - char delim; + std::vector*> m_options; //!< the options + CharT m_delim; //!< the delimiter + std::vector m_remaining_args; //!< non-option arguments };