diff --git a/.gitignore b/.gitignore index 32c2539..08410df 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ Sandbox/* *.app dstargateway Tests/dstargateway_tests +DGWRemoteControl/dgwremotecontrol diff --git a/.vscode/launch.json b/.vscode/launch.json index 1bba5af..264e6c4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "(gdb) Lancer", + "name": "(gdb) dstargateway", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DStarGateway/dstargateway", @@ -28,6 +28,30 @@ } ] }, + { + "name": "(gdb) dgwremotecontrol", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWRemoteControl/dgwremotecontrol", + "args": ["--name", "blabla", "F4FXL__B", "link", "never", "DCS208_C"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "Tests", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f14f4ca..7b2b113 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -25,6 +25,19 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "Build DGWRemoteControl", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWRemoteControl/dgwremotecontrol" + ], "group": { "kind": "build", "isDefault": true diff --git a/BaseCommon/optionparser.h b/BaseCommon/optionparser.h new file mode 100644 index 0000000..d5f90d2 --- /dev/null +++ b/BaseCommon/optionparser.h @@ -0,0 +1,699 @@ +//----------------------------------------------------------------------------- +// 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