#ifndef SHARGS_HPP #define SHARGS_HPP 1 #include #include #include #include #include #include #include class OptionParser { private: using arg_iterator = std::vector::iterator; using option_return = std::optional; using unknown_callback = std::function; struct Option { const std::string lform; const char sform; Option(const std::string &l, const char s) : lform(l), sform(s) {} /** Given an iterator to the arg after this, fetch arguments and stop * when another option switch is encountered. * When fetch fails, return the current it. * When fetch encounters the end, return nullopt. * @return Iterator to next argument * */ virtual option_return parse(arg_iterator it, arg_iterator end) = 0; }; template struct TypedOption : public Option { T &flag; TypedOption(const std::string &l, const char s, T &f) : Option(l, s), flag(f) {} /** Given an iterator to the arg after this, fetch an argument or stop * when an option switch is encountered. * When fetch is stopped prematurely, return the current it. * When fetch encounters the end, return nullopt. * @return Iterator to next argument * */ virtual option_return parse(arg_iterator it, arg_iterator end) { if(it == end) return std::nullopt; std::string_view itval = *it; if(itval[0] == '-') return it; std::stringstream flagstream; flagstream << itval; flagstream >> flag; return ++it; } }; template struct MultipleOption : public Option { std::vector &args; MultipleOption(const std::string &l, const char s, std::vector &a) : Option(l, s), args(a) {} /** Given an iterator to the arg after this, fetch multiple arguments * and stop when an option switch is encountered. * When fetch is stopped prematurely, return the current it. * When fetch encounters the end, return nullopt. * @return Iterator to next argument * */ option_return parse(arg_iterator it, arg_iterator end) { if(it == end) return end; while(it != end && (*it)[0] != '-') { std::string_view itval = *it; std::stringstream argstream; T val; argstream << itval; argstream >> val; args.emplace_back(val); it++; } return it; } }; template struct FlagOption : public TypedOption { const T set; FlagOption(const std::string &l, const char s, T &f, const T set) : TypedOption(l, s, f), set(set) {} /** Given an iterator to the arg after this, set the flag and return it. * @return Iterator to next argument. * */ option_return parse(arg_iterator it, arg_iterator end) { this->flag = set; return it; } }; std::vector> m_options; unknown_callback m_unknown_callback; public: /** Register an option that takes 1 naked argument. * @param longform Full name of the option. preceeded by '--'. * @param shortform Short name of the option. preceeded by '-'. * @param flag Variable to fill when option is encountered. * @return self for chaining. * */ template inline OptionParser & add_option_argument(const std::string &longform, char shortform, T &flag) { m_options.emplace_back( std::make_unique>(longform, shortform, flag)); return *this; } /** Register an option that sets a variable when encountered. * @param longform Full name of the option. preceeded by '--'. * @param shortform Short name of the option. preceeded by '-'. * @param flag Variable to fill when option is encountered. * @param set Value to fill into flag. * @return self for chaining. * */ template inline OptionParser & add_option_flag(const std::string &longform, char shortform, T &flag, const T set) { m_options.emplace_back( std::make_unique>(longform, shortform, flag, set)); return *this; } /** Register an option that takes multiple naked arguments * @param longform Full name of the option. preceeded by '--'. * @param shortform Short name of the option. preceeded by '-'. * @param flag Vector fill with naked argumens. * @return self for chaining. * */ template inline OptionParser & add_option_multiple(const std::string &longform, char shortform, std::vector &args) { m_options.emplace_back( std::make_unique>(longform, shortform, args)); return *this; } /** Add a callback function when parser encounters a flagged argument without * a known option. * @param func Callback to call. * */ inline void on_unknown(unknown_callback func) { m_unknown_callback = func; } /** Loop through argv, isolating any naked args and parsing those with an * option switch ('-' or '--'). * If the args list is empty, it will return an empty vector. * @param argc Number of arguments passed to the program (provided by main) * @param argv NULL-terminated array of string pointers. (provided by main) * @return Vector of all arguments which were not eaten by options. * */ std::vector parse(int argc, char **argv) { /* Convert argc, argv into vector of string_view. */ if(argc <= 1) return std::vector(); std::vector args(argc - 1); for(size_t i = 1; i < argc; i++) { args[i - 1] = std::string_view(argv[i]); } /* Fetch iterator for looping through passed values */ arg_iterator argit = args.begin(); /* We want to notify the program if the user input any naked values or * passed arguments we don't know. */ std::vector naked; std::vector unknown; while(argit != args.end()) { std::vector>::iterator> to_parse; std::string_view arg = *argit; if(arg[0] != '-') { naked.emplace_back(arg); argit++; continue; } auto optionsit = m_options.begin(); if(arg[1] == '-') { /* Look through longform names. */ arg.remove_prefix(2); for(; optionsit != m_options.end(); optionsit++) { if((*optionsit)->lform.empty()) continue; if((*optionsit)->lform == arg) { to_parse.emplace_back(optionsit); break; } } if(optionsit == m_options.end()) { unknown.emplace_back(*argit); } }else{ /* Look through shortform names. * The user should be able to pass as many shortform args * in one switch as they want. */ arg.remove_prefix(1); for(size_t c = 0; c < arg.size(); c++) { /* If the argument is unknown, add it to the unknown list. */ bool valid = false; /* Shortform names have arbitrary flags, so we search * through each character. */ for(optionsit = m_options.begin(); optionsit != m_options.end(); optionsit++) { /* Shortform name matches, add opt to to_parse. */ if(arg[c] == (*optionsit)->sform) { valid = true; to_parse.emplace_back(optionsit); break; } } if(!valid) { unknown.emplace_back((*argit).substr(c + 1, 1)); } } } /* Parse all options in to_parse. */ argit = argit + 1; for(auto it = to_parse.begin(); it != to_parse.end(); it++) { auto parse_ret = (*(*it))->parse(argit, args.end()); /* Parsing encountered args.end()*/ if(parse_ret == std::nullopt) { return naked; } argit = parse_ret.value(); } } /* Go through all unknown args */ if(m_unknown_callback) { for(auto it : unknown) { m_unknown_callback(it); } } return naked; } }; #endif