//----------------------------------------------------------------------------- // optionparser.h -- A Header-Only commandline argument parser // Author: Luke de Oliveira // License: MIT //----------------------------------------------------------------------------- #ifndef OPTIONPARSER_H_ #define OPTIONPARSER_H_ #include #include #include #include #include #include #include namespace optionparser { // The utils::* namespace contains general utilities not necessarily useful // outside the main scope of the library namespace utils { std::vector split_str(std::string s, const std::string &delimiter = " ") { size_t pos = 0; size_t delimiter_length = delimiter.length(); std::vector vals; while ((pos = s.find(delimiter)) != std::string::npos) { vals.push_back(s.substr(0, pos)); s.erase(0, pos + delimiter_length); } vals.push_back(s); return vals; } std::string stitch_str(const std::vector &text, unsigned max_per_line = 80, const std::string &leading_str = "") { std::vector result; std::string line_value; for (const auto &token : text) { if (line_value.empty()) { line_value = (leading_str + token); continue; } auto hypothetical_line = line_value; hypothetical_line.append(" " + token); if (hypothetical_line.size() > max_per_line) { // In this case, we were better off before result.emplace_back(line_value); line_value = (leading_str + token); } else { line_value = hypothetical_line; } } // Collect the last line since we don't track indices in the loop proper. result.emplace_back(line_value); return std::accumulate( result.begin() + 1, result.end(), result.at(0), [](std::string &s, const std::string &piece) -> std::string { return s + "\n" + piece; }); } } // end namespace utils // Define a thin error for any sort of parser error that arises class ParserError : public std::runtime_error { using std::runtime_error::runtime_error; }; // Enums for Option config enum StorageMode { STORE_TRUE = 0, STORE_VALUE, STORE_MULT_VALUES }; enum OptionType { LONG_OPT = 0, SHORT_OPT, POSITIONAL_OPT, EMPTY_OPT }; // Option class definition class Option { public: Option() = default; std::string help_doc(); std::string &short_flag() { return m_short_flag; } std::string &long_flag() { return m_long_flag; } std::string &pos_flag() { return m_pos_flag; } bool found() { return m_found; } Option &found(bool found) { m_found = found; return *this; } StorageMode mode() { return m_mode; } Option &mode(const StorageMode &mode) { m_mode = mode; return *this; } bool required() { return m_required; } Option &required(bool req) { m_required = req; return *this; } std::string metavar() { std::string formatted_metavar; if (!m_metavar.empty()) { if (m_mode == STORE_TRUE) { return ""; } formatted_metavar = m_metavar; } else { for (const auto &cand : {m_dest, m_pos_flag, m_long_flag, std::string("ARG")}) { if (!cand.empty()) { formatted_metavar = cand.substr(cand.find_first_not_of('-')); std::transform(formatted_metavar.begin(), formatted_metavar.end(), formatted_metavar.begin(), ::toupper); break; } } } if (m_mode == STORE_MULT_VALUES) { formatted_metavar = (formatted_metavar + "1 [" + formatted_metavar + "2, " + formatted_metavar + "3, ...]"); } return formatted_metavar; } Option &metavar(const std::string &mvar) { m_metavar = mvar; return *this; } std::string help() { return m_help; } Option &help(const std::string &help) { m_help = help; return *this; } std::string dest() { return m_dest; } Option &dest(const std::string &dest) { m_dest = dest; return *this; } std::string default_value() { return m_default_value; } Option &default_value(const std::string &default_value) { m_default_value = default_value; return *this; } Option &default_value(const char *default_value) { m_default_value = std::string(default_value); return *this; } template Option &default_value(const T &default_value) { m_default_value = std::to_string(default_value); return *this; } static OptionType get_type(std::string opt); static std::string get_destination(const std::string &first_option, const std::string &second_option); static void validate_option_types(const OptionType &first_option_type, const OptionType &second_option_type); private: bool m_found = false; bool m_required = false; StorageMode m_mode = STORE_TRUE; std::string m_help = ""; std::string m_dest = ""; std::string m_default_value = ""; std::string m_metavar = ""; std::string m_short_flag = ""; std::string m_long_flag = ""; std::string m_pos_flag = ""; }; // Non-inline definitions for Option methods std::string Option::help_doc() { std::string h = " "; if (!m_long_flag.empty()) { h += m_long_flag; if (!m_short_flag.empty()) { h += ", "; } } if (!m_short_flag.empty()) { h += m_short_flag; } if (!m_pos_flag.empty()) { h += m_pos_flag; } auto arg_buf = std::max(h.length() + 1, static_cast(25)); auto help_str = utils::stitch_str(utils::split_str(m_help), arg_buf + 50, std::string(arg_buf, ' ')); char char_buf[h.length() + help_str.length() + 100]; sprintf(char_buf, ("%-" + std::to_string(arg_buf) + "s%s\n").c_str(), h.c_str(), help_str.substr(arg_buf).c_str()); return std::string(char_buf); } OptionType Option::get_type(std::string opt) { if (opt.empty()) { return OptionType::EMPTY_OPT; } if (opt.size() == 2) { if (opt[0] == '-') { return OptionType::SHORT_OPT; } } if (opt.size() > 2) { if (opt[0] == '-' && opt[1] == '-') { return OptionType::LONG_OPT; } } return OptionType::POSITIONAL_OPT; } void Option::validate_option_types(const OptionType &first_option_type, const OptionType &second_option_type) { auto err = [](const std::string &msg) { throw std::runtime_error("Parser inconsistency: " + msg); }; if (first_option_type == OptionType::EMPTY_OPT) { err("Cannot have first option be empty."); } if (first_option_type == OptionType::POSITIONAL_OPT && second_option_type != OptionType::EMPTY_OPT) { err("Positional arguments can only have one option, found non-empty second " "option."); } if (second_option_type == OptionType::POSITIONAL_OPT) { err("Cannot have second option be a positional option."); } } std::string Option::get_destination(const std::string &first_option, const std::string &second_option) { std::string dest; auto first_opt_type = Option::get_type(first_option); auto second_opt_type = Option::get_type(second_option); validate_option_types(first_opt_type, second_opt_type); if (first_opt_type == OptionType::LONG_OPT) { dest = first_option.substr(2); } else if (second_opt_type == OptionType::LONG_OPT) { dest = second_option.substr(2); } else { if (first_opt_type == OptionType::SHORT_OPT) { dest = first_option.substr(1) + "_option"; } else if (second_opt_type == OptionType::SHORT_OPT) { dest = second_option.substr(1) + "_option"; } else { if (first_opt_type == OptionType::POSITIONAL_OPT && second_opt_type == OptionType::EMPTY_OPT) { dest = first_option; } else { std::string msg = "Parser inconsistency error."; throw std::runtime_error(msg); } } } return dest; } // OptionParser class definition class OptionParser { public: explicit OptionParser(std::string description = "", bool create_help = true) : m_options(0), m_description(std::move(description)), m_pos_args_count(1), m_exit_on_failure(true) { if (create_help) { add_option("--help", "-h").help("Display this help message and exit."); } } ~OptionParser() = default; void eat_arguments(unsigned int argc, char const *argv[]); Option &add_option(const std::string &first_option, const std::string &second_option = ""); // We template-specialize these later template T get_value(const std::string &key); void help(); OptionParser &exit_on_failure(bool exit = true); OptionParser &throw_on_failure(bool throw_ = true); private: Option &add_option_internal(const std::string &first_option, const std::string &second_option); void try_to_exit_with_message(const std::string &e); ParserError fail_for_missing_key(const std::string &key); ParserError fail_unrecognized_argument(const std::string &arg); ParserError fail_for_missing_arguments(const std::vector &missing_flags); bool get_value_arg(std::vector &arguments, unsigned int &arg, Option &opt, std::string &flag); bool try_to_get_opt(std::vector &arguments, unsigned int &arg, Option &option, std::string &flag); void check_for_missing_args(); std::vector