From 25cfbf1a0f9e2697a84b58d11664f7f42c4eb646 Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 08:43:21 +0100 Subject: [PATCH 1/6] Replace tabs with spaces, remove trailing whitespace --- argh.h | 66 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/argh.h b/argh.h index 4007ba3..4b8c8fa 100644 --- a/argh.h +++ b/argh.h @@ -10,16 +10,16 @@ class Option { public: Option() : parsed(false), required(false) {} - virtual ~Option() {}; + virtual ~Option() {}; virtual std::string getDefault() = 0; - virtual std::string getMessage() = 0; + virtual std::string getMessage() = 0; virtual std::string getName() = 0; virtual void setValue(std::string const& val) = 0; bool getParsed() { return parsed; } - bool getRequired() { return required; } - + bool getRequired() { return required; } + virtual void setParsed(bool parsed) { this->parsed = parsed; } protected: @@ -31,12 +31,12 @@ class OptionImpl : public Option { public: OptionImpl(T& var, T default_val, std::string const& name, bool required, std::string const& msg) : var(var) { - this->default_val = default_val; - this->name = name; - this->required = required; - this->msg = msg; - this->var = default_val; - } + this->default_val = default_val; + this->name = name; + this->required = required; + this->msg = msg; + this->var = default_val; + } virtual std::string getDefault() { std::stringstream ss; ss << default_val; return ss.str(); } std::string getName() { return name; } @@ -67,11 +67,11 @@ class MultiOptionImpl : public Option public: MultiOptionImpl(std::vector& var, std::string const& default_vals, std::string const& name, bool required, std::string const& msg, char delim) : var(var) { - this->default_vals = default_vals; - this->name = name; - this->required = required; - this->msg = msg; - this->delim = delim; + this->default_vals = default_vals; + this->name = name; + this->required = required; + this->msg = msg; + this->delim = delim; setValue(default_vals); } @@ -160,16 +160,16 @@ class Argh { } } } - - void parseEnv() { - for (auto o : options) { - auto str = getenv(o->getName().c_str()); - if (str) { - o->setParsed(true); - o->setValue(str); - } - } - } + + void parseEnv() { + for (auto o : options) { + auto str = getenv(o->getName().c_str()); + if (str) { + o->setParsed(true); + o->setValue(str); + } + } + } template void addOption(T& var, T const& default_val, std::string const& name, bool required = false, std::string const& msg = "") { @@ -205,7 +205,7 @@ class Argh { << 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") + << (o->getRequired() ? "REQUIRED" : "NOT REQUIRED") << std::endl; } return ret.str(); @@ -219,15 +219,15 @@ class Argh { } return false; } - - std::vector missingRequired() { - std::vector missing; + + std::vector missingRequired() { + std::vector missing; for (auto o : 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) { std::ifstream ifs(filename); @@ -244,7 +244,7 @@ class Argh { argv.push_back(argv_str[i].c_str()); } parse(argc, &*argv.begin()); - return true; + return true; } protected: From deb8daabdf8867f6a282b1199ec95dbae80552c0 Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 08:59:11 +0100 Subject: [PATCH 2/6] Prefix "m_" to class fields so no parameters and fields have the same name --- argh.h | 380 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 276 insertions(+), 104 deletions(-) diff --git a/argh.h b/argh.h index 4b8c8fa..80ea36e 100644 --- a/argh.h +++ b/argh.h @@ -7,151 +7,272 @@ #include #include -class Option { +class Option +{ public: - Option() : parsed(false), required(false) {} - virtual ~Option() {}; + Option() + : m_parsed(false), m_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; - bool getParsed() { return parsed; } - bool getRequired() { return required; } + bool + getParsed() + { + return m_parsed; + } - virtual void setParsed(bool parsed) { this->parsed = parsed; } + bool + getRequired() + { + return m_required; + } + + virtual void + setParsed(bool parsed) + { + m_parsed = parsed; + } protected: - bool parsed, required; + bool m_parsed; + bool m_required; }; template -class OptionImpl : public Option { +class OptionImpl + : public Option +{ public: - OptionImpl(T& var, T default_val, std::string const& name, bool required, std::string const& msg) : var(var) + OptionImpl(T& var, T default_val, std::string const& name, + bool required, std::string const& msg) + : m_var(var) { - this->default_val = default_val; - this->name = name; - this->required = required; - this->msg = msg; - this->var = default_val; + this->m_default_val = default_val; + this->m_name = name; + this->m_required = required; + this->m_msg = msg; + this->m_var = default_val; } - 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; } + virtual std::string + getDefault() + { + std::stringstream ss; + ss << m_default_val; + return ss.str(); + } + + std::string + getName() + { + return m_name; + } + + std::string + getMessage() + { + return m_msg; + } + + virtual void + setValue(std::string const& val) + { + std::stringstream ss(val); + ss >> m_var; + } protected: - T default_val; - T& var; - std::string name; - std::string msg; + T m_default_val; + T& m_var; + std::string m_name; + std::string m_msg; }; -class OptionStringImpl : public OptionImpl +class OptionStringImpl + : public OptionImpl { 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(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) {} - std::string getDefault() { std::stringstream ss; ss << "\"" << default_val << "\""; return ss.str(); } - void setValue(std::string const& val) { var = val; } + std::string + getDefault() + { + std::stringstream ss; + ss << "\"" << m_default_val << "\""; + return ss.str(); + } + + void + setValue(std::string const& val) + { + m_var = val; + } }; template class MultiOptionImpl : public Option { public: - MultiOptionImpl(std::vector& var, std::string const& default_vals, std::string const& name, bool required, std::string const& msg, char delim) : var(var) + MultiOptionImpl(std::vector& var, + std::string const& default_vals, + std::string const& name, bool required, + std::string const& msg, char delim) + : m_var(var) { - this->default_vals = default_vals; - this->name = name; - this->required = required; - this->msg = msg; - this->delim = delim; + this->m_default_vals = default_vals; + this->m_name = name; + this->m_required = required; + this->m_msg = msg; + this->m_delim = delim; setValue(default_vals); } - std::string getDefault() + std::string + getDefault() { std::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::string + getName() + { + return m_name; + } + + std::string + getMessage() + { + return m_msg; + } + + virtual void + setValue(std::string const& val) + { + m_var.clear(); std::stringstream ss(val); T elem; - for (std::string val_str; std::getline(ss, val_str, delim);) { + for (std::string val_str; std::getline(ss, val_str, m_delim);) + { std::stringstream st(val_str); st >> elem; - var.push_back(elem); + m_var.push_back(elem); } } protected: - std::string default_vals; - std::vector& var; - std::string name; - std::string msg; - char delim; + std::string m_default_vals; + std::vector& m_var; + std::string m_name; + std::string m_msg; + char m_delim; }; -class MultiOptionStringImpl : public MultiOptionImpl +class MultiOptionStringImpl + : public MultiOptionImpl { 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) + 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) {} - void setValue(std::string const& val) { - var.clear(); + void + setValue(std::string const& val) + { + m_var.clear(); std::stringstream ss(val); - for (std::string val_str; std::getline(ss, val_str, delim);) { - var.push_back(val_str); + for (std::string val_str; std::getline(ss, val_str, m_delim);) { + m_var.push_back(val_str); } } }; -class FlagImpl : public Option { +class FlagImpl + : public Option +{ public: - FlagImpl(bool& flag, std::string const& name, std::string const& msg) : - flag(flag), - name(name), - msg(msg) + FlagImpl(bool& flag, std::string const& name, std::string const& msg) + : m_flag(flag), m_name(name), m_msg(msg) { - flag = false; + m_flag = false; } - 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) {} + std::string + getDefault() + { + return ""; + } + + std::string + getName() + { + return m_name; + } + + std::string + getMessage() + { + return m_msg; + } + + void + setParsed(bool parsed) + { + Option::setParsed(parsed); + m_flag = parsed; + } + + void + setValue(std::string const& val) + {} protected: - bool& flag; - std::string name; - std::string msg; + bool& m_flag; + std::string m_name; + std::string m_msg; }; class Argh { public: - Argh(char delim = ',') : delim(delim) {} - ~Argh() { for (auto o : options) { delete o; } options.clear(); } + Argh(char 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(); + } + + void + parse(int argc, char const* argv[]) + { + for (int i = 0; i < argc; ++i) + { + for (auto o : m_options) + { + if (std::string(argv[i]) == o->getName()) + { o->setParsed(true); if (i + 1 < argc) { o->setValue(argv[i + 1]); @@ -161,8 +282,11 @@ class Argh { } } - void parseEnv() { - for (auto o : options) { + void + parseEnv() + { + for (auto o : m_options) + { auto str = getenv(o->getName().c_str()); if (str) { o->setParsed(true); @@ -172,35 +296,60 @@ class Argh { } 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, std::string const& name, + bool required = false, std::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)); + void + addOption(std::string& var, std::string const& default_val, + std::string const& name, bool required = false, + std::string const& msg = "") + { + m_options.push_back(new OptionStringImpl(var, default_val, name, + required, msg)); } 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, std::string const& default_vals, + std::string const& name, bool required = false, + std::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)); + void + addMultiOption(std::vector& var, + std::string const& default_vals, + std::string const& name, bool required = false, + std::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)); + void + addFlag(bool& flag, std::string const& name, std::string const& msg = "") + { + m_options.push_back(new FlagImpl(flag, name, msg)); } - std::string getUsage() { + std::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) { + for (auto o : m_options) + { ret << std::setw(static_cast(name_space)) << o->getName() << std::setw(static_cast(default_space)) << o->getDefault() @@ -211,36 +360,50 @@ class Argh { return ret.str(); } - bool isParsed(std::string const& name) { - for (auto o : options) { - if (name == o->getName() && o->getParsed()) { + bool + isParsed(std::string const& name) + { + for (auto o : m_options) + { + if (name == o->getName() && o->getParsed()) + { return true; } } return false; } - std::vector missingRequired() { + std::vector + missingRequired() + { std::vector missing; - for (auto o : options) { + for (auto o : m_options) + { if (o->getRequired() && !o->getParsed()) missing.push_back(o->getName()); } return missing; } - bool load(std::string const& filename) { + bool + load(std::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)) { + 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()); @@ -249,30 +412,39 @@ class Argh { protected: - size_t getLongestName() { + 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() { + 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() { + 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; + char m_delim; }; From f868e7c8f367fdc9e26053c4bc2452bae83f1136 Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 09:07:38 +0100 Subject: [PATCH 3/6] templatize on the type of character so we can use the code also for wide characters --- argh.h | 484 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 367 insertions(+), 117 deletions(-) diff --git a/argh.h b/argh.h index 80ea36e..c80911d 100644 --- a/argh.h +++ b/argh.h @@ -7,33 +7,58 @@ #include #include +/// 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: + + /// Constructs an empty option. Option() : m_parsed(false), m_required(false) {} - virtual ~Option() - {} + 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; - virtual std::string getDefault() = 0; - virtual std::string getMessage() = 0; - virtual std::string getName() = 0; - virtual void setValue(std::string const& val) = 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; } + /// Change the option's "was it parsed?"-status. + /// + /// \param parsed is the new status. virtual void setParsed(bool parsed) { @@ -41,217 +66,344 @@ class Option } protected: - bool m_parsed; - bool m_required; + bool m_parsed; //!< was the option parsed (i.e., seen)? + bool m_required; //!< is the option required? }; -template +/// 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 + : 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) + /// 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) { - this->m_default_val = default_val; - this->m_name = name; + // 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; - this->m_msg = msg; - this->m_var = default_val; + m_msg = msg; + m_var = default_val; } - virtual std::string + virtual string getDefault() { - std::stringstream ss; + stringstream ss; ss << m_default_val; return ss.str(); } - std::string + string getName() { return m_name; } - std::string + 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(std::string const& val) + setValue(string const& val) { - std::stringstream ss(val); + stringstream ss(val); ss >> m_var; } protected: - T m_default_val; - T& m_var; - std::string m_name; - std::string m_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 }; +/// A template class that represents a text option. +/// +/// \tparam CharT is the data type of the characters. +template class OptionStringImpl - : public OptionImpl + : 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) + OptionStringImpl(string& var, string const& default_val, string const& name, + bool required, string const& msg) : OptionImpl(var, default_val, name, required, msg) {} - std::string + string getDefault() { - std::stringstream ss; - ss << "\"" << m_default_val << "\""; + 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(std::string const& val) + setValue(string const& val) { - m_var = 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) + + /// 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->m_default_vals = default_vals; - this->m_name = name; + // 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; - this->m_msg = msg; - this->m_delim = delim; + m_msg = msg; + m_delim = delim; setValue(default_vals); } - std::string + string getDefault() { - std::stringstream ss; + stringstream ss; ss << "\""; ss << m_default_vals; ss << "\""; return ss.str(); } - std::string + string getName() { return m_name; } - std::string + 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(std::string const& val) + setValue(string const& val) { m_var.clear(); - std::stringstream ss(val); + stringstream ss(val); T elem; - for (std::string val_str; std::getline(ss, val_str, m_delim);) + for (string val_str; std::getline(ss, val_str, m_delim);) { - std::stringstream st(val_str); + stringstream st(val_str); st >> elem; m_var.push_back(elem); } } protected: - std::string m_default_vals; - std::vector& m_var; - std::string m_name; - std::string m_msg; - char m_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 }; +/// 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 + : 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) + /// 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) {} + /// 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(std::string const& val) + setValue(string const& val) { - m_var.clear(); - std::stringstream ss(val); - for (std::string val_str; std::getline(ss, val_str, m_delim);) { - m_var.push_back(val_str); + // 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); } } }; +/// A template class that represents a flag, an option without a +/// value. +template class FlagImpl - : public Option + : public Option { + using string = std::basic_string; public: - FlagImpl(bool& flag, std::string const& name, std::string const& 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; + m_flag = false; // false by default, changes to true when the option is seen } - std::string + string getDefault() { - return ""; + return {}; } - std::string + string getName() { return m_name; } - std::string + string getMessage() { return m_msg; } + /// Changes the "was it seen?" status of the option. + /// + /// \param parsed says whether the option was seen. void setParsed(bool parsed) { - Option::setParsed(parsed); + Option::setParsed(parsed); m_flag = parsed; } + /// Implementation required to placate the compiler. It does + /// nothing and is never called. void - setValue(std::string const& val) + setValue(string const& val) {} protected: - bool& m_flag; - std::string m_name; - std::string m_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 = ',') + /// Constructor. + /// + /// \param delim is the delimiter that separates the text + /// representing multiple values. + Argh(CharT delim = ',') : m_delim(delim) {} @@ -264,17 +416,23 @@ class Argh { 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, char const* argv[]) + parse(int argc, CharT const* const argv[]) { for (int i = 0; i < argc; ++i) { for (auto o : m_options) { - if (std::string(argv[i]) == o->getName()) + if (string(argv[i]) == o->getName()) { o->setParsed(true); - if (i + 1 < argc) { + if (i + 1 < argc) + { o->setValue(argv[i + 1]); } } @@ -282,71 +440,139 @@ class Argh { } } + /// 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) { + 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 = "") - { - m_options.push_back(new OptionImpl(var, default_val, name, - required, msg)); - } - + 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)); + } + + /// 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(std::string& var, std::string const& default_val, - std::string const& name, bool required = false, - std::string const& msg = "") + 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 = "") + 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)); } + /// 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, - std::string const& default_vals, - std::string const& name, bool required = false, - std::string const& msg = "") + 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)); } + /// 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, std::string const& name, std::string const& msg = "") + addFlag(bool& flag, string const& name, string const& msg = {}) { - m_options.push_back(new FlagImpl(flag, name, 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; + stringstream ret; ret << std::left; for (auto o : m_options) { @@ -360,8 +586,13 @@ class Argh { return ret.str(); } + /// 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(std::string const& name) + isParsed(string const& name) { for (auto o : m_options) { @@ -373,10 +604,14 @@ class Argh { return false; } - std::vector + /// 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; + std::vector missing; for (auto o : m_options) { if (o->getRequired() && !o->getParsed()) @@ -385,8 +620,13 @@ class Argh { return missing; } + /// 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(std::string const& filename) + load(string const& filename) { std::ifstream ifs(filename); if (!ifs.good()) @@ -394,9 +634,9 @@ class Argh { return false; } int argc = 0; - std::vector argv_str; - std::vector argv; - std::string arg; + std::vector argv_str; + std::vector argv; + string arg; while (std::getline(ifs, arg)) { argv_str.push_back(arg); @@ -412,6 +652,9 @@ class Argh { protected: + /// Get the greatest length of any of the option names. + /// + /// \returns the greatest length, or 0 if there are no options. size_t getLongestName() { @@ -423,6 +666,10 @@ class Argh { return ret; } + /// 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() { @@ -434,6 +681,9 @@ class Argh { return ret; } + /// Get the greatest length of any of the options' descriptions. + /// + /// \returns the greatest length, or 0 if there are no options. size_t getLongestMessage() { @@ -445,6 +695,6 @@ class Argh { return ret; } - std::vector m_options; - char m_delim; + std::vector*> m_options; //!< the options + CharT m_delim; //!< the delimiter }; From 4f8624281900ccbf26d039cc8a9e10024061ddb9 Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 13:51:13 +0100 Subject: [PATCH 4/6] Support whitespace in text-based options --- argh.h | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/argh.h b/argh.h index c80911d..846c4dd 100644 --- a/argh.h +++ b/argh.h @@ -5,6 +5,7 @@ #include #include #include +#include #include /// An abstract template class that represents an option. @@ -136,8 +137,18 @@ class OptionImpl virtual void setValue(string const& val) { - stringstream ss(val); - ss >> m_var; + 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: @@ -257,12 +268,26 @@ class MultiOptionImpl { m_var.clear(); stringstream ss(val); - T elem; - for (string val_str; std::getline(ss, val_str, m_delim);) + 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 { - stringstream st(val_str); - st >> elem; - m_var.push_back(elem); + T elem; + for (string val_str; std::getline(ss, val_str, m_delim);) + { + stringstream st(val_str); + st >> elem; + m_var.push_back(elem); + } } } From 2fd7103f4d5c3c829aacc393cab9b04f7b336dc4 Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 13:51:55 +0100 Subject: [PATCH 5/6] Make the non-option arguments available conveniently (after parsing) --- argh.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/argh.h b/argh.h index 846c4dd..1e7110f 100644 --- a/argh.h +++ b/argh.h @@ -449,6 +449,7 @@ class Argh 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) @@ -456,13 +457,32 @@ class Argh if (string(argv[i]) == o->getName()) { o->setParsed(true); + 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]); + } + } + + /// 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 @@ -722,4 +742,5 @@ class Argh std::vector*> m_options; //!< the options CharT m_delim; //!< the delimiter + std::vector m_remaining_args; //!< non-option arguments }; From 0e14d646c9683881f267dd5dbbd22b1e83dd68ad Mon Sep 17 00:00:00 2001 From: Louis Strous Date: Tue, 7 Nov 2023 13:52:17 +0100 Subject: [PATCH 6/6] Provide header line for usage text --- argh.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/argh.h b/argh.h index 1e7110f..c4d9d18 100644 --- a/argh.h +++ b/argh.h @@ -618,15 +618,19 @@ class Argh size_t msg_space = getLongestMessage() + 1; stringstream ret; - ret << std::left; + 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(); }