Skip to content

Instantly share code, notes, and snippets.

@koturn
Last active January 5, 2016 03:14
Show Gist options
  • Select an option

  • Save koturn/c319b2807323d88cdfd6 to your computer and use it in GitHub Desktop.

Select an option

Save koturn/c319b2807323d88cdfd6 to your computer and use it in GitHub Desktop.
Simple option parser (a wrapper of getopt)
/*!
* @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