Last active
January 5, 2016 03:14
-
-
Save koturn/c319b2807323d88cdfd6 to your computer and use it in GitHub Desktop.
Simple option parser (a wrapper of getopt)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*! | |
| * @brief Simple optionparser | |
| * @author koturn | |
| * @date 2015 12/27 | |
| * @file OptionParser.h | |
| * @version 0.2 | |
| */ | |
| #ifndef OPTION_PARSER_H | |
| #define OPTION_PARSER_H | |
| #include <getopt.h> | |
| #include <cstring> | |
| #include <iostream> | |
| #include <map> | |
| #include <sstream> | |
| #include <stdexcept> | |
| #include <vector> | |
| /*! | |
| * @brief Simple option parser | |
| * @code | |
| * #include <iostream> | |
| * #include <vector> | |
| * #include "OptionParser.h" | |
| * | |
| * int | |
| * main(int argc, char *argv[]) | |
| * { | |
| * OptionParser op(argv[0]); | |
| * op.setOption("apple", 'a', OptionParser::NO_ARGUMENT, "", "Specify apple"); | |
| * op.setOption("banana", 'b', OptionParser::REQUIRED_ARGUMENT, "", "Specify banana"); | |
| * op.setOption("cake", 'c', OptionParser::NO_ARGUMENT, "", "Specify cake"); | |
| * op.setOption("help", 'h', OptionParser::NO_ARGUMENT, "", "Show help and exit this program"); | |
| * op.setOption('x', OptionParser::NO_ARGUMENT, "0", "No long option sample"); | |
| * op.setOption("long-x", OptionParser::NO_ARGUMENT, "0", "no short option sample"); | |
| * op.parse(argc, argv); | |
| * | |
| * std::cout << "[Specified options]\n"; | |
| * if (op.get<bool>("help")) { | |
| * op.showUsage(); | |
| * return 0; | |
| * } | |
| * if (op.get<bool>("apple")) std::cout << " -a, --apple" << std::endl; | |
| * if (!op.get("banana").empty()) std::cout << " -b, --banana=" << op.get("banana") << std::endl; | |
| * if (op.get<bool>("cake")) std::cout << " -c, --cake" << std::endl; | |
| * if (op.get<bool>('x')) std::cout << " -x" << std::endl; | |
| * if (op.get<bool>("long-x")) std::cout << " --long-x" << std::endl; | |
| * | |
| * std::cout << "[Remaining arguments]\n"; | |
| * std::vector<std::string> v = op.getArguments(); | |
| * for (std::vector<std::string>::const_iterator itr = v.begin(); itr != v.end(); ++itr) { | |
| * std::cout << *itr << "\n"; | |
| * } | |
| * std::cout << std::flush; | |
| * | |
| * return 0; | |
| * } | |
| * @endcode | |
| */ | |
| class OptionParser | |
| { | |
| public: | |
| /*! | |
| * @brief Enumeration of option types | |
| * NO_ARGUMENT: The option takes no argument | |
| * REQUIRED_ARGUMENT: The option takes one argument | |
| * OPTIONAL_ARGUMENT: The option takes no or one argument | |
| */ | |
| enum OptionType { | |
| NO_ARGUMENT = no_argument, | |
| REQUIRED_ARGUMENT = required_argument, | |
| OPTIONAL_ARGUMENT = optional_argument | |
| }; | |
| private: | |
| /*! | |
| * @brief Set of option attributes | |
| */ | |
| struct OptionSet { | |
| std::string longOptName; | |
| int shortOptName; | |
| OptionType optAttr; | |
| std::string help; | |
| std::string metavar; | |
| OptionSet( | |
| const std::string& longOptName, | |
| int shortOptName, | |
| OptionType optAttr, | |
| const std::string& help, | |
| const std::string& metavar) : | |
| longOptName(longOptName), | |
| shortOptName(shortOptName), | |
| optAttr(optAttr), | |
| help(help), | |
| metavar(metavar) | |
| {} | |
| }; | |
| int noShortNameCnt; | |
| std::string progname; | |
| std::string description; | |
| std::string optionString; | |
| std::vector<OptionSet> optionSets; | |
| std::vector<std::string> arguments; | |
| std::map<std::string, std::string> longOptionMap; | |
| std::map<int, std::string> shortOptionMap; | |
| mutable std::stringstream ss; | |
| public: | |
| OptionParser(const std::string& progname="[This program]"); | |
| OptionParser(const OptionParser& that); | |
| void setDescription( | |
| const std::string& description); | |
| void setOption( | |
| const std::string& longOptName, | |
| int shortOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue="", | |
| const std::string& help="", | |
| const std::string& metavar="ARG"); | |
| void setOption( | |
| const std::string& longOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue, | |
| const std::string& help="", | |
| const std::string& metavar="ARG"); | |
| void setOption( | |
| int shortOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue, | |
| const std::string& help="", | |
| const std::string& metavar="ARG"); | |
| void parse( | |
| int argc, | |
| char *const *argv, | |
| int nRequiredArgument=-1); | |
| bool has( | |
| const std::string& key) const; | |
| bool has( | |
| int key) const; | |
| template<typename ValType> ValType get( | |
| const std::string& key) const; | |
| std::string get( | |
| const std::string& key) const; | |
| template<typename ValType> ValType get( | |
| int key) const; | |
| std::string get( | |
| int key) const; | |
| std::vector<std::string> getArguments(void) const; | |
| void showUsage(std::ostream& os=std::cout) const; | |
| std::string& operator[](const std::string& longOptName); | |
| std::string& operator[](char shortOptName); | |
| OptionParser& operator=(const OptionParser& that); | |
| template<typename CharT, typename Traits> | |
| friend std::basic_ostream<CharT, Traits>& | |
| operator<<(std::basic_ostream<CharT, Traits>& os, const OptionParser& this_); | |
| }; // class OptionParser | |
| /*! | |
| * @brief Constructor: Create option parser | |
| * @param [in] progname Program name (such as 'argv[0]') | |
| */ | |
| OptionParser::OptionParser(const std::string& progname) : | |
| noShortNameCnt(0), | |
| progname(progname), | |
| description(), | |
| optionString(), | |
| optionSets(), | |
| arguments(), | |
| longOptionMap(), | |
| shortOptionMap(), | |
| ss() | |
| {} | |
| /*! | |
| * @brief Copy constructor: Copy OptionParser instance | |
| * @param [in] that Original option parser instance | |
| */ | |
| OptionParser::OptionParser( | |
| const OptionParser& that) : | |
| noShortNameCnt(that.noShortNameCnt), | |
| progname(that.progname), | |
| description(that.description), | |
| optionString(that.optionString), | |
| optionSets(that.optionSets), | |
| arguments(that.arguments), | |
| longOptionMap(that.longOptionMap), | |
| shortOptionMap(that.shortOptionMap), | |
| ss() | |
| {} | |
| /*! | |
| * @brief Set description of your program | |
| * | |
| * Specified description is shown by OptionParser::showUsage() | |
| * | |
| * @param [in] description Description of your program | |
| */ | |
| void | |
| OptionParser::setDescription(const std::string& description) | |
| { | |
| this->description = description; | |
| } | |
| /*! | |
| * @brief Set one option | |
| * @param [in] longOptName Long option name such as "apple", "banana" or "cake" | |
| * @param [in] shortOptName Short option name such as 'a', 'b' or 'c' | |
| * @param [in] optAttr Option attributes (NO_ARGUMENT, REQUIRED_ARGUMENT) | |
| * @param [in] defaultValue Default value for this option | |
| * @param [in] help Description of this option | |
| * @param [in] metavar Name of meta variable for this option | |
| */ | |
| void | |
| OptionParser::setOption( | |
| const std::string& longOptName, | |
| int shortOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue, | |
| const std::string& help, | |
| const std::string& metavar) | |
| { | |
| std::string defaultValue_ = (optAttr == NO_ARGUMENT && defaultValue.empty()) ? "0" : defaultValue; | |
| if (longOptName.empty()) { | |
| optionSets.push_back(OptionSet("", shortOptName, optAttr, help, metavar)); | |
| } else { | |
| optionSets.push_back(OptionSet(longOptName, shortOptName, optAttr, help, metavar)); | |
| longOptionMap[longOptName] = defaultValue_; | |
| } | |
| shortOptionMap[shortOptName] = defaultValue_; | |
| optionString += static_cast<char>(shortOptName); | |
| if (optAttr == REQUIRED_ARGUMENT || optAttr == OPTIONAL_ARGUMENT) { | |
| optionString += ":"; | |
| } | |
| } | |
| /*! | |
| * @brief Set one option | |
| * @param [in] longOptName Long option name such as "apple", "banana" or "cake" | |
| * @param [in] optAttr Option attributes (NO_ARGUMENT, REQUIRED_ARGUMENT) | |
| * @param [in] defaultValue Default value for this option | |
| * @param [in] help Description of this option | |
| * @param [in] metavar Name of meta variable for this option | |
| */ | |
| void | |
| OptionParser::setOption( | |
| const std::string& longOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue, | |
| const std::string& help, | |
| const std::string& metavar) | |
| { | |
| setOption(longOptName, ++noShortNameCnt, optAttr, defaultValue, help, metavar); | |
| } | |
| /*! | |
| * @brief Set one option | |
| * @param [in] shortOptName Short option name such as 'a', 'b' or 'c' | |
| * @param [in] optAttr Option attributes (NO_ARGUMENT, REQUIRED_ARGUMENT) | |
| * @param [in] defaultValue Default value for this option | |
| * @param [in] help Description of this option | |
| * @param [in] metavar Name of meta variable for this option | |
| */ | |
| void | |
| OptionParser::setOption( | |
| int shortOptName, | |
| OptionType optAttr, | |
| const std::string& defaultValue, | |
| const std::string& help, | |
| const std::string& metavar) | |
| { | |
| setOption("", shortOptName, optAttr, defaultValue, help, metavar); | |
| } | |
| /*! | |
| * @brief Parse specified command line arguments | |
| * @param [in] argc A number of command-line arguments | |
| * @param [in] argv Command line arguments | |
| * @param [in] nRequiredArgument Number of required argument | |
| */ | |
| void | |
| OptionParser::parse( | |
| int argc, | |
| char *const *argv, | |
| int nRequiredArgument) | |
| { | |
| if (argv == NULL) { | |
| throw std::invalid_argument("Second argument of OptionParser::parse() must not be NULL pointer"); | |
| } | |
| std::vector<char *> argvVector(argc); | |
| for (int i = 0; i < argc; i++) { | |
| argvVector[i] = argv[i]; | |
| } | |
| std::vector<option> options; | |
| for (std::vector<OptionSet>::const_iterator itr = optionSets.begin(); itr != optionSets.end(); ++itr) { | |
| option opt = {itr->longOptName.c_str(), itr->optAttr, NULL, itr->shortOptName}; | |
| options.push_back(opt); | |
| } | |
| option sentinelOption = {NULL, 0, NULL, 0}; | |
| options.push_back(sentinelOption); | |
| // int prevOpterr = opterr; | |
| // opterr = 0; | |
| int ret, optidx; | |
| while ((ret = getopt_long(argc, &argvVector[0], optionString.c_str(), &options[0], &optidx)) != -1) { | |
| for (std::vector<OptionSet>::const_iterator itr = optionSets.begin(); itr != optionSets.end(); ++itr) { | |
| if (itr->shortOptName == ret) { | |
| switch (itr->optAttr) { | |
| case REQUIRED_ARGUMENT: | |
| longOptionMap[itr->longOptName] = optarg; | |
| shortOptionMap[ret] = optarg; | |
| break; | |
| case NO_ARGUMENT: | |
| longOptionMap[itr->longOptName] = "1"; | |
| shortOptionMap[ret] = "1"; | |
| break; | |
| case OPTIONAL_ARGUMENT: | |
| if (optarg == NULL) { | |
| longOptionMap[itr->longOptName] = "1"; | |
| shortOptionMap[ret] = "1"; | |
| } else { | |
| longOptionMap[itr->longOptName] = optarg; | |
| shortOptionMap[ret] = optarg; | |
| } | |
| break; | |
| } | |
| } else if (ret == '?') { | |
| throw std::runtime_error("Invalid option"); | |
| } | |
| } | |
| } | |
| // opterr = prevOpterr; | |
| if (nRequiredArgument > 0 && optind > argc - nRequiredArgument) { | |
| ss.str(""); | |
| ss.clear(std::stringstream::goodbit); | |
| ss << "Please specify "; | |
| ss << nRequiredArgument; | |
| ss << " argument"; | |
| if (nRequiredArgument > 1) { | |
| ss << "s"; | |
| } | |
| throw std::runtime_error(ss.str()); | |
| } | |
| for (int i = optind; i < argc; i++) { | |
| arguments.push_back(argv[i]); | |
| } | |
| } | |
| /*! | |
| * @brief Check whether option parser can recognize specified option or not | |
| * @param [in] key Long option name | |
| * @return True then option parser can recognize specified option | |
| */ | |
| bool | |
| OptionParser::has( | |
| const std::string& key) const | |
| { | |
| return longOptionMap.find(key) != longOptionMap.end(); | |
| } | |
| /*! | |
| * @brief Check whether option parser can recognize specified option or not | |
| * @param [in] key Short option name | |
| * @return True then option parser can recognize specified option | |
| */ | |
| bool | |
| OptionParser::has( | |
| int key) const | |
| { | |
| return shortOptionMap.find(key) != shortOptionMap.end(); | |
| } | |
| /*! | |
| * @brief Get option value | |
| * @tparam ValType Value type | |
| * @param [in] key Long option name | |
| * @return Option value (Its type is converted to the specified template argument type) | |
| */ | |
| template<typename ValType> | |
| ValType | |
| OptionParser::get( | |
| const std::string& key) const | |
| { | |
| ValType val; | |
| ss.str(""); | |
| ss.clear(std::stringstream::goodbit); | |
| ss << longOptionMap.at(key); | |
| ss >> val; | |
| return val; | |
| } | |
| /*! | |
| * @brief Get option value | |
| * @param [in] key Long option name | |
| * @return Option value (as std::string) | |
| */ | |
| std::string | |
| OptionParser::get( | |
| const std::string& key) const | |
| { | |
| return longOptionMap.at(key); | |
| } | |
| /*! | |
| * @brief Get option value | |
| * @tparam ValType Value type | |
| * @param [in] key Short option name | |
| * @return Option value (Its type is converted to the specified template argument type) | |
| */ | |
| template<typename ValType> | |
| ValType | |
| OptionParser::get( | |
| int key) const | |
| { | |
| ValType val; | |
| ss.str(""); | |
| ss.clear(std::stringstream::goodbit); | |
| ss << shortOptionMap.at(key); | |
| ss >> val; | |
| return val; | |
| } | |
| /*! | |
| * @brief Get option value | |
| * @param [in] key Short option name | |
| * @return Option value (as std::string) | |
| */ | |
| std::string | |
| OptionParser::get( | |
| int key) const | |
| { | |
| return shortOptionMap.at(key); | |
| } | |
| /*! | |
| * @brief Get remaining arguments | |
| * @return String vector of remaining arguments | |
| */ | |
| std::vector<std::string> | |
| OptionParser::getArguments(void) const | |
| { | |
| return arguments; | |
| } | |
| /*! | |
| * @brief Show usage of your program | |
| * @param [in] os Output stream | |
| */ | |
| void | |
| OptionParser::showUsage( | |
| std::ostream& os) const | |
| { | |
| if (!description.empty()) { | |
| os << description << "\n"; | |
| } | |
| os << "[Usage]\n" | |
| << " " << progname << " [Options ...] [Arguments ...]\n\n" | |
| << "[Option]\n"; | |
| for (std::vector<OptionSet>::const_iterator itr = optionSets.begin(); itr != optionSets.end(); ++itr) { | |
| os << " "; | |
| if (itr->longOptName.empty()) { | |
| os << "-" << static_cast<char>(itr->shortOptName); | |
| if (itr->optAttr == REQUIRED_ARGUMENT) { | |
| os << " " << itr->metavar; | |
| } | |
| } else if (itr->shortOptName < 30) { | |
| os << "--" << itr->longOptName; | |
| if (itr->optAttr == REQUIRED_ARGUMENT) { | |
| os << "=" << itr->metavar; | |
| } | |
| } else { | |
| os << "-" << static_cast<char>(itr->shortOptName); | |
| if (itr->optAttr == REQUIRED_ARGUMENT) { | |
| os << " " << itr->metavar; | |
| } | |
| os << ", --" << itr->longOptName; | |
| if (itr->optAttr == REQUIRED_ARGUMENT) { | |
| os << "=" << itr->metavar; | |
| } | |
| } | |
| os << "\n"; | |
| if (!itr->help.empty()) { | |
| os << " " << itr->help << "\n"; | |
| } | |
| } | |
| os << std::endl; | |
| } | |
| /*! | |
| * @brief Access option value | |
| * | |
| * You can get/set option value | |
| * | |
| * @param [in] longOptName Long option name such as "apple", "banana" or "cake" | |
| * | |
| * @return Option value as std::string | |
| */ | |
| std::string& | |
| OptionParser::operator[]( | |
| const std::string& longOptName) | |
| { | |
| return longOptionMap.at(longOptName); | |
| } | |
| /*! | |
| * @brief Access option value | |
| * | |
| * You can get/set option value as following code | |
| * | |
| * @code | |
| * OptionParser op(argc, argv); | |
| * op.setOption("apple", 'a', OptionParser::NO_ARGUMENT, "", "Specify apple"); | |
| * op.parse(); | |
| * std::cout << op["apple"] << std::endl; | |
| * std::cout << op['a'] << std::endl; | |
| * op["apple"] = "foo"; | |
| * std::cout << op["apple"] << std::endl; | |
| * std::cout << op['a'] << std::endl; | |
| * @endcode | |
| * | |
| * @param [in] shortOptName Short option name such as 'a', 'b' or 'c' | |
| * | |
| * @return Option value as std::string | |
| */ | |
| std::string& | |
| OptionParser::operator[]( | |
| char shortOptName) | |
| { | |
| return shortOptionMap.at(shortOptName); | |
| } | |
| /*! | |
| * @brief Assignment operator for OptionParser | |
| * | |
| * Allocate memory and 'argv' of 'that' is copied to there | |
| * | |
| * @param [in] that Original OptionParser instance | |
| * | |
| * @return Copied option parser instance | |
| */ | |
| OptionParser& | |
| OptionParser::operator=( | |
| const OptionParser& that) | |
| { | |
| noShortNameCnt = that.noShortNameCnt; | |
| progname = that.progname; | |
| description = that.description; | |
| optionString = that.optionString; | |
| optionSets = that.optionSets; | |
| arguments = that.arguments; | |
| longOptionMap = that.longOptionMap; | |
| shortOptionMap = that.shortOptionMap; | |
| return *this; | |
| } | |
| /*! | |
| * @brief Operator overload for 'std::ostream' (such as 'std::cout') | |
| * | |
| * @tparam CharT Character type | |
| * @tparam Traits Traits type | |
| * @param [in,out] os Output stream | |
| * @param [in] this_ Option parser instance | |
| * | |
| * @return Output stream | |
| */ | |
| template<typename CharT, typename Traits> | |
| std::basic_ostream<CharT, Traits>& | |
| operator<<( | |
| std::basic_ostream<CharT, Traits>& os, | |
| const OptionParser& this_) | |
| { | |
| this_.showUsage(os); | |
| return os; | |
| } | |
| #endif // OPTION_PARSER_H |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment