| // This file is copied from https://github.com/adishavit/argh v1.3.1 release. | |
| // | |
| // Copyright (c) 2016, Adi Shavit | |
| // All rights reserved. | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // * Redistributions of source code must retain the above copyright notice, | |
| // this list of conditions and the following disclaimer. | |
| // * Redistributions in binary form must reproduce the above copyright | |
| // notice, this list of conditions and the following disclaimer in the | |
| // documentation and/or other materials provided with the distribution. | |
| // * Neither the name of nor the names of its contributors may be used to | |
| // endorse or promote products derived from this software without specific | |
| // prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| // POSSIBILITY OF SUCH DAMAGE. | |
| #pragma once | |
| #include <algorithm> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include <set> | |
| #include <map> | |
| #include <cassert> | |
| namespace argh { | |
| // Terminology: | |
| // A command line is composed of 2 types of args: | |
| // 1. Positional args, i.e. free standing values | |
| // 2. Options: args beginning with '-'. We identify two kinds: | |
| // 2.1: Flags: boolean options => (exist ? true : false) | |
| // 2.2: Parameters: a name followed by a non-option value | |
| #if !defined(__GNUC__) || (__GNUC__ >= 5) | |
| using string_stream = std::istringstream; | |
| #else | |
| // Until GCC 5, istringstream did not have a move constructor. | |
| // stringstream_proxy is used instead, as a workaround. | |
| class stringstream_proxy | |
| { | |
| public: | |
| stringstream_proxy() = default; | |
| // Construct with a value. | |
| stringstream_proxy(std::string const &value) : stream_(value) {} | |
| // Copy constructor. | |
| stringstream_proxy(const stringstream_proxy &other) : stream_(other.stream_.str()) | |
| { | |
| stream_.setstate(other.stream_.rdstate()); | |
| } | |
| void setstate(std::ios_base::iostate state) { stream_.setstate(state); } | |
| // Stream out the value of the parameter. | |
| // If the conversion was not possible, the stream will enter the fail state, | |
| // and operator bool will return false. | |
| template <typename T> | |
| stringstream_proxy &operator>>(T &thing) | |
| { | |
| stream_ >> thing; | |
| return *this; | |
| } | |
| // Get the string value. | |
| std::string str() const { return stream_.str(); } | |
| std::stringbuf *rdbuf() const { return stream_.rdbuf(); } | |
| // Check the state of the stream. | |
| // False when the most recent stream operation failed | |
| operator bool() const { return !!stream_; } | |
| ~stringstream_proxy() = default; | |
| private: | |
| std::istringstream stream_; | |
| }; | |
| using string_stream = stringstream_proxy; | |
| #endif | |
| class parser | |
| { | |
| public: | |
| enum Mode | |
| { | |
| PREFER_FLAG_FOR_UNREG_OPTION = 1 << 0, | |
| PREFER_PARAM_FOR_UNREG_OPTION = 1 << 1, | |
| NO_SPLIT_ON_EQUALSIGN = 1 << 2, | |
| SINGLE_DASH_IS_MULTIFLAG = 1 << 3, | |
| }; | |
| parser() = default; | |
| parser(std::initializer_list<char const *const> pre_reg_names) { add_params(pre_reg_names); } | |
| parser(const char *const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION) { parse(argv, mode); } | |
| parser(int argc, const char *const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION) | |
| { | |
| parse(argc, argv, mode); | |
| } | |
| void add_param(std::string const &name); | |
| void add_params(std::initializer_list<char const *const> init_list); | |
| void parse(const char *const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION); | |
| void parse(int argc, const char *const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION); | |
| std::multiset<std::string> const &flags() const { return flags_; } | |
| std::map<std::string, std::string> const ¶ms() const { return params_; } | |
| std::vector<std::string> const &pos_args() const { return pos_args_; } | |
| // begin() and end() for using range-for over positional args. | |
| std::vector<std::string>::const_iterator begin() const { return pos_args_.cbegin(); } | |
| std::vector<std::string>::const_iterator end() const { return pos_args_.cend(); } | |
| size_t size() const { return pos_args_.size(); } | |
| ////////////////////////////////////////////////////////////////////////// | |
| // Accessors | |
| // flag (boolean) accessors: return true if the flag appeared, otherwise false. | |
| bool operator[](std::string const &name) const; | |
| // multiple flag (boolean) accessors: return true if at least one of the flag appeared, | |
| // otherwise false. | |
| bool operator[](std::initializer_list<char const *const> init_list) const; | |
| // returns positional arg string by order. Like argv[] but without the options | |
| std::string const &operator[](size_t ind) const; | |
| // returns a std::istream that can be used to convert a positional arg to a typed value. | |
| string_stream operator()(size_t ind) const; | |
| // same as above, but with a default value in case the arg is missing (index out of range). | |
| template <typename T> | |
| string_stream operator()(size_t ind, T &&def_val) const; | |
| // parameter accessors, give a name get an std::istream that can be used to convert to a typed | |
| // value. call .str() on result to get as string | |
| string_stream operator()(std::string const &name) const; | |
| // accessor for a parameter with multiple names, give a list of names, get an std::istream that | |
| // can be used to convert to a typed value. call .str() on result to get as string returns the | |
| // first value in the list to be found. | |
| string_stream operator()(std::initializer_list<char const *const> init_list) const; | |
| // same as above, but with a default value in case the param was missing. | |
| // Non-string def_val types must have an operator<<() (output stream operator) | |
| // If T only has an input stream operator, pass the string version of the type as in "3" instead | |
| // of 3. | |
| template <typename T> | |
| string_stream operator()(std::string const &name, T &&def_val) const; | |
| // same as above but for a list of names. returns the first value to be found. | |
| template <typename T> | |
| string_stream operator()(std::initializer_list<char const *const> init_list, T &&def_val) const; | |
| private: | |
| string_stream bad_stream() const; | |
| std::string trim_leading_dashes(std::string const &name) const; | |
| bool is_number(std::string const &arg) const; | |
| bool is_option(std::string const &arg) const; | |
| bool got_flag(std::string const &name) const; | |
| bool is_param(std::string const &name) const; | |
| private: | |
| std::vector<std::string> args_; | |
| std::map<std::string, std::string> params_; | |
| std::vector<std::string> pos_args_; | |
| std::multiset<std::string> flags_; | |
| std::set<std::string> registeredParams_; | |
| std::string empty_; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline void parser::parse(const char *const argv[], int mode) | |
| { | |
| int argc = 0; | |
| for (auto argvp = argv; *argvp; ++argc, ++argvp) | |
| ; | |
| parse(argc, argv, mode); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline void | |
| parser::parse(int argc, const char *const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/) | |
| { | |
| // convert to strings | |
| args_.resize(argc); | |
| std::transform(argv, argv + argc, args_.begin(), [](const char *const arg) { return arg; }); | |
| // parse line | |
| for (auto i = 0u; i < args_.size(); ++i) { | |
| if (!is_option(args_[i])) { | |
| pos_args_.emplace_back(args_[i]); | |
| continue; | |
| } | |
| auto name = trim_leading_dashes(args_[i]); | |
| if (!(mode & NO_SPLIT_ON_EQUALSIGN)) { | |
| auto equalPos = name.find('='); | |
| if (equalPos != std::string::npos) { | |
| params_.insert({name.substr(0, equalPos), name.substr(equalPos + 1)}); | |
| continue; | |
| } | |
| } | |
| // if the option is unregistered and should be a multi-flag | |
| if (1 == (args_[i].size() - name.size()) && // single dash | |
| argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode | |
| !is_param(name)) // unregistered | |
| { | |
| std::string keep_param; | |
| if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param | |
| { | |
| keep_param += name.back(); | |
| name.resize(name.size() - 1); | |
| } | |
| for (auto const &c : name) { | |
| flags_.emplace(std::string{c}); | |
| } | |
| if (!keep_param.empty()) { | |
| name = keep_param; | |
| } else { | |
| continue; // do not consider other options for this arg | |
| } | |
| } | |
| // any potential option will get as its value the next arg, unless that arg is an option too | |
| // in that case it will be determined a flag. | |
| if (i == args_.size() - 1 || is_option(args_[i + 1])) { | |
| flags_.emplace(name); | |
| continue; | |
| } | |
| // if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it | |
| // is skipped otherwise we have 2 modes: PREFER_FLAG_FOR_UNREG_OPTION: a non-registered | |
| // 'name' is determined a flag. | |
| // The following value (the next arg) will be a free | |
| // parameter. | |
| // | |
| // PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the | |
| // next arg | |
| // will be the value of that option. | |
| assert(!(mode & argh::parser::PREFER_FLAG_FOR_UNREG_OPTION) || | |
| !(mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION)); | |
| bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION; | |
| if (is_param(name) || preferParam) { | |
| params_.insert({name, args_[i + 1]}); | |
| ++i; // skip next value, it is not a free parameter | |
| continue; | |
| } else { | |
| flags_.emplace(name); | |
| } | |
| }; | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline string_stream parser::bad_stream() const | |
| { | |
| string_stream bad; | |
| bad.setstate(std::ios_base::failbit); | |
| return bad; | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool parser::is_number(std::string const &arg) const | |
| { | |
| // inefficient but simple way to determine if a string is a number (which can start with a '-') | |
| std::istringstream istr(arg); | |
| double number; | |
| istr >> number; | |
| return !(istr.fail() || istr.bad()); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool parser::is_option(std::string const &arg) const | |
| { | |
| assert(0 != arg.size()); | |
| if (is_number(arg)) | |
| return false; | |
| return '-' == arg[0]; | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline std::string parser::trim_leading_dashes(std::string const &name) const | |
| { | |
| auto pos = name.find_first_not_of('-'); | |
| return std::string::npos != pos ? name.substr(pos) : name; | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool argh::parser::got_flag(std::string const &name) const | |
| { | |
| return flags_.end() != flags_.find(trim_leading_dashes(name)); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool argh::parser::is_param(std::string const &name) const | |
| { | |
| return registeredParams_.count(name); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool parser::operator[](std::string const &name) const { return got_flag(name); } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline bool parser::operator[](std::initializer_list<char const *const> init_list) const | |
| { | |
| return std::any_of( | |
| init_list.begin(), init_list.end(), [&](char const *const name) { return got_flag(name); }); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline std::string const &parser::operator[](size_t ind) const | |
| { | |
| if (ind < pos_args_.size()) | |
| return pos_args_[ind]; | |
| return empty_; | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline string_stream parser::operator()(std::string const &name) const | |
| { | |
| auto optIt = params_.find(trim_leading_dashes(name)); | |
| if (params_.end() != optIt) | |
| return string_stream(optIt->second); | |
| return bad_stream(); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline string_stream parser::operator()(std::initializer_list<char const *const> init_list) const | |
| { | |
| for (auto &name : init_list) { | |
| auto optIt = params_.find(trim_leading_dashes(name)); | |
| if (params_.end() != optIt) | |
| return string_stream(optIt->second); | |
| } | |
| return bad_stream(); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| template <typename T> | |
| string_stream parser::operator()(std::string const &name, T &&def_val) const | |
| { | |
| auto optIt = params_.find(trim_leading_dashes(name)); | |
| if (params_.end() != optIt) | |
| return string_stream(optIt->second); | |
| std::ostringstream ostr; | |
| ostr << def_val; | |
| return string_stream(ostr.str()); // use default | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| // same as above but for a list of names. returns the first value to be found. | |
| template <typename T> | |
| string_stream parser::operator()(std::initializer_list<char const *const> init_list, | |
| T &&def_val) const | |
| { | |
| for (auto &name : init_list) { | |
| auto optIt = params_.find(trim_leading_dashes(name)); | |
| if (params_.end() != optIt) | |
| return string_stream(optIt->second); | |
| } | |
| std::ostringstream ostr; | |
| ostr << def_val; | |
| return string_stream(ostr.str()); // use default | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline string_stream parser::operator()(size_t ind) const | |
| { | |
| if (pos_args_.size() <= ind) | |
| return bad_stream(); | |
| return string_stream(pos_args_[ind]); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| template <typename T> | |
| string_stream parser::operator()(size_t ind, T &&def_val) const | |
| { | |
| if (pos_args_.size() <= ind) { | |
| std::ostringstream ostr; | |
| ostr << def_val; | |
| return string_stream(ostr.str()); | |
| } | |
| return string_stream(pos_args_[ind]); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline void parser::add_param(std::string const &name) | |
| { | |
| registeredParams_.insert(trim_leading_dashes(name)); | |
| } | |
| ////////////////////////////////////////////////////////////////////////// | |
| inline void parser::add_params(std::initializer_list<char const *const> init_list) | |
| { | |
| for (auto &name : init_list) | |
| registeredParams_.insert(trim_leading_dashes(name)); | |
| } | |
| } // namespace argh |