a portable and fast pairing-based cryptography library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mcl/include/cybozu/option.hpp

724 lines
18 KiB

#pragma once
/**
@file
@brief command line parser
@author MITSUNARI Shigeo(@herumi)
*/
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <iostream>
#include <limits>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <cybozu/exception.hpp>
#include <cybozu/atoi.hpp>
/*
Option parser
progName (opt1-name|opt2-name|...) param1 param2 ...
param1:param1-help
param2:param2-help
-op1-name:opt1-help
...
How to setup
int num;
-n num ; (optional) option => appendOpt(&x, <defaultValue>, "num", "num-help");
-n num ; must option => appendMust(&x, "num", "num-help");
std::vector<int> v;
-v s1 s2 s3 ... => appendVec(&v, "v");
Remark1: terminate parsing of v if argv begins with '-[^0-9]'
Remark2: the begining character of opt-name is not a number ('0'...'9')
because avoid conflict with minus number
std::string file1;
file1 is param => appendParam(&file1, "input-file");
file2 is optional param => appendParamOpt(&file2, "output-file");
How to use
opt.parse(argc, argv);
see sample/option_smpl.cpp
*/
namespace cybozu {
struct OptionError : public cybozu::Exception {
enum Type {
NoError = 0,
BAD_OPT = 1,
BAD_VALUE,
NO_VALUE,
OPT_IS_NECESSARY,
PARAM_IS_NECESSARY,
REDUNDANT_VAL,
BAD_ARGC
};
Type type;
int argPos;
OptionError()
: cybozu::Exception("OptionError", false)
, type(NoError)
, argPos(0)
{
}
cybozu::Exception& set(Type _type, int _argPos = 0)
{
this->type = _type;
this->argPos = _argPos;
switch (_type) {
case BAD_OPT:
(*this) << "bad opt";
break;
case BAD_VALUE:
(*this) << "bad value";
break;
case NO_VALUE:
(*this) << "no value";
break;
case OPT_IS_NECESSARY:
(*this) << "opt is necessary";
break;
case PARAM_IS_NECESSARY:
(*this) << "param is necessary";
break;
case REDUNDANT_VAL:
(*this) << "redundant argVal";
break;
case BAD_ARGC:
(*this) << "bad argc";
default:
break;
}
return *this;
}
};
namespace option_local {
template<class T>
bool convert(T* x, const char *str)
{
std::istringstream is(str);
is >> *x;
return !!is;
}
template<>
inline bool convert(std::string* x, const char *str)
{
*x = str;
return true;
}
template<class T>
bool convertInt(T* x, const char *str)
{
if (str[0] == '0' && str[1] == 'x') {
bool b;
*x = cybozu::hextoi(&b, str + 2);
return b;
}
size_t len = strlen(str);
int factor = 1;
if (len > 1) {
switch (str[len - 1]) {
case 'k': factor = 1000; len--; break;
case 'm': factor = 1000 * 1000; len--; break;
case 'g': factor = 1000 * 1000 * 1000; len--; break;
case 'K': factor = 1024; len--; break;
case 'M': factor = 1024 * 1024; len--; break;
case 'G': factor = 1024 * 1024 * 1024; len--; break;
default: break;
}
}
bool b;
T y = cybozu::atoi(&b, str, len);
if (!b) return false;
if (factor > 1) {
if ((std::numeric_limits<T>::min)() / factor <= y
&& y <= (std::numeric_limits<T>::max)() / factor) {
*x = y * factor;
} else {
return false;
}
} else {
*x = y;
}
return true;
}
#define CYBOZU_OPTION_DEFINE_CONVERT_INT(type) \
template<>inline bool convert(type* x, const char *str) { return convertInt(x, str); }
CYBOZU_OPTION_DEFINE_CONVERT_INT(int)
CYBOZU_OPTION_DEFINE_CONVERT_INT(long)
CYBOZU_OPTION_DEFINE_CONVERT_INT(long long)
CYBOZU_OPTION_DEFINE_CONVERT_INT(unsigned int)
CYBOZU_OPTION_DEFINE_CONVERT_INT(unsigned long)
CYBOZU_OPTION_DEFINE_CONVERT_INT(unsigned long long)
#undef CYBOZU_OPTION_DEFINE_CONVERT_INT
struct HolderBase {
virtual ~HolderBase(){}
virtual bool set(const char*) = 0;
virtual HolderBase *clone() const = 0;
virtual std::string toStr() const = 0;
virtual const void *get() const = 0;
};
template<class T>
struct Holder : public HolderBase {
T *p_;
Holder(T *p) : p_(p) {}
HolderBase *clone() const { return new Holder(p_); }
bool set(const char *str) { return option_local::convert(p_, str); }
std::string toStr() const
{
std::ostringstream os;
os << *p_;
return os.str();
}
const void *get() const { return (void*)p_; }
};
/*
for gcc 7 with -fnew-ttp-matching
this specialization is not necessary under -fno-new-ttp-matching
*/
template struct Holder<std::string>;
template<class T, class Alloc, template<class T_, class Alloc_>class Container>
struct Holder<Container<T, Alloc> > : public HolderBase {
typedef Container<T, Alloc> Vec;
Vec *p_;
Holder(Vec *p) : p_(p) {}
HolderBase *clone() const { return new Holder<Vec>(p_); }
bool set(const char *str)
{
T t;
bool b = option_local::convert(&t, str);
if (b) p_->push_back(t);
return b;
}
std::string toStr() const
{
std::ostringstream os;
bool isFirst = true;
for (typename Vec::const_iterator i = p_->begin(), ie = p_->end(); i != ie; ++i) {
if (isFirst) {
isFirst = false;
} else {
os << ' ';
}
os << *i;
}
return os.str();
}
const void *get() const { return (void*)p_; }
};
class Var {
HolderBase *p_;
bool isSet_;
public:
Var() : p_(0), isSet_(false) { }
Var(const Var& rhs) : p_(rhs.p_->clone()), isSet_(false) { }
template<class T>
explicit Var(T *x) : p_(new Holder<T>(x)), isSet_(false) { }
~Var() { delete p_; }
void swap(Var& rhs) CYBOZU_NOEXCEPT
{
std::swap(p_, rhs.p_);
std::swap(isSet_, rhs.isSet_);
}
void operator=(const Var& rhs)
{
Var v(rhs);
swap(v);
}
bool set(const char *str)
{
isSet_ = true;
return p_->set(str);
}
std::string toStr() const { return p_ ? p_->toStr() : ""; }
bool isSet() const { return isSet_; }
const void *get() const { return p_ ? p_->get() : 0; }
};
} // option_local
class Option {
enum Mode { // for opt
N_is0 = 0, // for bool by appendBoolOpt()
N_is1 = 1,
N_any = 2
};
enum ParamMode {
P_exact = 0, // one
P_optional = 1, // zero or one
P_variable = 2 // zero or greater
};
struct Info {
option_local::Var var;
Mode mode; // 0 or 1 or any ; for opt, not used for Param
bool isMust; // this option is must
std::string opt; // option param name without '-'
std::string help; // description of option
Info() : mode(N_is0), isMust(false) {}
template<class T>
Info(T* pvar, Mode mode, bool isMust, const char *opt, const std::string& help)
: var(pvar)
, mode(mode)
, isMust(isMust)
, opt(opt)
, help(help)
{
}
friend inline std::ostream& operator<<(std::ostream& os, const Info& self)
{
os << self.opt << '=' << self.var.toStr();
if (self.var.isSet()) {
os << " (set)";
} else {
os << " (default)";
}
return os;
}
void put() const
{
std::cout << *this;
}
void usage() const
{
printf(" -%s %s%s\n", opt.c_str(), help.c_str(), isMust ? " (must)" : "");
}
void shortUsage() const
{
printf(" -%s %s", opt.c_str(), mode == N_is0 ? "" : mode == N_is1 ? "para" : "para...");
}
bool isSet() const { return var.isSet(); }
const void *get() const { return var.get(); }
};
typedef std::vector<Info> InfoVec;
typedef std::vector<std::string> StrVec;
typedef std::map<std::string, size_t> OptMap;
InfoVec infoVec_;
InfoVec paramVec_;
Info remains_;
OptMap optMap_;
bool showOptUsage_;
ParamMode paramMode_;
std::string progName_;
std::string desc_;
std::string helpOpt_;
std::string help_;
std::string usage_;
StrVec delimiters_;
StrVec *remainsAfterDelimiter_;
int nextDelimiter_;
template<class T>
void appendSub(T *pvar, Mode mode, bool isMust, const char *opt, const std::string& help)
{
const char c = opt[0];
if ('0' <= c && c <= '9') throw cybozu::Exception("Option::appendSub:opt must begin with not number") << opt;
if (optMap_.find(opt) != optMap_.end()) {
throw cybozu::Exception("Option::append:duplicate option") << opt;
}
optMap_[opt] = infoVec_.size();
infoVec_.push_back(Info(pvar, mode, isMust, opt, help));
}
template<class T, class U>
void append(T *pvar, const U& defaultVal, bool isMust, const char *opt, const std::string& help = "")
{
*pvar = static_cast<const T&>(defaultVal);
appendSub(pvar, N_is1, isMust, opt, help);
}
/*
don't deal with negative number as option
*/
bool isOpt(const char *str) const
{
if (str[0] != '-') return false;
const char c = str[1];
if ('0' <= c && c <= '9') return false;
return true;
}
void verifyParamMode()
{
if (paramMode_ != P_exact) throw cybozu::Exception("Option:appendParamVec:appendParam is forbidden after appendParamOpt/appendParamVec");
}
std::string getBaseName(const std::string& name) const
{
size_t pos = name.find_last_of("/\\");
if (pos == std::string::npos) return name;
return name.substr(pos + 1);
}
bool inDelimiters(const std::string& str) const
{
return std::find(delimiters_.begin(), delimiters_.end(), str) != delimiters_.end();
}
public:
Option()
: showOptUsage_(true)
, paramMode_(P_exact)
, remainsAfterDelimiter_(0)
, nextDelimiter_(-1)
{
}
virtual ~Option() {}
/*
append optional option with default value
@param pvar [in] pointer to option variable
@param defaultVal [in] default value
@param opt [in] option name
@param help [in] option help
@note you can use 123k, 56M if T is int/long/long long
k : *1000
m : *1000000
g : *1000000000
K : *1024
M : *1024*1024
G : *1024*1024*1024
*/
template<class T, class U>
void appendOpt(T *pvar, const U& defaultVal, const char *opt, const std::string& help = "")
{
append(pvar, defaultVal, false, opt, help);
}
/*
default value of *pvar is false
*/
void appendBoolOpt(bool *pvar, const char *opt, const std::string& help = "")
{
*pvar = false;
appendSub(pvar, N_is0, false, opt, help);
}
/*
append necessary option
@param pvar [in] pointer to option variable
@param opt [in] option name
@param help [in] option help
*/
template<class T>
void appendMust(T *pvar, const char *opt, const std::string& help = "")
{
append(pvar, T(), true, opt, help);
}
/*
append vector option
@param pvar [in] pointer to option variable
@param opt [in] option name
@param help [in] option help
*/
template<class T, class Alloc, template<class T_, class Alloc_>class Container>
void appendVec(Container<T, Alloc> *pvar, const char *opt, const std::string& help = "")
{
appendSub(pvar, N_any, false, opt, help);
}
/*
append parameter
@param pvar [in] pointer to parameter
@param opt [in] option name
@param help [in] option help
*/
template<class T>
void appendParam(T *pvar, const char *opt, const std::string& help = "")
{
verifyParamMode();
paramVec_.push_back(Info(pvar, N_is1, true, opt, help));
}
/*
append optional parameter
@param pvar [in] pointer to parameter
@param defaultVal [in] default value
@param opt [in] option name
@param help [in] option help
@note you can call appendParamOpt once after appendParam
*/
template<class T, class U>
void appendParamOpt(T *pvar, const U& defaultVal, const char *opt, const std::string& help = "")
{
verifyParamMode();
*pvar = defaultVal;
paramMode_ = P_optional;
paramVec_.push_back(Info(pvar, N_is1, false, opt, help));
}
/*
append remain parameter
@param pvar [in] pointer to vector of parameter
@param opt [in] option name
@param help [in] option help
@note you can call appendParamVec once after appendParam
*/
template<class T, class Alloc, template<class T_, class Alloc_>class Container>
void appendParamVec(Container<T, Alloc> *pvar, const char *name, const std::string& help = "")
{
verifyParamMode();
paramMode_ = P_variable;
remains_.var = option_local::Var(pvar);
remains_.mode = N_any;
remains_.isMust = false;
remains_.opt = name;
remains_.help = help;
}
void appendHelp(const char *opt, const std::string& help = ": show this message")
{
helpOpt_ = opt;
help_ = help;
}
/*
stop parsing after delimiter is found
@param delimiter [in] string to stop
@param remain [out] set remaining strings if remain
*/
void setDelimiter(const std::string& delimiter, std::vector<std::string> *remain = 0)
{
delimiters_.push_back(delimiter);
remainsAfterDelimiter_ = remain;
}
/*
stop parsing after delimiter is found
@param delimiter [in] string to stop to append list of delimiters
*/
void appendDelimiter(const std::string& delimiter)
{
delimiters_.push_back(delimiter);
}
/*
clear list of delimiters
*/
void clearDelimiterList() { delimiters_.clear(); }
/*
return the next position of delimiter between [0, argc]
@note return argc if delimiter is not set nor found
*/
int getNextPositionOfDelimiter() const { return nextDelimiter_; }
/*
parse (argc, argv)
@param argc [in] argc of main
@param argv [in] argv of main
@param startPos [in] start position of argc
@param progName [in] used instead of argv[0]
*/
bool parse(int argc, const char *const argv[], int startPos = 1, const char *progName = 0)
{
if (argc < 1 || startPos > argc) return false;
progName_ = getBaseName(progName ? progName : argv[startPos - 1]);
nextDelimiter_ = argc;
OptionError err;
for (int pos = startPos; pos < argc; pos++) {
if (inDelimiters(argv[pos])) {
nextDelimiter_ = pos + 1;
if (remainsAfterDelimiter_) {
for (int i = nextDelimiter_; i < argc; i++) {
remainsAfterDelimiter_->push_back(argv[i]);
}
}
break;
}
if (isOpt(argv[pos])) {
const std::string str = argv[pos] + 1;
if (helpOpt_ == str) {
usage();
exit(0);
}
OptMap::const_iterator i = optMap_.find(str);
if (i == optMap_.end()) {
err.set(OptionError::BAD_OPT, pos);
goto ERR;
}
Info& info = infoVec_[i->second];
switch (info.mode) {
case N_is0:
if (!info.var.set("1")) {
err.set(OptionError::BAD_VALUE, pos);
goto ERR;
}
break;
case N_is1:
pos++;
if (pos == argc) {
err.set(OptionError::BAD_VALUE, pos) << (std::string("no value for -") + info.opt);
goto ERR;
}
if (!info.var.set(argv[pos])) {
err.set(OptionError::BAD_VALUE, pos) << (std::string(argv[pos]) + " for -" + info.opt);
goto ERR;
}
break;
case N_any:
default:
{
pos++;
int j = 0;
while (pos < argc && !isOpt(argv[pos])) {
if (!info.var.set(argv[pos])) {
err.set(OptionError::BAD_VALUE, pos) << (std::string(argv[pos]) + " for -" + info.opt) << j;
goto ERR;
}
pos++;
j++;
}
if (j > 0) {
pos--;
} else {
err.set(OptionError::NO_VALUE, pos) << (std::string("for -") + info.opt);
goto ERR;
}
}
break;
}
} else {
bool used = false;
for (size_t i = 0; i < paramVec_.size(); i++) {
Info& param = paramVec_[i];
if (!param.var.isSet()) {
if (!param.var.set(argv[pos])) {
err.set(OptionError::BAD_VALUE, pos) << (std::string(argv[pos]) + " for " + param.opt);
goto ERR;
}
used = true;
break;
}
}
if (!used) {
if (paramMode_ == P_variable) {
remains_.var.set(argv[pos]);
} else {
err.set(OptionError::REDUNDANT_VAL, pos) << argv[pos];
goto ERR;
}
}
}
}
// check whether must-opt is set
for (size_t i = 0; i < infoVec_.size(); i++) {
const Info& info = infoVec_[i];
if (info.isMust && !info.var.isSet()) {
err.set(OptionError::OPT_IS_NECESSARY) << info.opt;
goto ERR;
}
}
// check whether param is set
for (size_t i = 0; i < paramVec_.size(); i++) {
const Info& param = paramVec_[i];
if (param.isMust && !param.var.isSet()) {
err.set(OptionError::PARAM_IS_NECESSARY) << param.opt;
goto ERR;
}
}
// check whether remains is set
if (paramMode_ == P_variable && remains_.isMust && !remains_.var.isSet()) {
err.set(OptionError::PARAM_IS_NECESSARY) << remains_.opt;
goto ERR;
}
return true;
ERR:
assert(err.type);
printf("%s\n", err.what());
return false;
}
/*
show desc at first in usage()
*/
void setDescription(const std::string& desc)
{
desc_ = desc;
}
/*
show command line after desc
don't put option message if not showOptUsage
*/
void setUsage(const std::string& usage, bool showOptUsage = false)
{
usage_ = usage;
showOptUsage_ = showOptUsage;
}
void usage() const
{
if (!desc_.empty()) printf("%s\n", desc_.c_str());
if (usage_.empty()) {
printf("usage:%s", progName_.c_str());
if (!infoVec_.empty()) printf(" [opt]");
for (size_t i = 0; i < infoVec_.size(); i++) {
if (infoVec_[i].isMust) infoVec_[i].shortUsage();
}
for (size_t i = 0; i < paramVec_.size(); i++) {
printf(" %s", paramVec_[i].opt.c_str());
}
if (paramMode_ == P_variable) {
printf(" %s", remains_.opt.c_str());
}
printf("\n");
} else {
printf("%s\n", usage_.c_str());
if (!showOptUsage_) return;
}
for (size_t i = 0; i < paramVec_.size(); i++) {
const Info& param = paramVec_[i];
if (!param.help.empty()) printf(" %s %s\n", paramVec_[i].opt.c_str(), paramVec_[i].help.c_str());
}
if (!remains_.help.empty()) printf(" %s %s\n", remains_.opt.c_str(), remains_.help.c_str());
if (!helpOpt_.empty()) {
printf(" -%s %s\n", helpOpt_.c_str(), help_.c_str());
}
for (size_t i = 0; i < infoVec_.size(); i++) {
infoVec_[i].usage();
}
}
friend inline std::ostream& operator<<(std::ostream& os, const Option& self)
{
for (size_t i = 0; i < self.paramVec_.size(); i++) {
const Info& param = self.paramVec_[i];
os << param.opt << '=' << param.var.toStr() << std::endl;
}
if (self.paramMode_ == P_variable) {
os << "remains=" << self.remains_.var.toStr() << std::endl;
}
for (size_t i = 0; i < self.infoVec_.size(); i++) {
os << self.infoVec_[i] << std::endl;
}
return os;
}
void put() const
{
std::cout << *this;
}
/*
whether pvar is set or not
*/
template<class T>
bool isSet(const T* pvar) const
{
const void *p = static_cast<const void*>(pvar);
for (size_t i = 0; i < paramVec_.size(); i++) {
const Info& v = paramVec_[i];
if (v.get() == p) return v.isSet();
}
if (remains_.get() == p) return remains_.isSet();
for (size_t i = 0; i < infoVec_.size(); i++) {
const Info& v = infoVec_[i];
if (v.get() == p) return v.isSet();
}
throw cybozu::Exception("Option:isSet:no assigned var") << pvar;
}
};
} // cybozu