/** * cola.hpp: a single header only COmmand Line Argument parser. */ #ifndef COLA_HPP #define COLA_HPP #include #include #include #include #include #include #include #include #include namespace cola { // option argument type typedef int integer; typedef double real; typedef std::string string; typedef std::vector ivector; typedef std::vector rvector; typedef std::vector svector; // programming error class logic_error; // runtime error class runtime_error; /** * Command-line option parser. * $ command [option [argument]] */ class parser { public: /** * Represents an option. */ class option; parser(); ~parser(); /** * Define a long option. * * @param name option name. '--' is automatically added. */ option& define(const string& name, const string& description = ""); /** * Define a short option. * * @param name short option name. '-' is automatically added. */ option& define(char name, const string& description = ""); /** * Parse command line options. */ void parse(int argc, const char* const argv[]); /** * Returns true if the name is defined or aliased. */ bool is_passed(const string& name) const; bool is_passed(char name) const; /** * Returns passed argument. if T is different from the type of defined one, throw exception. * @tparam T type of argument. integer, real, string is allowed. */ template T get(const string& name) const; template T get(char name) const; /** * Returns passed argument as vector. if T is different from the type of defined one, throw exception. * @tparam T type of argument. integer, real, string is allowed. */ template std::vector get_vector(const string& name) const; template std::vector get_vector(char name) const; /** * Returns passed arguments. */ svector args() const; /** * Returns arguments which is not defined or not an option. */ svector rest_args() const; /** * Returns command name. */ string command() const; /** * Returns command name and its description. * @param offset offset space from beggining of line */ string descriptions(const string& offset = "") const; /** * Output template usage to ostream. * @param overview overview of this program * @param os output ostream */ void easy_usage(const string& overview, std::ostream& os = std::cerr) const; /** * Returns the executed command. */ string exec_str() const; /** * Reset parsed data. * use this function after parse error. */ void reset(); /** * True when there are undefined long or short options * in parsed argv. */ bool exists_undefined_options() const; private: enum argument_type { FLAG, INTEGER, REAL, STRING, IVECTOR, RVECTOR, SVECTOR }; enum literal_type { SHORT, // -a SHORTS, // -abc LONG, // --option LONG_ARG, // --option= IGNORE, // - IGNORE_AFTER, // -- ELSE // else }; typedef std::vector option_container; typedef std::map option_map; typedef option_container::iterator option_iterator; typedef option_container::const_iterator option_citerator; typedef option_map::iterator lookup_iterator; typedef option_map::const_iterator lookup_citerator; private: option& get_option(const string& name); const option& get_option(const string& name) const; option& get_option(char name); const option& get_option(char name) const; bool is_defined(const string& name) const; bool is_defined(char name) const; void parse_impl(svector::iterator begin, svector::iterator end); literal_type judge(const string& arg) const; void eval_short_option(svector::iterator& it, svector::iterator end); void eval_shorts_option(svector::iterator& it, svector::iterator end); void eval_long_option(svector::iterator& it, svector::iterator end); void eval_long_param_option(svector::iterator& it, svector::iterator end); static string make_arg(const string& name); static string make_arg(char name); void must_be_parsed(const string& func_name) const; void must_not_be_parsed(const string& func_name) const; template static string to_string(const T& value); template static string to_string(const std::vector& value); static string to_string(argument_type type); template static string to_string(); template static T from_string(const string& value); template static std::vector from_string(const string& value, char delim); parser(const option& other); void operator=(const option& other); private: svector args_; svector rest_args_; option_container options_; option_map lookup_; bool parsed_; bool exists_undefined_; }; /** * represents an option. */ class parser::option { public: ~option(); /** * Let this option have an argument. * @tparam T type of argument. integer, real, string are allowed. */ template option& with_arg(T default_value = T()); /** * Let this option have a vector argument. * @tparam T type of argument. integer, real, string are allowed. */ template option& with_arg_vector(const std::vector& default_value = std::vector()); /** * Let this option have a vector argument. * @param default_value string expression of default vector. ex) "1,2,3" * @tparam T type of argument. integer, real, string are allowed. */ template option& with_arg_vector(const string& default_value); /** * Set an alias name of this option. * @param name long option name. '--' is automatically added. */ option& alias(const string& long_name); /** * Set an alias name of this option. * @param name short option name. '-' is automatically added. */ option& alias(char short_name); /** * Set delimiter to parse vector value. */ option& delimiter(char d); /** * Hide this option from usage. */ option& hidden(); private: typedef svector name_container; private: friend class parser; // parser is only class that can generate option. option(const string& name, const string& description, parser& mother); option(char name, const string& description, parser& mother); template T as() const; template std::vector as_vector() const; void check_multi(const string& name) const; void passed(); bool has_arg() const; void parse_arg(const string& arg); void reset(); option(const option& other); void operator=(const option& other); private: parser& mother_; argument_type type_; union { integer value_i_; real value_r_; string* value_s_; ivector* value_iv_; rvector* value_rv_; svector* value_sv_; }; bool is_passed_; string description_; name_container names_; // contains '--', '-' string default_value_; char delim_; // delimiter to parse vector bool hidden_; }; class logic_error: public std::logic_error { public: logic_error(const string& msg): std::logic_error(msg) {} }; class runtime_error: public std::runtime_error { public: runtime_error(const string& msg): std::runtime_error(msg) {} }; // parser ------------------------------------------------------------------------------------ // ctor/dtor --------------------------------------------------------------------------------- inline parser::parser(): parsed_(false), exists_undefined_(false) { } inline parser::~parser() { for(option_iterator it = options_.begin(); it != options_.end(); ++it) { delete *it; } } // define ------------------------------------------------------------------------------------ inline parser::option& parser::define(const string& name, const string& description) { must_not_be_parsed("parser::define"); if(is_defined(name)) { throw logic_error("the option '" + make_arg(name) + "' is already defined"); } if(name.empty()) { throw logic_error("don't pass empty string to parser::define"); } const string desc = description.empty()? "option #" + to_string(options_.size() + 1) : description; if(name.size() == 1) { options_.push_back(new option(name[0], desc, *this)); } else { options_.push_back(new option(name, desc, *this)); } return *options_.back(); } inline parser::option& parser::define(char name, const string& description) { must_not_be_parsed("parser::define"); if(is_defined(name)) { throw logic_error("the option '" + make_arg(name) + "' is already defined"); } if(name == ' ') { throw logic_error("don't pass white space to parser::define"); } const string desc = description.empty()? "option #" + to_string(options_.size() + 1) : description; options_.push_back(new option(name, desc, *this)); return *options_.back(); } // parse ------------------------------------------------------------------------------------ inline void parser::parse(int argc, const char* const argv[]) { if(parsed_) { throw logic_error("call parser::parse() only once"); } args_.assign(argv, argv + argc); parse_impl(args_.begin(), args_.end()); } // passed ------------------------------------------------------------------------------------ inline bool parser::is_passed(const string& name) const { must_be_parsed("parser::is_passed"); return get_option(name).is_passed_; } inline bool parser::is_passed(char name) const { must_be_parsed("parser::is_passed"); return get_option(name).is_passed_; } // get ------------------------------------------------------------------------------------ template inline T parser::get(const string& name) const { must_be_parsed("parser::get"); return get_option(name).as(); } template inline T parser::get(char name) const { must_be_parsed("parser::get"); return get_option(name).as(); } template inline std::vector parser::get_vector(const string& name) const { must_be_parsed("parser::get_vector"); return get_option(name).as_vector(); } template inline std::vector parser::get_vector(char name) const { must_be_parsed("parser::get_vector"); return get_option(name).as_vector(); } // other ------------------------------------------------------------------------------------ inline svector parser::args() const { must_be_parsed("parser::args"); return args_; } inline svector parser::rest_args() const { must_be_parsed("parser::rest_args"); return rest_args_; } inline string parser::command() const { must_be_parsed("parser::command"); return args_[0].substr(args_[0].find_last_of('/') + 1); } inline string parser::descriptions(const string& offset) const { must_be_parsed("parser::descriptions"); svector optheads; std::vector sizes; option_citerator it = options_.begin(); for(; it!=options_.end(); ++it) { std::ostringstream oss; const option& opt = **it; const svector& names = opt.names_; if(opt.hidden_) continue; oss << offset << names[0]; if(names.size() >= 2) oss << " ("; for(svector::const_iterator it = names.begin()+1; it != names.end(); ++it) { oss << *it << (it == names.end() - 1? "" : ", "); } if(names.size() >= 2) oss << ')'; if(opt.has_arg()) { oss << " <" << to_string(opt.type_) << ">"; } optheads.push_back(oss.str()); } std::size_t max_width = 0; svector::const_iterator it2 = optheads.begin(); for(; it2!=optheads.end(); ++it2) { max_width = std::max(max_width, it2->size()); sizes.push_back(it2->size()); } std::ostringstream oss; int i = 0; it = options_.begin(); for(; it!=options_.end(); ++it) { const option& opt = **it; if(opt.hidden_) continue; oss << std::left << std::setw(max_width) << optheads[i] << " | " << opt.description_; if(opt.has_arg()) { oss << " (default = " << opt.default_value_ << ")"; } oss << std::endl; i++; } return oss.str(); } inline void parser::easy_usage(const string& overview, std::ostream& os) const { must_be_parsed("parser::easy_usage"); os << "overview: " << overview << '\n' << "usage: $ " << command() << " [options...]\n" << "options:\n" << descriptions(" ") << std::endl; } inline string parser::exec_str() const { must_be_parsed("parser::exec_str"); svector optheads; std::ostringstream os; for(svector::const_iterator it = args_.begin(); it != args_.end(); ++it) { os << *it << ' '; } return os.str(); } inline void parser::reset() { args_.clear(); rest_args_.clear(); for(option_iterator it = options_.begin(); it != options_.end(); ++it) { (**it).reset(); } parsed_ = false; } inline bool parser::exists_undefined_options() const { return exists_undefined_; } // private -------------------------------------------------------------------------------------------- // get option ----------------------------------------------------------------------------------------- inline parser::option& parser::get_option(const string& name) { const string arg = make_arg(name); lookup_iterator it = lookup_.find(arg); if(it == lookup_.end()) throw logic_error("an option '" + arg + "' is undefined"); return *it->second; } inline const parser::option& parser::get_option(const string& name) const { const string arg = make_arg(name); lookup_citerator it = lookup_.find(arg); if(it == lookup_.end()) throw logic_error("an option '" + arg + "' is undefined"); return *it->second; } inline parser::option& parser::get_option(char name) { const string arg = make_arg(name); lookup_iterator it = lookup_.find(arg); if(it == lookup_.end()) throw logic_error("an option '" + arg + "' is undefined"); return *it->second; } inline const parser::option& parser::get_option(char name) const { const string arg = make_arg(name); lookup_citerator it = lookup_.find(arg); if(it == lookup_.end()) throw logic_error("an option '" + arg + "' is undefined"); return *it->second; } // defined ------------------------------------------------------------------------------------ inline bool parser::is_defined(const string& name) const { return lookup_.count(make_arg(name)) != 0; } inline bool parser::is_defined(char name) const { return lookup_.count(make_arg(name)) != 0; } // parse --------------------------------------------------------------------------------------- inline void parser::parse_impl(svector::iterator begin, svector::iterator end) { svector::iterator it = begin + 1; if(it == end) goto parse_end; while(it != end) { const string& arg = *it; switch(judge(arg)) { case SHORT: // -a eval_short_option(it, end); break; case SHORTS: // -abc eval_shorts_option(it, end); break; case LONG: // --option eval_long_option(it, end); break; case LONG_ARG: // --option= eval_long_param_option(it, end); break; case IGNORE: // - ++it; break; case IGNORE_AFTER: // -- ++it; for(; it != end; ++it) { rest_args_.push_back(*it); } goto parse_end; case ELSE: rest_args_.push_back(arg); ++it; break; default: assert(0); } } parse_end: parsed_ = true; } inline parser::literal_type parser::judge(const string& arg) const { const std::size_t len = arg.size(); if(arg[0] == '-') { if(len == 1) return IGNORE; // arg == "-" if(arg[1] == '-') { // arg == "--something" if(len == 2) return IGNORE_AFTER; // arg == "--" string::size_type is_optarg = arg.find_first_of('='); if(is_optarg == string::npos) { return LONG; // arg == "--something" } else { return LONG_ARG; // arg == "--something=opt_arg" } } else { if(len != 2) return SHORTS; // arg == "-something" else return SHORT; // arg == "-s" } } else { return ELSE; } } // eval each arguments ------------------------------------------------------------------------ inline void parser::eval_short_option(svector::iterator& it, svector::iterator end) { const string& arg = *it++; // it points next of arg "-a" const char c = arg[1]; // after '-' if(!is_defined(c)) { exists_undefined_ = true; rest_args_.push_back(arg); return; } option& opt = get_option(c); if(opt.has_arg()) { if(it == end) { throw runtime_error("the option '"+arg+"' needs argument"); } else { opt.parse_arg(*it++); } } opt.passed(); } inline void parser::eval_shorts_option(svector::iterator& it, svector::iterator) { const string& arg = *it++; // it points next of arg "-abc" // -a123 option& opt1 = get_option(arg[1]); if(opt1.has_arg()) { opt1.parse_arg(string(arg.begin()+2, arg.end())); opt1.passed(); return; } // -abc for(svector::size_type j=1; j inline string parser::to_string(const T& value) { std::stringstream ss; ss << value; return ss.str(); } template<> inline string parser::to_string(const string& value) { std::stringstream ss; ss << '"' + value + '"'; return ss.str(); } template inline string parser::to_string(const std::vector& value) { std::stringstream ss; ss << "["; for(typename std::vector::const_iterator it = value.begin(); it != value.end(); ++it) { ss << *it << ((it == value.end()-1)? "" : ", "); } ss << "]"; return ss.str(); } template<> inline string parser::to_string(const std::vector& value) { std::stringstream ss; ss << "["; for(std::vector::const_iterator it = value.begin(); it != value.end(); ++it) { ss << '"' + *it + '"' << ((it == value.end()-1)? "" : ", "); } ss << "]"; return ss.str(); } template<> inline string parser::to_string() { return "integer"; } template<> inline string parser::to_string() { return "real"; } template<> inline string parser::to_string() { return "string"; } template<> inline string parser::to_string() { return "vector"; } template<> inline string parser::to_string() { return "vector"; } template<> inline string parser::to_string() { return "vector"; } inline string parser::to_string(argument_type type) { switch(type) { case FLAG: return "flag"; case INTEGER: return "integer"; case REAL: return "real"; case STRING: return "string"; case IVECTOR: return "vector"; case RVECTOR: return "vector"; case SVECTOR: return "vector"; default: assert(0); } } template inline T parser::from_string(const string& value) { std::stringstream ss; T ret; if(!(ss << value && ss >> ret && ss.eof())) { throw runtime_error("cast failed: '"+value+"' to "+to_string()); } return ret; } template<> inline string parser::from_string(const string& value) { return value; } template inline std::vector parser::from_string(const string& value, char delim) { std::vector ret; std::stringstream ss(value); string item; while(std::getline(ss, item, delim)) { if(!item.empty()) { ret.push_back(from_string(item)); } } return ret; } // parser::option ------------------------------------------------------------------------------ // dtor ----------------------------------------------------------------------------------- inline parser::option::~option() { switch(type_) { case FLAG: break; case INTEGER: break; case REAL: break; case STRING: delete value_s_; value_s_ = NULL; break; case IVECTOR: delete value_iv_; value_iv_ = NULL; break; case RVECTOR: delete value_rv_; value_rv_ = NULL; break; case SVECTOR: delete value_sv_; value_sv_ = NULL; break; default: assert(0); } } // with_arg --------------------------------------------------------------------------------- template<> inline parser::option& parser::option::with_arg(integer default_value) { mother_.must_not_be_parsed("parser::option::with_arg"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_i_ = default_value; default_value_ = to_string(default_value); type_ = INTEGER; return *this; } template<> inline parser::option& parser::option::with_arg(real default_value) { mother_.must_not_be_parsed("parser::option::with_arg"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_r_ = default_value; default_value_ = to_string(default_value); type_ = REAL; return *this; } template<> inline parser::option& parser::option::with_arg(string default_value) { mother_.must_not_be_parsed("parser::option::with_arg"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_s_ = new string(default_value); default_value_ = to_string(default_value); type_ = STRING; return *this; } // with_arg_vector(vector) ----------------------------------------------------------------------------------- template<> inline parser::option& parser::option::with_arg_vector(const std::vector& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_iv_ = new ivector(default_value); default_value_ = to_string(default_value); type_ = IVECTOR; return *this; } template<> inline parser::option& parser::option::with_arg_vector(const std::vector& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_rv_ = new rvector(default_value); default_value_ = to_string(default_value); type_ = RVECTOR; return *this; } template<> inline parser::option& parser::option::with_arg_vector(const std::vector& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_sv_ = new svector(default_value); default_value_ = to_string(default_value); type_ = SVECTOR; return *this; } // with_arg_string(string) ----------------------------------------------------------------------------------- template<> inline parser::option& parser::option::with_arg_vector(const string& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_iv_ = new ivector(); *value_iv_ = from_string(default_value, delim_); default_value_ = '[' + default_value + ']'; type_ = IVECTOR; return *this; } template<> inline parser::option& parser::option::with_arg_vector(const string& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_rv_ = new rvector(); *value_rv_ = from_string(default_value, delim_); default_value_ = '[' + default_value + ']'; type_ = RVECTOR; return *this; } template<> inline parser::option& parser::option::with_arg_vector(const string& default_value) { mother_.must_not_be_parsed("parser::option::with_arg_vector"); if(type_ != FLAG) throw logic_error("parser::option::with_arg() cal be called only once"); value_sv_ = new svector(); *value_sv_ = from_string(default_value, delim_); default_value_ = '[' + default_value + ']'; type_ = SVECTOR; return *this; } // alias ----------------------------------------------------------------------------------- inline parser::option& parser::option::alias(const string& long_name) { mother_.must_not_be_parsed("parser::option::alias"); if(long_name.size() == 1) { alias(long_name[0]); return *this; } const string name = make_arg(long_name); check_multi(name); names_.push_back(name); mother_.lookup_.insert(std::make_pair(name, this)); return *this; } inline parser::option& parser::option::alias(char short_name) { mother_.must_not_be_parsed("parser::option::alias"); const string name = make_arg(short_name); check_multi(name); names_.push_back(name); mother_.lookup_.insert(std::make_pair(name, this)); return *this; } // others ----------------------------------------------------------------------------------- inline parser::option& parser::option::delimiter(char d) { mother_.must_not_be_parsed("parser::option::delimiter"); delim_ = d; return *this; } inline parser::option& parser::option::hidden() { hidden_ = true; return *this; } // private ----------------------------------------------------------------------------------- // ctor ----------------------------------------------------------------------------------- inline parser::option::option(const string& name, const string& description, parser& mother): mother_(mother), type_(FLAG), value_s_(NULL), is_passed_(false), description_(description), delim_(','), hidden_(false) { alias(name); } inline parser::option::option(char name, const string& description, parser& mother): mother_(mother), type_(FLAG), value_s_(NULL), is_passed_(false), description_(description), delim_(','), hidden_(false) { alias(name); } // as ---------------------------------------------------------------------------------------- template<> inline integer parser::option::as() const { mother_.must_be_parsed("parser::option::as"); if(type_ != INTEGER) throw logic_error("argument type of '"+names_[0]+"' is not integer"); return value_i_; } template<> inline real parser::option::as() const { mother_.must_be_parsed("parser::option::as"); if(type_ != REAL) throw logic_error("argument type of '"+names_[0]+"' is not real"); return value_r_; } template<> inline string parser::option::as() const { mother_.must_be_parsed("parser::option::as"); if(type_ != STRING) throw logic_error("argument type of '"+names_[0]+"' is not string"); return *value_s_; } // as_vector ----------------------------------------------------------------------------------- template<> inline ivector parser::option::as_vector() const { mother_.must_be_parsed("parser::option::as_vector"); if(type_ != IVECTOR) throw logic_error("argument type of '"+names_[0]+"' is not vector"); return *value_iv_; } template<> inline rvector parser::option::as_vector() const { mother_.must_be_parsed("parser::option::as_vector"); if(type_ != RVECTOR) throw logic_error("argument type of '"+names_[0]+"' is not vector"); return *value_rv_; } template<> inline svector parser::option::as_vector() const { mother_.must_be_parsed("parser::option::as_vector"); if(type_ != SVECTOR) throw logic_error("argument type of '"+names_[0]+"' is not vector"); return *value_sv_; } template<> inline ivector parser::option::as() const { mother_.must_be_parsed("parser::option::as"); return as_vector(); } template<> inline rvector parser::option::as() const { mother_.must_be_parsed("parser::option::as"); return as_vector(); } template<> inline svector parser::option::as() const { mother_.must_be_parsed("parser::option::as"); return as_vector(); } // utility -------------------------------------------------------------------------------------------- inline bool parser::option::has_arg() const { return type_ != FLAG; } inline void parser::option::passed() { is_passed_ = true; } inline void parser::option::check_multi(const string& name) const { if(std::find(names_.begin(), names_.end(), name) != names_.end()) { throw logic_error("'"+name+"' is already defined"); } } inline void parser::option::parse_arg(const string& arg) { switch(type_) { case INTEGER: value_i_ = from_string(arg); break; case REAL: value_r_ = from_string(arg); break; case STRING: *value_s_ = arg; break; case IVECTOR: *value_iv_ = from_string(arg, delim_); break; case RVECTOR: *value_rv_ = from_string(arg, delim_); break; case SVECTOR: *value_sv_ = from_string(arg, delim_); break; default: throw logic_error("the option '"+names_[0]+"' don't take an argument."); } } inline void parser::option::reset() { is_passed_ = false; } } // namespace cola #endif // COLA_HPP