/** * Author: Dmitriy Morozov * The interface is heavily influenced by GetOptPP (https://code.google.com/p/getoptpp/). * The parsing logic is from ProgramOptions.hxx (https://github.com/Fytch/ProgramOptions.hxx). * * History: * - 2015-06-01: added Traits<...>::type_string() for long, unsigned long * - ... * - 2018-04-27: replace parsing logic with the one from ProgramOptions.hxx to * make the parser compliant with [GNU Program Argument Syntax * Conventions](https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html) * - 2018-05-11: add dashed_non_option(), to accept arguments that are negative numbers */ #ifndef OPTS_OPTS_H #define OPTS_OPTS_H #include #include #include #include #include #include #include #include #include namespace opts { // Converters template struct Converter { Converter() {} static bool convert(const std::string& val, T& res) { std::istringstream iss(val); iss >> res; return !iss.fail() && iss.eof(); } }; // Type template struct Traits { static std::string type_string() { return "UNKNOWN TYPE"; } }; template<> struct Traits { static std::string type_string() { return "INT"; } }; template<> struct Traits { static std::string type_string() { return "SHORT INT"; } }; template<> struct Traits { static std::string type_string() { return "LONG"; } }; template<> struct Traits { static std::string type_string() { return "UNSIGNED INT"; } }; template<> struct Traits { static std::string type_string() { return "SHORT UNSIGNED INT"; } }; template<> struct Traits { static std::string type_string() { return "UNSIGNED LONG"; } }; template<> struct Traits { static std::string type_string() { return "FLOAT"; } }; template<> struct Traits { static std::string type_string() { return "DOUBLE"; } }; template<> struct Traits { static std::string type_string() { return "STRING"; } }; struct BasicOption { using IsShort = std::function; BasicOption(char s_, std::string l_, std::string default_, std::string type_, std::string help_): s(s_), l(l_), d(default_), t(type_), help(help_) {} virtual ~BasicOption() {} int long_size() const { return l.size() + 1 + t.size(); } void output(std::ostream& out, int max_long) const { out << " "; if (s) out << '-' << s << ", "; else out << " "; out << "--" << l << ' '; if (!t.empty()) out << t; for (int i = long_size(); i < max_long; ++i) out << ' '; out << " " << help; if (!d.empty()) { out << " [default: " << d << "]"; } out << '\n'; } virtual bool flag() const { return false; } virtual bool parse(int argc, char** argv, int& i, int j, IsShort is_short); virtual bool set(std::string arg) =0; char s; std::string l; std::string d; std::string t; std::string help; }; // Option template struct OptionContainer: public BasicOption { OptionContainer(char s_, const std::string& l_, T& var_, const std::string& help_, const std::string& type_ = Traits::type_string()): BasicOption(s_, l_, default_value(var_), type_, help_), var(&var_) {} static std::string default_value(const T& def) { std::ostringstream oss; oss << def; return oss.str(); } bool set(std::string s) override { return Converter::convert(s, *var); } T* var; }; template<> struct OptionContainer: public BasicOption { OptionContainer(char s_, const std::string& l_, bool& var_, const std::string& help_): BasicOption(s_, l_, "", "", help_), var(&var_) { *var = false; } bool parse(int, char**, int&, int, IsShort) override { *var = true; return true; } bool set(std::string) override { return true; } bool flag() const override { return true; } bool* var; }; template struct OptionContainer< std::vector >: public BasicOption { OptionContainer(char s_, const std::string& l_, std::vector& var_, const std::string& help_, const std::string& type_ = "SEQUENCE"): BasicOption(s_, l_, default_value(var_), type_, help_), var(&var_), first(true) { } static std::string default_value(const std::vector& def) { std::ostringstream oss; oss << "("; if (def.size()) oss << def[0]; for (size_t i = 1; i < def.size(); ++i) oss << ", " << def[i]; oss << ")"; return oss.str(); } bool set(std::string s) override { if (first) { var->clear(); first = false; } T x; bool result = Converter::convert(s,x); var->emplace_back(std::move(x)); return result; } std::vector* var; mutable bool first; }; template std::unique_ptr Option(char s, const std::string& l, T& var, const std::string& help) { return std::unique_ptr{new OptionContainer(s, l, var, help)}; } template std::unique_ptr Option(char s, const std::string& l, T& var, const std::string& type, const std::string& help) { return std::unique_ptr{new OptionContainer(s, l, var, help, type)}; } template std::unique_ptr Option(const std::string& l, T& var, const std::string& help) { return std::unique_ptr{new OptionContainer(0, l, var, help)}; } template std::unique_ptr Option(const std::string& l, T& var, const std::string& type, const std::string& help) { return std::unique_ptr{new OptionContainer(0, l, var, help, type)}; } // PosOption template struct PosOptionContainer { PosOptionContainer(T& var_): var(&var_) {} bool parse(std::list& args) const { if (args.empty()) return false; bool result = Converter::convert(args.front(), *var); if (!result) std::cerr << "error: failed to parse " << args.front() << '\n'; args.pop_front(); return result; } T* var; }; template PosOptionContainer PosOption(T& var) { return PosOptionContainer(var); } // Options struct Options { Options(): failed(false) {} inline Options& operator>>(std::unique_ptr opt); template Options& operator>>(const PosOptionContainer& poc); operator bool() { return !failed; } friend std::ostream& operator<<(std::ostream& out, const Options& ops) { int max_long = 0; for (auto& cur : ops.options) { int cur_long = cur->long_size(); if (cur_long > max_long) max_long = cur_long; } out << "Options:\n"; for (auto& cur : ops.options) cur->output(out, max_long); return out; } bool parse(int argc, char** argv); void unrecognized_option(std::string arg) const { std::cerr << "error: unrecognized option " << arg << '\n'; } static bool dashed_non_option(char* arg, BasicOption::IsShort is_short) { return arg[ 0 ] == '-' && (std::isdigit(arg[ 1 ]) || arg[ 1 ] == '.') && !is_short(arg[ 1 ]); } private: std::list args; std::list> options; bool failed; }; bool BasicOption::parse(int argc, char** argv, int& i, int j, IsShort is_short) { char* argument; char* cur_arg = argv[i]; // -v... if (argv[i][j] == '\0') { // -v data if (i + 1 < argc && (argv[i+1][0] != '-' || Options::dashed_non_option(argv[i+1], is_short))) { ++i; argument = argv[i]; } else { std::cerr << "error: cannot find the argument; ignoring " << argv[i] << '\n'; return false; } } else if (argv[i][j] == '=') { // -v=data argument = &argv[i][j+1]; } else if( j == 2 ) { // only for short options // -vdata argument = &argv[i][j]; } else { std::cerr << "error: unexpected character \'" << argv[i][j] << "\' ignoring " << argv[i] << '\n'; return false; } bool result = set(argument); if (!result) std::cerr << "error: failed to parse " << argument << " in " << cur_arg << '\n'; return result; } bool Options::parse(int argc, char** argv) { std::map short_opts; std::map long_opts; for (auto& opt : options) { if (opt->s) short_opts[opt->s] = opt.get(); long_opts[opt->l] = opt.get(); } auto is_short = [&short_opts](char c) -> bool { return short_opts.find(c) != short_opts.end(); }; for (int i = 1; i < argc; ++i) { if( argv[ i ][ 0 ] == '\0' ) continue; if( argv[ i ][ 0 ] != '-' || dashed_non_option(argv[i], is_short)) args.push_back(argv[i]); else { // -... if( argv[ i ][ 1 ] == '\0' ) { // - args.push_back(argv[i]); } else if( argv[ i ][ 1 ] == '-' ) { if( argv[ i ][ 2 ] == '\0' ) { // -- while( ++i < argc ) args.push_back(argv[i]); } else { // --... char* first = &argv[ i ][ 2 ]; char* last = first; for(; *last != '=' && *last != '\0'; ++last); if (first == last) { failed = true; unrecognized_option(argv[i]); } else { auto opt_it = long_opts.find(std::string{first,last}); if (opt_it == long_opts.end()) { failed = true; unrecognized_option(argv[i]); } else { failed |= !opt_it->second->parse(argc, argv, i, last - argv[i], is_short); } } } } else { // -f... auto opt_it = short_opts.find(argv[i][1]); if (opt_it == short_opts.end()) { failed = true; unrecognized_option(argv[i]); } else if (opt_it->second->flag()) { opt_it->second->parse(argc, argv, i, 0, is_short); // arguments are meaningless; just sets the flag // -fgh char c; for(int j = 1; (c = argv[i][j]) != '\0'; ++j) { if (!std::isprint(c) || c == '-') { failed = true; std::cerr << "error: invalid character\'" << c << " ignoring " << &argv[i][j] << '\n'; break; } opt_it = short_opts.find(c); if (opt_it == short_opts.end()) { failed = true; unrecognized_option("-" + std::string(1, c)); continue; } if (!opt_it->second->flag()) { failed = true; std::cerr << "error: non-void options not allowed in option packs; ignoring " << c << '\n'; continue; } opt_it->second->parse(argc, argv, i, 0, is_short); // arguments are meaningless; just sets the flag } } else { failed |= !opt_it->second->parse(argc, argv, i, 2, is_short); } } } } return !failed; } Options& Options::operator>>(std::unique_ptr opt) { options.emplace_back(std::move(opt)); return *this; } template Options& Options::operator>>(const PosOptionContainer& poc) { if (!failed) failed = !poc.parse(args); return *this; } } #endif