diff --git a/BaseCommon/ProgramArgs.cpp b/BaseCommon/ProgramArgs.cpp new file mode 100644 index 0000000..7370c36 --- /dev/null +++ b/BaseCommon/ProgramArgs.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include "ProgramArgs.h" + +void CProgramArgs::eatArguments(int argc, const char *argv[], std::unordered_map& namedArgs, std::vector& positionalArgs) +{ + assert(argv != nullptr); + + namedArgs.clear(); + positionalArgs.clear(); + + std::vector programArgs; + + // Copy to a vector for easier handling, also skip program name + for(int i = 1;i < argc; i++) { + if(argv[i] != nullptr) { + programArgs.push_back(std::string(argv[i])); + } + } + + // Consume Named args first + for(auto it = programArgs.begin(); it != programArgs.end();) { + if(boost::starts_with(*it, "-")) { + std::string argName = boost::trim_left_copy_if(*it, [] (char c) { return c == '-'; }); + if(!argName.empty()) { + namedArgs[argName] = ""; + it = programArgs.erase(it); + if(it != programArgs.end()) { + namedArgs[argName] = *it; + it = programArgs.erase(it); + } + } + } + else { + it++; + } + } + + //ProgramArgs now only contains pĂ´sitional Args + positionalArgs.assign(programArgs.begin(), programArgs.end()); +} diff --git a/BaseCommon/ProgramArgs.h b/BaseCommon/ProgramArgs.h new file mode 100644 index 0000000..78d47b0 --- /dev/null +++ b/BaseCommon/ProgramArgs.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include + +class CProgramArgs +{ +public: + static void eatArguments(int argc, const char *argv[], std::unordered_map& namedArgs, std::vector& positionalArgs); +}; \ No newline at end of file diff --git a/BaseCommon/optionparser.h b/BaseCommon/optionparser.h deleted file mode 100644 index d5f90d2..0000000 --- a/BaseCommon/optionparser.h +++ /dev/null @@ -1,699 +0,0 @@ -//----------------------------------------------------------------------------- -// 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