From f29799d3b185c255f0366a56920c43fa6c7f70a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeromos=20Kov=C3=A1cs?= Date: Wed, 16 Apr 2025 17:13:13 +0200 Subject: [PATCH 01/24] feat(utils): add and use 'to_upper' util --- include/Arg.hpp | 2 +- include/utils.hpp | 7 ++++++- src/Parser.cpp | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 4809ff1..2ea3f92 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -107,7 +107,7 @@ class Arg { // auto_env_name_ // [[nodiscard]] inline const std::string get__auto_env_name() const { // std::string env_name = PROGRAM_NAME() + '_' + this->get__name(); - // std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); + // to_upper(env_name); // return env_name; // } diff --git a/include/utils.hpp b/include/utils.hpp index cfd0dec..387a618 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -1,9 +1,10 @@ #pragma once +#include #include +#include #include #include -#include template inline T ok_or(std::optional opt, E&& err) { @@ -44,3 +45,7 @@ inline void print_indent(std::ostream& os, int indent_level) { for (int i = 0; i < indent_level; ++i) os << '\t'; } + +inline void to_upper(std::string &s) { + std::ranges::transform(s, s.begin(), [](const unsigned char& c) { return std::toupper(c); }); +} diff --git a/src/Parser.cpp b/src/Parser.cpp index dcdfc44..31c44e0 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -63,7 +63,7 @@ void ClapParser::check_env() { for (auto& arg : args_) { if (arg.get__auto_env()) { std::string env_name = this->program_name_ + '_' + arg.get__name(); - std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); + to_upper(env_name); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { arg.set__value(value_from_env); From fcffbc652ab6dab1c6bab45c6414130452f5a880 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 13 May 2025 13:09:00 +0200 Subject: [PATCH 02/24] fix(Parser): removed unneeded value array --- include/Parser.hpp | 2 -- src/Parser.cpp | 20 ++++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index fc8d483..1f0f494 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include class ClapParser { @@ -30,7 +29,6 @@ class ClapParser { friend std::ostream& operator<<(std::ostream& os, const ClapParser& parser); private: std::vector args_; - std::unordered_map values_; std::string program_name_; // Helper methods diff --git a/src/Parser.cpp b/src/Parser.cpp index 31c44e0..ae28942 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -99,14 +99,14 @@ void ClapParser::check_env() { // } // } -void ClapParser::handle_missing_positional(const Arg& arg) { - if (arg.get__is_required()) { - throw std::runtime_error("missing required positional argument: " + arg.get__name()); - } - if (arg.has_default()) { - values_[arg.get__name()] = std::string(arg.get__default_value()); - } -} +// void ClapParser::handle_missing_positional(const Arg& arg) { +// if (arg.get__is_required()) { +// throw std::runtime_error("missing required positional argument: " + arg.get__name()); +// } +// if (arg.has_default()) { +// values_[arg.get__name()] = std::string(arg.get__default_value()); +// } +// } inline bool ClapParser::is_option(const std::string& token) const { return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1); @@ -202,10 +202,6 @@ void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int in } print_indent(os, indent + 1); os << "],\n"; - print_indent(os, indent + 1); os << "values: {\n"; - for (const auto& [key, val] : parser.values_) { - print_indent(os, indent + 2); os << "\"" << key << "\": \"" << val << "\",\n"; - } print_indent(os, indent + 1); os << "}\n"; print_indent(os, indent); os << "}"; From b830bc097ad31f1c18f42375ea1e755d134b2571 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 13 May 2025 13:12:44 +0200 Subject: [PATCH 03/24] fix(tests): test_priority: remove unused import --- tests/test_priority.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_priority.cpp b/tests/test_priority.cpp index 49d3c89..722a4a6 100644 --- a/tests/test_priority.cpp +++ b/tests/test_priority.cpp @@ -2,7 +2,6 @@ #include "Parser.hpp" #include "Arg.hpp" #include "utils.hpp" -#include #include #include #include From 1859a560b4daf8a48f47ae82bf37c7db5ea4e58b Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 13 May 2025 13:14:27 +0200 Subject: [PATCH 04/24] fix(tests): test_combo: debug print parser for logs --- tests/test_combo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_combo.cpp b/tests/test_combo.cpp index b07170d..9aa7ab0 100644 --- a/tests/test_combo.cpp +++ b/tests/test_combo.cpp @@ -27,6 +27,7 @@ int main(int argc, char* argv[]) { auto actual_val = ok_or_throw_str(p.get_one_as("val"), "test argument: 'val' is missing"); auto actual_boolean = ok_or_throw_str(p.get_one_as("flag"), "test argument: 'flag' is missing"); + std::cerr << p << '\n'; assert(actual_val == expected_val); assert(actual_boolean == expected_boolean); return 0; From 9d3efa2a1fe2e765d04d586d043a3c83550216c1 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 13 May 2025 13:19:18 +0200 Subject: [PATCH 05/24] misc(Parser): rename parse_options() -> parse_cli_args() --- include/Parser.hpp | 2 +- src/Parser.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 1f0f494..89c61cc 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -39,7 +39,7 @@ class ClapParser { std::vector get_positional_args() const; void apply_defaults(); - void parse_options(const std::vector& args); + void parse_cli_args(const std::vector& args); void check_env(); void parse_positional_args(const std::vector& args); void handle_missing_positional(const Arg& arg); diff --git a/src/Parser.cpp b/src/Parser.cpp index ae28942..b38e168 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -22,7 +22,7 @@ void ClapParser::parse(const int& argc, char* argv[]) { this->apply_defaults(); this->check_env(); - this->parse_options(args); // parse from cli (argc, argv) + this->parse_cli_args(args); // parse from cli (argc, argv) // parse_positional_args(args); // Validate all arguments that need values received them @@ -36,7 +36,7 @@ void ClapParser::parse(const int& argc, char* argv[]) { void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } -void ClapParser::parse_options(const std::vector& args) { +void ClapParser::parse_cli_args(const std::vector& args) { for (size_t i = 0; i < args.size(); ++i) { const auto& token = args.at(i); From a30b12dd6f6e466af43dc90b6275e9e531c92595 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:03:20 +0200 Subject: [PATCH 06/24] feat(Arg): add a functionality to accept many values --- include/Arg.hpp | 10 ++++++++++ src/Arg.cpp | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/include/Arg.hpp b/include/Arg.hpp index 2ea3f92..d1487a2 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -17,6 +17,7 @@ class Arg { Arg& help(const std::string& help); Arg& required(bool is_required); Arg& is_flag(); + Arg& accepts_many(); Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); Arg& auto_env(); @@ -33,6 +34,7 @@ class Arg { std::string help_; bool is_required_; bool is_flag_; + bool accepts_many_; std::string env_name_; bool auto_env_; // std::string auto_env_name_; @@ -88,6 +90,14 @@ class Arg { this->is_flag_ = takes_value; } + // accepts_many_ + [[nodiscard]] inline bool get__accepts_many() const { + return this->accepts_many_; + } + inline void set__accepts_many(const bool& accepts_many) { + this->accepts_many_ = accepts_many; + } + // env_name_ [[nodiscard]] inline const std::string& get__env_name() const { return this->env_name_; diff --git a/src/Arg.cpp b/src/Arg.cpp index b7766e9..21b4f5e 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -12,6 +12,7 @@ Arg::Arg(const std::string& name) : help_(""), is_required_(false), is_flag_(false), + accepts_many_(false), env_name_(""), auto_env_(false), default_value_(""), @@ -36,6 +37,10 @@ Arg& Arg::is_flag() { default_value_ = "0"; return *this; } +Arg& Arg::accepts_many() { + accepts_many_ = true; + return *this; +} Arg& Arg::default_value(const std::string& default_value) { default_value_ = default_value; return *this; From 9cb542759bb2caac470be3ec1c0bf197020d0461 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:04:13 +0200 Subject: [PATCH 07/24] fix(Arg): print 'accepts_many' as well --- src/Arg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Arg.cpp b/src/Arg.cpp index 21b4f5e..be10381 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -74,6 +74,7 @@ void Arg::print_arg(std::ostream& os, const Arg& arg, int indent) { print_indent(os, indent + 1); os << "help: \"" << arg.help_ << "\",\n"; print_indent(os, indent + 1); os << "required: " << std::boolalpha << arg.is_required_ << ",\n"; print_indent(os, indent + 1); os << "is_flag: " << std::boolalpha << arg.is_flag_ << ",\n"; + print_indent(os, indent + 1); os << "accepts_many: " << std::boolalpha << arg.accepts_many_ << ",\n"; print_indent(os, indent + 1); os << "default: \"" << arg.default_value_ << "\",\n"; print_indent(os, indent + 1); os << "value: "; if (arg.value_) From 176c0f710497f8972f543fb9f082163018229fde Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:05:34 +0200 Subject: [PATCH 08/24] misc(Arg): correct a comment, remove a space --- include/Arg.hpp | 2 +- src/Arg.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index d1487a2..96c0cba 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -82,7 +82,7 @@ class Arg { this->is_required_ = is_required; } - // takes_value_ + // is_flag_ [[nodiscard]] inline bool get__is_flag() const { return this->is_flag_; } diff --git a/src/Arg.cpp b/src/Arg.cpp index be10381..ac18a58 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -32,7 +32,7 @@ Arg& Arg::required(bool is_required) { is_required_ = is_required; return *this; } -Arg& Arg::is_flag() { +Arg& Arg::is_flag() { is_flag_ = true; default_value_ = "0"; return *this; From 1b400400fe6bc3efc50042c1c90c06e63670cf84 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:26:02 +0200 Subject: [PATCH 09/24] fix(Parser): corrected some helper func def and decl --- include/Parser.hpp | 6 +++--- src/Parser.cpp | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 89c61cc..84e58a1 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -32,9 +32,9 @@ class ClapParser { std::string program_name_; // Helper methods - inline bool is_option(const std::string& token) const ; - inline bool is_long_option(const std::string& token) const ; - inline bool is_short_option(const std::string& token) const ; + static bool is_option(const std::string& token); + static bool is_long_option(const std::string& token); + static bool is_short_option(const std::string& token); static std::optional find_arg(ClapParser& parser, const std::string& name); std::vector get_positional_args() const; void apply_defaults(); diff --git a/src/Parser.cpp b/src/Parser.cpp index b38e168..6a5966e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -108,15 +108,17 @@ void ClapParser::check_env() { // } // } - inline bool ClapParser::is_option(const std::string& token) const { - return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1); - } +bool ClapParser::is_option(const std::string& token) { + return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1); +} - inline bool ClapParser::is_long_option(const std::string& token) const { return token.substr(0, 2) == "--"; } +bool ClapParser::is_long_option(const std::string& token) { + return token.substr(0, 2) == "--"; +} - inline bool ClapParser::is_short_option(const std::string& token) const { - return token[0] == '-' && token.size() > 1 && token[1] != '-'; - } +bool ClapParser::is_short_option(const std::string& token) { + return token[0] == '-' && token.size() > 1 && token[1] != '-'; +} void ClapParser::print_help() const { std::cout << "Usage: " << this->program_name_ << " [OPTIONS]"; From f69c2134e9a0a186f3f8ec22114afd0ae3e456f1 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:27:08 +0200 Subject: [PATCH 10/24] fix(Parser): remove old/unused logic --- src/Parser.cpp | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 6a5966e..b0414f9 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -23,7 +23,6 @@ void ClapParser::parse(const int& argc, char* argv[]) { this->apply_defaults(); this->check_env(); this->parse_cli_args(args); // parse from cli (argc, argv) - // parse_positional_args(args); // Validate all arguments that need values received them for (const auto& arg : args_) { @@ -78,36 +77,6 @@ void ClapParser::check_env() { } }; -// void ClapParser::parse_positional_args(const std::vector& args) { -// std::vector positional_args; - -// // Collect positional arguments (tokens not starting with '-') -// for (const auto& token : args) { -// if (!is_option(token)) { -// positional_args.push_back(token); -// } -// } - -// // Assign positional arguments to their respective slots -// auto positional_specs = get_positional_args(); -// for (size_t j = 0; j < positional_specs.size(); ++j) { -// if (j < positional_args.size()) { -// values_[positional_specs[j].name()] = positional_specs[j].convert(positional_args[j]); -// } else { -// handle_missing_positional(positional_specs[j]); -// } -// } -// } - -// void ClapParser::handle_missing_positional(const Arg& arg) { -// if (arg.get__is_required()) { -// throw std::runtime_error("missing required positional argument: " + arg.get__name()); -// } -// if (arg.has_default()) { -// values_[arg.get__name()] = std::string(arg.get__default_value()); -// } -// } - bool ClapParser::is_option(const std::string& token) { return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1); } From 77d2741ae9494b4fb7c1e83557f5c8261b770bf3 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 02:27:59 +0200 Subject: [PATCH 11/24] feat(Parser): WIP added ability to parse positionals from cli --- include/Parser.hpp | 1 + src/Parser.cpp | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 84e58a1..89eddfa 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -42,5 +42,6 @@ class ClapParser { void parse_cli_args(const std::vector& args); void check_env(); void parse_positional_args(const std::vector& args); + static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args); void handle_missing_positional(const Arg& arg); }; diff --git a/src/Parser.cpp b/src/Parser.cpp index b0414f9..a4cc87f 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -3,11 +3,13 @@ #include "utils.hpp" #include +#include #include #include #include #include #include +#include void ClapParser::parse(const int& argc, char* argv[]) { const std::string& raw_program_name = argv[0]; @@ -45,19 +47,33 @@ void ClapParser::parse_cli_args(const std::vector& args) { } auto arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: \'" + token); + if (!arg->get__is_flag()) { - if (i + 1 < args.size() && !is_option(args[i + 1])) { - arg->set__value(args.at(i + 1)); - i++; // Skip the value in the next iteration - } else { - throw std::runtime_error("option '" + token + "' requires a value but none was provided"); - } + ClapParser::parse_value_for_non_flag(arg, i, args); } else { arg->set__value("1"); } } } +void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args) { + if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { + if (arg->get__accepts_many()) { + std::string value; + while (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { + value += args.at(cli_index + 1) + ' '; + cli_index++; + } + arg->set__value(value); + } else { + arg->set__value(args.at(cli_index + 1)); + cli_index++; // Skip the value in the next iteration + } + } else { + throw std::runtime_error("option '" + arg->get__name() + "' requires a value but none was provided"); + } +} + void ClapParser::check_env() { for (auto& arg : args_) { if (arg.get__auto_env()) { From 52f1f060557985a16c427a51a5de22a441b2a3cd Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:01:50 +0200 Subject: [PATCH 12/24] feat(Parser): impl Parse for: int, std::string, bool --- include/Parser.hpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/include/Parser.hpp b/include/Parser.hpp index 89eddfa..302e229 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -3,11 +3,42 @@ #include "Arg.hpp" #include "utils.hpp" +#include #include #include #include #include +template +struct Parse { + static_assert(sizeof(T) == 0, "No Parse specialization defined for this type"); +}; + +template<> +struct Parse { + static std::optional parse(std::string_view s) { + int value; + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); + if (ec == std::errc()) return value; + return std::nullopt; + } +}; + +template<> +struct Parse { + static std::optional parse(std::string_view s) { + return std::string(s); + } +}; + +template<> +struct Parse { + static std::optional parse(std::string_view s) { + auto as_int = Parse::parse(s).value(); + return as_int; + } +}; + class ClapParser { public: void add_arg(const Arg& arg); From 1bc25c8e6dcd94de313e4820023bc984b7665c3d Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:03:09 +0200 Subject: [PATCH 13/24] feat(Parser): added a 'Parseable' c++ concept --- include/Parser.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/Parser.hpp b/include/Parser.hpp index 302e229..4046498 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -39,6 +39,11 @@ struct Parse { } }; +template +concept Parseable = requires(std::string_view s) { + { Parse::parse(s) } -> std::convertible_to>; +}; + class ClapParser { public: void add_arg(const Arg& arg); From 950d6575417c1e5fb4a85cb83f34acb9191176ca Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:04:24 +0200 Subject: [PATCH 14/24] feat(Parser): parser uses way better parsing method! --- include/Parser.hpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 4046498..3caf513 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -50,15 +50,19 @@ class ClapParser { void parse(const int& argc, char* argv[]); void print_help() const; - template inline std::optional get_one_as(const std::string& name) { + template + requires Parseable + inline std::optional get_one_as(const std::string& name) { Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), []{ return std::nullopt; }); - if (auto arg_value = arg->get__value(); arg_value) { - T value; - std::istringstream(*arg_value) >> value; - return value; - } - return std::nullopt; + // if (auto arg_value = arg->get__value(); arg_value) { + // T value; + // std::istringstream(*arg_value) >> value; + // return value; + // } + // return std::nullopt; + + return Parse::parse(arg->get__value().value()); } static void print_parser(std::ostream& os, const ClapParser& parser, int indent); From 0da288c6c481e10bae980d4d91847e0ecb1ee945 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:16:06 +0200 Subject: [PATCH 15/24] feat(Parser): impl Parse for (most) numeric types --- include/Parser.hpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 3caf513..c2d69ff 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -11,16 +11,22 @@ template struct Parse { - static_assert(sizeof(T) == 0, "No Parse specialization defined for this type"); -}; - -template<> -struct Parse { - static std::optional parse(std::string_view s) { - int value; - auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); - if (ec == std::errc()) return value; - return std::nullopt; + static std::optional parse(std::string_view s) { + if constexpr (std::is_integral_v) { + T value; + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); + if (ec == std::errc()) return value; + return std::nullopt; + } + else if constexpr (std::is_floating_point_v) { + T value; + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); + if (ec == std::errc()) return value; + return std::nullopt; + } + else { + static_assert(sizeof(T) == 0, "No Parse specialization defined for this type"); + } } }; From 8545e16826c2f35710ca3792bad7d6e5c0ac659d Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:19:28 +0200 Subject: [PATCH 16/24] fix(Parser): include 'charconv', remove 'cmath' --- include/Parser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index c2d69ff..aea750e 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -3,7 +3,7 @@ #include "Arg.hpp" #include "utils.hpp" -#include +#include #include #include #include From fee7117ea8386988556c63031b66a62350510f5c Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 14 May 2025 03:27:11 +0200 Subject: [PATCH 17/24] fix(Parser): more explicit number casting for MacOS compatibility --- include/Parser.hpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index aea750e..dd7b0c7 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -3,6 +3,11 @@ #include "Arg.hpp" #include "utils.hpp" +#include +#include +#include +#include +#include #include #include #include @@ -18,10 +23,22 @@ struct Parse { if (ec == std::errc()) return value; return std::nullopt; } - else if constexpr (std::is_floating_point_v) { - T value; - auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); - if (ec == std::errc()) return value; + else if constexpr (std::is_same_v) { + char* end = nullptr; + float value = std::strtof(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } + else if constexpr (std::is_same_v) { + char* end = nullptr; + double value = std::strtod(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } + else if constexpr (std::is_same_v) { + char* end = nullptr; + long double value = std::strtold(s.data(), &end); + if (end == s.data() + s.size()) return value; return std::nullopt; } else { From 807243decf29cf330cced114abf45651b3cde15c Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:21:18 +0200 Subject: [PATCH 18/24] feat(Parser): add macros for code generation --- include/Macros.hpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 include/Macros.hpp diff --git a/include/Macros.hpp b/include/Macros.hpp new file mode 100644 index 0000000..85f8df1 --- /dev/null +++ b/include/Macros.hpp @@ -0,0 +1,27 @@ +#pragma once + +#define DEFINE_PARSABLE_BASIC_TYPE(TYPE) \ +template<> \ +struct Parse { \ + static std::optional parse(std::string_view s) { \ + TYPE value; \ + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); \ + if (ec == std::errc()) return value; \ + return std::nullopt; \ + } \ +}; + +#define DEFINE_PARSABLE_FLOAT_TYPE(TYPE, CONVERT_FN) \ +template<> \ +struct Parse { \ + static std::optional parse(std::string_view s) { \ + char* end = nullptr; \ + TYPE value = CONVERT_FN(s.data(), &end); \ + if (end == s.data() + s.size()) return value; \ + return std::nullopt; \ + } \ +}; + +#define DEFINE_GETTER_SETTER(NAME, TYPE) \ + [[nodiscard]] inline const TYPE& get__##NAME() const { return this->NAME##_; } \ + inline void set__##NAME(const TYPE& NAME) { this->NAME##_ = NAME; } From 5a211ac853bcae7ee9734fbbc2d5a7a62bea1b3b Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:24:02 +0200 Subject: [PATCH 19/24] feat(Parser): move 'Parsable' and implementations --> Parsables.hpp --- include/Parsables.hpp | 52 +++++++++++++++++++++++++++++++++++++++++ include/Parser.hpp | 54 +------------------------------------------ 2 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 include/Parsables.hpp diff --git a/include/Parsables.hpp b/include/Parsables.hpp new file mode 100644 index 0000000..18bb7e9 --- /dev/null +++ b/include/Parsables.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "Macros.hpp" + +#include +#include +#include +#include +#include +#include + +template +struct Parse { + static_assert(sizeof(T) == 0, "No Parse specialization defined for this type"); +}; + +template +concept Parseable = requires(std::string_view s) { + { Parse::parse(s) } -> std::convertible_to>; +}; + +// Integer types +DEFINE_PARSABLE_BASIC_TYPE(long long) +DEFINE_PARSABLE_BASIC_TYPE(unsigned long long) +DEFINE_PARSABLE_BASIC_TYPE(int8_t) +DEFINE_PARSABLE_BASIC_TYPE(uint8_t) +DEFINE_PARSABLE_BASIC_TYPE(int16_t) +DEFINE_PARSABLE_BASIC_TYPE(uint16_t) +DEFINE_PARSABLE_BASIC_TYPE(int32_t) +DEFINE_PARSABLE_BASIC_TYPE(uint32_t) +DEFINE_PARSABLE_BASIC_TYPE(int64_t) +DEFINE_PARSABLE_BASIC_TYPE(uint64_t) + +// Floating-point types +DEFINE_PARSABLE_FLOAT_TYPE(float, std::strtof) +DEFINE_PARSABLE_FLOAT_TYPE(double, std::strtod) +DEFINE_PARSABLE_FLOAT_TYPE(long double, std::strtold) + +template<> +struct Parse { + static std::optional parse(std::string_view s) { + return std::string(s.data()); + } +}; + +template<> +struct Parse { + static std::optional parse(std::string_view s) { + auto as_int = Parse::parse(s).value(); + return as_int; + } +}; diff --git a/include/Parser.hpp b/include/Parser.hpp index dd7b0c7..8168d43 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -2,6 +2,7 @@ #include "Arg.hpp" #include "utils.hpp" +#include "Parsables.hpp" #include #include @@ -14,59 +15,6 @@ #include #include -template -struct Parse { - static std::optional parse(std::string_view s) { - if constexpr (std::is_integral_v) { - T value; - auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); - if (ec == std::errc()) return value; - return std::nullopt; - } - else if constexpr (std::is_same_v) { - char* end = nullptr; - float value = std::strtof(s.data(), &end); - if (end == s.data() + s.size()) return value; - return std::nullopt; - } - else if constexpr (std::is_same_v) { - char* end = nullptr; - double value = std::strtod(s.data(), &end); - if (end == s.data() + s.size()) return value; - return std::nullopt; - } - else if constexpr (std::is_same_v) { - char* end = nullptr; - long double value = std::strtold(s.data(), &end); - if (end == s.data() + s.size()) return value; - return std::nullopt; - } - else { - static_assert(sizeof(T) == 0, "No Parse specialization defined for this type"); - } - } -}; - -template<> -struct Parse { - static std::optional parse(std::string_view s) { - return std::string(s); - } -}; - -template<> -struct Parse { - static std::optional parse(std::string_view s) { - auto as_int = Parse::parse(s).value(); - return as_int; - } -}; - -template -concept Parseable = requires(std::string_view s) { - { Parse::parse(s) } -> std::convertible_to>; -}; - class ClapParser { public: void add_arg(const Arg& arg); From 4626943aa6d18e01563817489b6bae301f0072ad Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:25:16 +0200 Subject: [PATCH 20/24] feat(Arg): use macro for getters and setters --- include/Arg.hpp | 114 +++++++----------------------------------------- 1 file changed, 16 insertions(+), 98 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 96c0cba..4b12a8a 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -1,6 +1,7 @@ #pragma once #include "utils.hpp" +#include "Macros.hpp" #include #include @@ -42,78 +43,18 @@ class Arg { std::optional value_; // ----| Getters & Setters |---- - // name_ - [[nodiscard]] inline const std::string& get__name() const { - return this->name_; - } - inline void set__name(const std::string& name) { - this->name_ = name; - } - - // short_ - [[nodiscard]] inline const std::string& get__short_name() const { - return this->short_name_; - } - inline void set__short_name(const std::string& short_name) { - this->short_name_ = short_name; - } - - // long_ - [[nodiscard]] inline const std::string& get__long_name() const { - return this->long_name_; - } - inline void set__long_name(const std::string& long_name) { - this->long_name_ = long_name; - } - - // help_ - [[nodiscard]] inline const std::string& get__help() const { - return this->help_; - } - inline void set__help(const std::string& help) { - this->help_ = help; - } - - // required_ - [[nodiscard]] inline bool get__is_required() const { - return this->is_required_; - } - inline void set__is_required(const bool& is_required) { - this->is_required_ = is_required; - } - - // is_flag_ - [[nodiscard]] inline bool get__is_flag() const { - return this->is_flag_; - } - inline void set__is_flag(const bool& takes_value) { - this->is_flag_ = takes_value; - } - - // accepts_many_ - [[nodiscard]] inline bool get__accepts_many() const { - return this->accepts_many_; - } - inline void set__accepts_many(const bool& accepts_many) { - this->accepts_many_ = accepts_many; - } + DEFINE_GETTER_SETTER(name, std::string) + DEFINE_GETTER_SETTER(short_name, std::string) + DEFINE_GETTER_SETTER(long_name, std::string) + DEFINE_GETTER_SETTER(help, std::string) + DEFINE_GETTER_SETTER(is_required, bool) + DEFINE_GETTER_SETTER(is_flag, bool) + DEFINE_GETTER_SETTER(accepts_many, bool) + DEFINE_GETTER_SETTER(env_name, std::string) + DEFINE_GETTER_SETTER(auto_env, bool) + DEFINE_GETTER_SETTER(default_value, std::string) + DEFINE_GETTER_SETTER(value, std::optional) - // env_name_ - [[nodiscard]] inline const std::string& get__env_name() const { - return this->env_name_; - } - inline void set__env_name(const std::string& env_name) { - this->env_name_ = env_name; - } - - // auto_env_ - [[nodiscard]] inline bool get__auto_env() const { - return this->auto_env_; - } - inline void set__auto_env(const bool& auto_env) { - this->auto_env_ = auto_env; - } - // auto_env_name_ // [[nodiscard]] inline const std::string get__auto_env_name() const { // std::string env_name = PROGRAM_NAME() + '_' + this->get__name(); @@ -121,36 +62,13 @@ class Arg { // return env_name; // } - // default_ - [[nodiscard]] inline const std::string& get__default_value() const { - return this->default_value_; - } - inline void set__default_value(const std::string& default_value) { - this->default_value_ = default_value; - } - - // value_ - [[nodiscard]] inline const std::optional get__value() const { - return this->value_; - } - inline void set__value(const std::string& value) { - this->value_ = value; - } - // ----| Checkers |---- // has_env_ - [[nodiscard]] inline bool has_env() const { - return !this->env_name_.empty(); - } - + [[nodiscard]] inline bool has_env() const { return !this->env_name_.empty(); } + // has_default_ - [[nodiscard]] inline bool has_default() const { - return !this->default_value_.empty(); - } + [[nodiscard]] inline bool has_default() const { return !this->default_value_.empty(); } // has_value_ - [[nodiscard]] inline bool has_value() const { - return this->value_.has_value(); - } - + [[nodiscard]] inline bool has_value() const { return this->value_.has_value(); } }; From a8f24581142dca1330a2bc68391748917c6dc463 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:26:59 +0200 Subject: [PATCH 21/24] fix(Arg): remove unused code --- include/Arg.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 4b12a8a..f3a76b8 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -38,7 +38,6 @@ class Arg { bool accepts_many_; std::string env_name_; bool auto_env_; - // std::string auto_env_name_; std::string default_value_; std::optional value_; @@ -55,13 +54,6 @@ class Arg { DEFINE_GETTER_SETTER(default_value, std::string) DEFINE_GETTER_SETTER(value, std::optional) - // auto_env_name_ - // [[nodiscard]] inline const std::string get__auto_env_name() const { - // std::string env_name = PROGRAM_NAME() + '_' + this->get__name(); - // to_upper(env_name); - // return env_name; - // } - // ----| Checkers |---- // has_env_ [[nodiscard]] inline bool has_env() const { return !this->env_name_.empty(); } From 851903f316d8421c3a0a46d0191dbd4b378727e5 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:30:41 +0200 Subject: [PATCH 22/24] fix(Parser): cleanup old/unused code --- include/Parser.hpp | 9 --------- src/Parser.cpp | 24 ------------------------ 2 files changed, 33 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 8168d43..314b131 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -25,14 +25,6 @@ class ClapParser { requires Parseable inline std::optional get_one_as(const std::string& name) { Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), []{ return std::nullopt; }); - - // if (auto arg_value = arg->get__value(); arg_value) { - // T value; - // std::istringstream(*arg_value) >> value; - // return value; - // } - // return std::nullopt; - return Parse::parse(arg->get__value().value()); } @@ -54,5 +46,4 @@ class ClapParser { void check_env(); void parse_positional_args(const std::vector& args); static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args); - void handle_missing_positional(const Arg& arg); }; diff --git a/src/Parser.cpp b/src/Parser.cpp index a4cc87f..dcaf32c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -107,10 +107,6 @@ bool ClapParser::is_short_option(const std::string& token) { void ClapParser::print_help() const { std::cout << "Usage: " << this->program_name_ << " [OPTIONS]"; - auto positionals = get_positional_args(); - for (const auto& pos : positionals) { - std::cout << " [" << pos.get__name() << "]"; - } std::cout << "\n\nOptions:\n"; for (const auto& arg : args_) { @@ -135,16 +131,6 @@ void ClapParser::print_help() const { std::cout << "--help"; std::cout << "\t" << "Prints this help message"; std::cout << "\n"; - - if (!positionals.empty()) { - std::cout << "\nPositional arguments:\n"; - for (const auto& pos : positionals) { - std::cout << " " << pos.get__name() << "\t" << pos.get__help(); - if (pos.has_default()) - std::cout << " (default: " << pos.get__default_value() << ")"; - std::cout << "\n"; - } - } } // Helper methods @@ -159,16 +145,6 @@ std::optional ClapParser::find_arg(ClapParser& parser, const std::string& return &(*it); } -std::vector ClapParser::get_positional_args() const { - std::vector positional; - for (const auto& arg : args_) { - if (arg.get__short_name().empty() && arg.get__long_name().empty()) { - positional.push_back(arg); - } - } - return positional; -} - void ClapParser::apply_defaults() { for (auto& arg : args_) { if (!arg.has_value() && arg.has_default()) { From 7fac396a542a13d506eef9b2ced3ddc0309db14f Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 01:33:16 +0200 Subject: [PATCH 23/24] fix: remove all unused imports everywhere --- include/Arg.hpp | 2 -- include/Parser.hpp | 4 ---- src/Arg.cpp | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index f3a76b8..593b2a7 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -1,9 +1,7 @@ #pragma once -#include "utils.hpp" #include "Macros.hpp" -#include #include #include #include diff --git a/include/Parser.hpp b/include/Parser.hpp index 314b131..bc45e95 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -5,11 +5,7 @@ #include "Parsables.hpp" #include -#include -#include -#include #include -#include #include #include #include diff --git a/src/Arg.cpp b/src/Arg.cpp index ac18a58..5ed46d4 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -1,6 +1,6 @@ #include "Arg.hpp" +#include "utils.hpp" -#include #include #include #include From 80beb8ceabb5b6fe129537541729f68940eb51a2 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 15 May 2025 02:41:03 +0200 Subject: [PATCH 24/24] fix(Parsables): remove redefinition of i64 and u64 --- include/Parsables.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/Parsables.hpp b/include/Parsables.hpp index 18bb7e9..d8b3ccc 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -20,8 +20,6 @@ concept Parseable = requires(std::string_view s) { }; // Integer types -DEFINE_PARSABLE_BASIC_TYPE(long long) -DEFINE_PARSABLE_BASIC_TYPE(unsigned long long) DEFINE_PARSABLE_BASIC_TYPE(int8_t) DEFINE_PARSABLE_BASIC_TYPE(uint8_t) DEFINE_PARSABLE_BASIC_TYPE(int16_t)