From 7fbd662e126572933462d2e0263e3289d3092eda Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 22 May 2025 08:17:34 +0200 Subject: [PATCH 01/29] fix(Parser): throw, when user wants a value of a non-specified, but non-required arg --- include/Parser.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 2c29de0..588996c 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -20,7 +20,10 @@ class ClapParser { requires Parseable std::optional get_one_as(const std::string& name) { Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; }); - return Parse::parse(arg->get__value().value()); + + return Parse::parse( + ok_or_throw_str(arg->get__value(), "value for option: " + quote(arg->get__name()) + " is missing") + ); } static void print_parser(std::ostream& os, const ClapParser& parser, int indent); From 998c11591bf4f957981103e5a55addbc9e53bb19 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 22 May 2025 08:33:58 +0200 Subject: [PATCH 02/29] feat(Parser): introduce much improved value parsing (--val=abc and alike) --- include/Parser.hpp | 4 +++- src/Parser.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 588996c..466fa24 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -40,8 +40,10 @@ class ClapParser { static std::optional find_arg(ClapParser& parser, const std::string& name); void apply_defaults(); - void parse_cli_args(const std::vector& args); + void parse_cli_args(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); + static void parse_value_for_flag(Arg* arg, size_t& cli_index, const std::vector& args); + static void analyze_token(std::string& token, size_t& cli_index, std::vector& args); }; diff --git a/src/Parser.cpp b/src/Parser.cpp index cdfb31c..ff3eea9 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -37,21 +37,26 @@ void ClapParser::parse(const int& argc, char* argv[]) { void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } -void ClapParser::parse_cli_args(const std::vector& args) { +void ClapParser::parse_cli_args(std::vector& args) { for (size_t i = 0; i < args.size(); ++i) { - const auto& token = args.at(i); + std::string token = args.at(i); + // TODO this could be better with string view contains? if (token == "--help" || token == "-h") { print_help(); exit(0); } - auto* arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: \'" + token); + // solve --opt="value" stuff + ClapParser::analyze_token(token, i, args); + + + auto* arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: " + quote(token)); if (!arg->get__is_flag()) { ClapParser::parse_value_for_non_flag(arg, i, args); } else { - arg->set__value("1"); + ClapParser::parse_value_for_flag(arg, i, args); } } } @@ -74,6 +79,42 @@ void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std } } +void ClapParser::parse_value_for_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 (args.at(cli_index + 1) == "true" || args.at(cli_index + 1) == "1") { + arg->set__value("1"); + cli_index++; + } else if (args.at(cli_index + 1) == "false" || args.at(cli_index + 1) == "0") { + arg->set__value("0"); + cli_index++; + } else { + throw std::runtime_error("boolean option " + quote(arg->get__name()) + " strictly takes: true|false|1|0 (got: " + args.at(cli_index + 1) + ")"); + } + } else { + arg->set__value("1"); + } +} + +void ClapParser::analyze_token(std::string& token, size_t& cli_index, std::vector& args) { + if (token.contains('=')) { + std::cerr << "'=' found, separating token ( "; + const auto middle = token.find('='); + + std::string token_name = token.substr(0, middle); + std::string token_value = token.substr(middle + 1); + if (token_value.empty()) { + throw std::runtime_error("value not specified after '='"); + } + + args.at(cli_index) = token_value; + cli_index--; + + token = token_name; + } else { + std::cerr << "ending token analysis, left alone\n"; + } +} + void ClapParser::check_env() { for (auto& arg : args_) { if (arg.get__auto_env()) { From 969429e46ea4f1ca99034d19957ed8a949960dac Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 22 May 2025 08:35:20 +0200 Subject: [PATCH 03/29] fix(Parser): removed unused import --- include/Parser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 466fa24..ad9f026 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include From c9cba4d508f74289aea1c22d951d394111d4a5d8 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 22 May 2025 08:39:47 +0200 Subject: [PATCH 04/29] misc(Parser): don't debug to std::cerr, proper logging soon... --- src/Parser.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index ff3eea9..8f3ee01 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -38,8 +38,16 @@ void ClapParser::parse(const int& argc, char* argv[]) { void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } void ClapParser::parse_cli_args(std::vector& args) { + // std::cerr << "\nstart cli arg parsing\n"; + // std::cerr << "args vector: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; + for (size_t i = 0; i < args.size(); ++i) { std::string token = args.at(i); + // std::cerr << "\ngoing through args: size=" << args.size() << ", current token=" << quote(token) << ", index=" << i << "\n"; // TODO this could be better with string view contains? if (token == "--help" || token == "-h") { @@ -49,19 +57,29 @@ void ClapParser::parse_cli_args(std::vector& args) { // solve --opt="value" stuff ClapParser::analyze_token(token, i, args); + // std::cerr << "args vector AGAIN: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; auto* arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: " + quote(token)); if (!arg->get__is_flag()) { + // std::cerr << "\nparsing non-flag\n"; ClapParser::parse_value_for_non_flag(arg, i, args); } else { + // std::cerr << "\nparsing flag\n"; ClapParser::parse_value_for_flag(arg, i, args); } } + std::cerr << "cli arg parsing done\n"; + std::cerr << *this << "\n"; } void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args) { + // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { if (arg->get__accepts_many()) { std::string value; @@ -69,8 +87,10 @@ void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std value += args.at(cli_index + 1) + ' '; cli_index++; } + // std::cerr << "value " << value << "\n"; arg->set__value(value); } else { + // std::cerr << "value " << args.at(cli_index + 1) << "\n"; arg->set__value(args.at(cli_index + 1)); cli_index++; // Skip the value in the next iteration } @@ -80,38 +100,56 @@ void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std } void ClapParser::parse_value_for_flag(Arg* arg, size_t& cli_index, const std::vector& args) { + // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { + // std::cerr << "found flag with value in cli value: ("; if (args.at(cli_index + 1) == "true" || args.at(cli_index + 1) == "1") { + // std::cerr << args.at(cli_index + 1) << ")\n"; arg->set__value("1"); cli_index++; } else if (args.at(cli_index + 1) == "false" || args.at(cli_index + 1) == "0") { + // std::cerr << args.at(cli_index + 1) << ")\n"; arg->set__value("0"); cli_index++; } else { + // std::cerr << "WRONG VALUE, throwing\n"; throw std::runtime_error("boolean option " + quote(arg->get__name()) + " strictly takes: true|false|1|0 (got: " + args.at(cli_index + 1) + ")"); } } else { + // std::cerr << "no value for flag, fallback to 1\n"; arg->set__value("1"); } } void ClapParser::analyze_token(std::string& token, size_t& cli_index, std::vector& args) { + // std::cerr << "\nstarting token analysis for: " << quote(token) << "\n"; + // std::cerr << "[WARNING]: might mess up arg vector!\n"; if (token.contains('=')) { - std::cerr << "'=' found, separating token ( "; + // std::cerr << "'=' found, separating token ( "; const auto middle = token.find('='); std::string token_name = token.substr(0, middle); + // std::cerr << "name: " << quote(token_name) << ", "; std::string token_value = token.substr(middle + 1); if (token_value.empty()) { throw std::runtime_error("value not specified after '='"); } + // std::cerr << "value: " << quote(token_value) << " )\n"; args.at(cli_index) = token_value; + // std::cerr << "TEST: " << cli_index << "\n"; cli_index--; + // std::cerr << "args vector: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; + + // std::cerr << "ending token analysis\n"; token = token_name; } else { - std::cerr << "ending token analysis, left alone\n"; + // std::cerr << "ending token analysis, left alone\n"; } } From fca738fed84b4806b375c3026db14d7fb71e2437 Mon Sep 17 00:00:00 2001 From: csboo Date: Sat, 31 May 2025 18:42:52 +0200 Subject: [PATCH 05/29] feat(Macros): improved number parsing algorithms --- include/Macros.hpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 49e1049..f389318 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -1,27 +1,23 @@ #pragma once -#define DEFINE_PARSABLE_BASIC_TYPE(TYPE) \ +#define DEFINE_PARSABLE_INTEGER_TYPE(TYPE) \ template <> \ struct Parse { \ static std::optional parse(std::string_view s) { \ - TYPE value; \ + TYPE value = 0; \ auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); \ - if (ec == std::errc()) return value; \ - return std::nullopt; \ + return ec == std::errc() ? std::nullopt : std::optional{value}; \ } \ }; -#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_PARSABLE_FLOAT_TYPE(TYPE) \ + template <> \ + struct Parse { \ + static std::optional parse(std::string_view s) { \ + TYPE value = 0; \ + auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value, std::chars_format::scientific); \ + return ec == std::errc() ? std::nullopt : std::optional{value}; \ + } \ }; #define DEFINE_GETTER_SETTER(NAME, TYPE) \ From 76a27839fedc410796ffcecd99b0eba2e444d605 Mon Sep 17 00:00:00 2001 From: csboo Date: Sun, 1 Jun 2025 16:24:43 +0200 Subject: [PATCH 06/29] feat(Parser): whole-new, safe, templated, arg-creating logic" --- include/Arg.hpp | 64 --------------- include/Macros.hpp | 49 +++++++++++- include/Parsables.hpp | 24 +++--- include/Parser.hpp | 20 +++-- include/arg/Arg.hpp | 172 ++++++++++++++++++++++++++++++++++++++++ include/arg/BaseArg.hpp | 54 +++++++++++++ meson.build | 5 +- src/Arg.cpp | 90 --------------------- src/BaseArg.cpp | 3 + src/Parser.cpp | 57 +++++++------ 10 files changed, 331 insertions(+), 207 deletions(-) delete mode 100644 include/Arg.hpp create mode 100644 include/arg/Arg.hpp create mode 100644 include/arg/BaseArg.hpp delete mode 100644 src/Arg.cpp create mode 100644 src/BaseArg.cpp diff --git a/include/Arg.hpp b/include/Arg.hpp deleted file mode 100644 index 49b3275..0000000 --- a/include/Arg.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "Macros.hpp" - -class Arg { - public: - Arg(std::string name); - - Arg& short_name(const std::string& short_name); - Arg& long_name(const std::string& long_name); - 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(); - - static void print_arg(std::ostream& os, const Arg& arg, int indent); - friend std::ostream& operator<<(std::ostream& os, const Arg& arg); - - private: - friend class ClapParser; - - std::string name_; - std::string short_name_; - std::string long_name_; - std::string help_; - bool is_required_; - bool is_flag_; - bool accepts_many_; - std::string env_name_; - bool auto_env_; - std::string default_value_; - std::optional value_; - - // ----| Getters & Setters |---- - 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) - - // ----| Checkers |---- - // has_env_ - [[nodiscard]] bool has_env() const { return !this->env_name_.empty(); } - - // has_default_ - [[nodiscard]] bool has_default() const { return !this->default_value_.empty(); } - - // has_value_ - [[nodiscard]] bool has_value() const { return this->value_.has_value(); } -}; diff --git a/include/Macros.hpp b/include/Macros.hpp index f389318..66fd02d 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -20,6 +20,49 @@ } \ }; -#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; } +#define DEFINE_GETTER_SETTER_OVERRIDE(NAME, TYPE) \ + [[nodiscard]] inline const TYPE& get__##NAME() const override { return this->NAME##_; } \ + inline void set__##NAME(const TYPE& NAME) override { this->NAME##_ = NAME; } + +#define DEFINE_GETTER_SETTER_VIRTUAL(NAME, TYPE) \ + [[nodiscard]] virtual const TYPE& get__##NAME() const = 0; \ + virtual void set__##NAME(const TYPE& NAME) = 0; + +#define ARG_USER_BOOL_FUNCTION_SAFE(NAME, TARGET_STATE, VALUE, ERROR_MSG, ...) \ + [[nodiscard]] inline Arg<__VA_ARGS__> NAME() { \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = VALUE; \ + Arg<__VA_ARGS__> next = std::move(*this); \ + return next; \ + } + +#define ARG_USER_CUSTOM_FUNCTION_SAFE(NAME, TYPE, TARGET_STATE, ERROR_MSG, ...) \ + [[nodiscard]] inline Arg<__VA_ARGS__> NAME(const TYPE NAME) { \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = std::move(NAME); \ + Arg<__VA_ARGS__> next = *this; \ + return next; \ + } + +#define ARG_STATE_CONVERSION_CTOR(ALLOW_DIFF_IDX) \ + template ) && \ + (ALLOW_DIFF_IDX != 1 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 2 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 3 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 4 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 5 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 6 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 7 || std::is_same_v) && \ + (ALLOW_DIFF_IDX != 8 || std::is_same_v)>* = nullptr> \ + Arg(const Arg& other) { \ + this->name_ = other.name_; \ + this->long_name_ = other.long_name_; \ + this->help_ = other.help_; \ + this->is_required_ = other.is_required_; \ + this->is_flag_ = other.is_flag_; \ + this->accepts_many_ = other.accepts_many_; \ + this->value_ = other.value_; \ + this->auto_env_ = other.auto_env_; \ + } diff --git a/include/Parsables.hpp b/include/Parsables.hpp index 343545f..e1b666d 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -20,23 +20,23 @@ concept Parseable = requires(std::string_view s) { }; // Integer types -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) +DEFINE_PARSABLE_INTEGER_TYPE(int8_t) +DEFINE_PARSABLE_INTEGER_TYPE(uint8_t) +DEFINE_PARSABLE_INTEGER_TYPE(int16_t) +DEFINE_PARSABLE_INTEGER_TYPE(uint16_t) +DEFINE_PARSABLE_INTEGER_TYPE(int32_t) +DEFINE_PARSABLE_INTEGER_TYPE(uint32_t) +DEFINE_PARSABLE_INTEGER_TYPE(int64_t) +DEFINE_PARSABLE_INTEGER_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) +DEFINE_PARSABLE_FLOAT_TYPE(float) +DEFINE_PARSABLE_FLOAT_TYPE(double) +DEFINE_PARSABLE_FLOAT_TYPE(long double) template <> struct Parse { - static std::optional parse(std::string_view s) { return std::string(s.data()); } + static std::optional parse(std::string_view s) { return std::string(s.data(), s.data() + s.size()); } }; template <> diff --git a/include/Parser.hpp b/include/Parser.hpp index ad9f026..4ffeccf 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -1,25 +1,30 @@ #pragma once +#include #include +#include #include +#include #include #include #include -#include "Arg.hpp" +#include "arg/Arg.hpp" #include "Parsables.hpp" +#include "arg/BaseArg.hpp" #include "utils.hpp" class ClapParser { public: - void add_arg(const Arg& arg); + template + void add_arg(Arg arg) { args_.emplace_back(std::make_unique>(std::move(arg))); } void parse(const int& argc, char* argv[]); void print_help() const; template requires Parseable std::optional get_one_as(const std::string& name) { - Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; }); + auto *arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; }); return Parse::parse( ok_or_throw_str(arg->get__value(), "value for option: " + quote(arg->get__name()) + " is missing") @@ -30,20 +35,21 @@ class ClapParser { friend std::ostream& operator<<(std::ostream& os, const ClapParser& parser); private: - std::vector args_; + std::vector> args_; std::string program_name_; // Helper methods 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); + static std::optional find_arg(ClapParser& parser, const std::string& arg_name); + // static std::optional find_arg(ClapParser& parser, const std::string& name); void apply_defaults(); void parse_cli_args(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); - static void parse_value_for_flag(Arg* arg, size_t& cli_index, const std::vector& args); + static void parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector& args); + static void parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std::vector& args); static void analyze_token(std::string& token, size_t& cli_index, std::vector& args); }; diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp new file mode 100644 index 0000000..66cda7e --- /dev/null +++ b/include/arg/Arg.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Macros.hpp" +#include "BaseArg.hpp" +#include "utils.hpp" + +// === Type state flags === +struct NotSet {}; +struct Set {}; + +// sth +template +struct CountMismatches; + +template +struct CountMismatches { + static constexpr int value = (!std::is_same_v) + CountMismatches::value; +}; + +template<> +struct CountMismatches<> { + static constexpr int value = 0; +}; + +// === Builder class with full type-state enforcement === +template +class Arg final : public BaseArg { + public: + Arg(std::string name) { + name_ = std::move(name); + long_name_ = name_; + } + + ~Arg() override = default; + + static inline std::unique_ptr builder(const std::string& name) { + return std::make_unique>(name); + } + + template + friend class Arg; + + template + Arg(const Arg& other) requires (CountMismatches< + ShortName, OSN, + LongName, OLN, + Help, OH, + IsRequired, OIR, + IsFlag, OIF, + AcceptsMany, OAM, + DefaultValue, ODV, + FromEnv, OFE, + AutoEnv, OAE + >::value == 1) { + name_ = other.name_; + long_name_ = other.long_name_; + help_ = other.help_; + is_required_ = other.is_required_; + is_flag_ = other.is_flag_; + accepts_many_ = other.accepts_many_; + value_ = other.value_; + auto_env_ = other.auto_env_; + } + + ARG_USER_CUSTOM_FUNCTION_SAFE(short_name, std::string, ShortName, "Error: don't call short_name() more than once!", + Set, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE(long_name, std::string, LongName, "Error: don't call long_name() more than once!", + ShortName, Set, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE(help, std::string, Help, "Error: don't call help() more than once!", ShortName, + LongName, Set, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(is_required, IsRequired, true, "Error: don't call is_required() more than once!", + ShortName, LongName, Help, Set, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(is_flag, IsFlag, true, "Error: don't call is_flag() more than once!", ShortName, + LongName, Help, IsRequired, Set, AcceptsMany, DefaultValue, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(accepts_many, AcceptsMany, true, "Error: don't call accepts_many() more than once!", + ShortName, LongName, Help, IsRequired, IsFlag, Set, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE(default_value, std::string, DefaultValue, + "Error: don't call default_value() more than once!", ShortName, LongName, Help, + IsRequired, IsFlag, AcceptsMany, Set, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(auto_env, AutoEnv, true, "Error: don't call auto_env() more than once!", ShortName, + LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, Set) + + // ARG_USER_BOOL_FUNCTION_SAFE(from_env, FromEnv, false, "Error: don't call from_env() more than once!") + + private: + friend class ClapParser; + + // std::string name_; + // std::string short_name_; + // std::string long_name_; + // std::string help_; + // bool is_required_; + // bool is_flag_; + // bool accepts_many_; + // std::string env_name_; + // bool auto_env_; + // std::string default_value_; + // std::optional value_; + + void print_arg(std::ostream& os, int indent = 0) const override { + print_indent(os, indent); + os << "Arg {\n"; + + print_indent(os, indent + 1); + os << "name: \"" << name_ << "\",\n"; + print_indent(os, indent + 1); + os << "short: \"" << short_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "long: \"" << long_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "help: \"" << help_ << "\",\n"; + print_indent(os, indent + 1); + os << "required: " << std::boolalpha << is_required_ << ",\n"; + print_indent(os, indent + 1); + os << "is_flag: " << std::boolalpha << is_flag_ << ",\n"; + print_indent(os, indent + 1); + os << "accepts_many: " << std::boolalpha << accepts_many_ << ",\n"; + print_indent(os, indent + 1); + os << "default: \"" << default_value_ << "\",\n"; + print_indent(os, indent + 1); + os << "value: "; + if (value_) { + os << "\"" << value_.value() << "\""; + } else { + os << "std::nullopt"; + } + os << '\n'; + + print_indent(os, indent); + os << "}"; + } + + friend std::ostream& operator<<(std::ostream& os, const BaseArg& arg); + + // ----| Getters & Setters |---- + DEFINE_GETTER_SETTER_OVERRIDE(name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(short_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(long_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(help, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(is_required, bool) + DEFINE_GETTER_SETTER_OVERRIDE(is_flag, bool) + DEFINE_GETTER_SETTER_OVERRIDE(accepts_many, bool) + DEFINE_GETTER_SETTER_OVERRIDE(env_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(auto_env, bool) + DEFINE_GETTER_SETTER_OVERRIDE(default_value, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(value, std::optional) + + // ----| Checkers |---- + // has_env_ + [[nodiscard]] bool has_env() const override { return !this->env_name_.empty(); } + + // has_default_ + [[nodiscard]] bool has_default() const override { return !this->default_value_.empty(); } + + // has_value_ + [[nodiscard]] bool has_value() const override { return this->value_.has_value(); } +}; diff --git a/include/arg/BaseArg.hpp b/include/arg/BaseArg.hpp new file mode 100644 index 0000000..845e963 --- /dev/null +++ b/include/arg/BaseArg.hpp @@ -0,0 +1,54 @@ +// BaseArg.hpp +#pragma once + +#include "Macros.hpp" +#include +#include + +class BaseArg { + public: + BaseArg() = default; + virtual ~BaseArg(); + + virtual void print_arg(std::ostream& os, int indent = 0) const = 0; + + DEFINE_GETTER_SETTER_VIRTUAL(name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(short_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(long_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(help, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(is_required, bool) + DEFINE_GETTER_SETTER_VIRTUAL(is_flag, bool) + DEFINE_GETTER_SETTER_VIRTUAL(accepts_many, bool) + DEFINE_GETTER_SETTER_VIRTUAL(env_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(auto_env, bool) + DEFINE_GETTER_SETTER_VIRTUAL(default_value, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(value, std::optional) + + // ----| Checkers |---- + // has_env_ + [[nodiscard]] virtual bool has_env() const = 0; + + // has_default_ + [[nodiscard]] virtual bool has_default() const = 0; + + // has_value_ + [[nodiscard]] virtual bool has_value() const = 0; + + protected: + std::string name_; + std::string short_name_; + std::string long_name_; + std::string help_; + bool is_required_{}; + bool is_flag_{}; + bool accepts_many_{}; + std::string env_name_; + bool auto_env_{}; + std::string default_value_; + std::optional value_ = std::nullopt; +}; + +inline std::ostream& operator<<(std::ostream& os, const BaseArg& arg) { + arg.print_arg(os, 0); + return os; +} diff --git a/meson.build b/meson.build index ee36240..7d85bb1 100644 --- a/meson.build +++ b/meson.build @@ -10,14 +10,15 @@ project('claplusplus', 'cpp', ) # Add include directory for headers -include_dir = include_directories('include') +include_dir = include_directories('include', 'include/arg') # Build executable directly from all necessary sources executable('claPlusPlus', sources: [ 'src/example.cpp', 'src/Arg.cpp', - 'src/Parser.cpp' + 'src/Parser.cpp', + 'src/BaseArg.cpp' ], include_directories: include_dir, install: false diff --git a/src/Arg.cpp b/src/Arg.cpp deleted file mode 100644 index b4b3811..0000000 --- a/src/Arg.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Arg.hpp" - -#include - -#include -#include - -#include "utils.hpp" - -Arg::Arg(std::string name) - : name_(std::move(name)), - long_name_(this->name_), - is_required_(false), - is_flag_(false), - accepts_many_(false), - auto_env_(false), - value_(std::nullopt) {} - -// Setters -Arg& Arg::short_name(const std::string& short_name) { - short_name_ = short_name; - return *this; -} -Arg& Arg::help(const std::string& help) { - help_ = help; - return *this; -} -Arg& Arg::required(bool is_required) { - is_required_ = is_required; - return *this; -} -Arg& Arg::is_flag() { - is_flag_ = true; - 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; -} -Arg& Arg::from_env(const char* env_var_name) { - this->env_name_ = env_var_name; - return *this; -}; -Arg& Arg::auto_env() { - this->auto_env_ = true; - return *this; -}; - -void Arg::print_arg(std::ostream& os, const Arg& arg, int indent) { - print_indent(os, indent); - os << "Arg {\n"; - - print_indent(os, indent + 1); - os << "name: \"" << arg.name_ << "\",\n"; - print_indent(os, indent + 1); - os << "short: \"" << arg.short_name_ << "\",\n"; - print_indent(os, indent + 1); - os << "long: \"" << arg.long_name_ << "\",\n"; - 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_) { - os << "\"" << arg.value_.value() << "\""; - } else { - os << "std::nullopt"; - } - os << '\n'; - - print_indent(os, indent); - os << "}"; -} - -std::ostream& operator<<(std::ostream& os, const Arg& arg) { - Arg::print_arg(os, arg, 0); - return os; -} diff --git a/src/BaseArg.cpp b/src/BaseArg.cpp new file mode 100644 index 0000000..43c27cf --- /dev/null +++ b/src/BaseArg.cpp @@ -0,0 +1,3 @@ +#include "arg/BaseArg.hpp" + +BaseArg::~BaseArg() = default; diff --git a/src/Parser.cpp b/src/Parser.cpp index 8f3ee01..fe93aa6 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -8,7 +8,8 @@ #include #include -#include "Arg.hpp" +#include "arg/Arg.hpp" +#include "arg/BaseArg.hpp" #include "utils.hpp" void ClapParser::parse(const int& argc, char* argv[]) { @@ -29,14 +30,12 @@ void ClapParser::parse(const int& argc, char* argv[]) { // Validate all arguments that need values received them for (const auto& arg : args_) { // std::cerr << arg << "\n\n\n"; - if (arg.get__is_required() && !arg.has_value()) { - throw std::runtime_error("argument '" + arg.get__name() + "' is required"); + if (arg->get__is_required() && !arg->has_value()) { + throw std::runtime_error("argument '" + arg->get__name() + "' is required"); } } } -void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } - void ClapParser::parse_cli_args(std::vector& args) { // std::cerr << "\nstart cli arg parsing\n"; // std::cerr << "args vector: ["; @@ -78,7 +77,7 @@ void ClapParser::parse_cli_args(std::vector& args) { std::cerr << *this << "\n"; } -void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args) { +void ClapParser::parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector& args) { // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { if (arg->get__accepts_many()) { @@ -99,7 +98,7 @@ void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std } } -void ClapParser::parse_value_for_flag(Arg* arg, size_t& cli_index, const std::vector& args) { +void ClapParser::parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std::vector& args) { // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { // std::cerr << "found flag with value in cli value: ("; @@ -155,18 +154,18 @@ void ClapParser::analyze_token(std::string& token, size_t& cli_index, std::vecto void ClapParser::check_env() { for (auto& arg : args_) { - if (arg.get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg.get__name(); + if (arg->get__auto_env()) { + std::string env_name = this->program_name_ + '_' + arg->get__name(); to_upper(env_name); auto* value_from_env = std::getenv(env_name.c_str()); if (value_from_env != nullptr) { - arg.set__value(value_from_env); + arg->set__value(value_from_env); } } - if (arg.has_env()) { - auto* value_from_env = std::getenv(arg.get__env_name().c_str()); + if (arg->has_env()) { + auto* value_from_env = std::getenv(arg->get__env_name().c_str()); if (value_from_env != nullptr) { - arg.set__value(value_from_env); + arg->set__value(value_from_env); } } } @@ -187,17 +186,17 @@ void ClapParser::print_help() const { std::cout << "\n\nOptions:\n"; for (const auto& arg : args_) { - arg.get__short_name().empty() ? std::cout << " " : std::cout << " -" << arg.get__short_name() << ", "; - std::cout << "--" << arg.get__long_name(); - std::cout << "\t" << arg.get__help(); - if (arg.has_default()) { - std::cout << " (default: " << arg.get__default_value() << ")"; + arg->get__short_name().empty() ? std::cout << " " : std::cout << " -" << arg->get__short_name() << ", "; + std::cout << "--" << arg->get__long_name(); + std::cout << "\t" << arg->get__help(); + if (arg->has_default()) { + std::cout << " (default: " << arg->get__default_value() << ")"; } - if (arg.has_env()) { - std::cout << " [env: " << arg.get__env_name() << "]"; + if (arg->has_env()) { + std::cout << " [env: " << arg->get__env_name() << "]"; } - if (arg.get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg.get__name(); + if (arg->get__auto_env()) { + std::string env_name = this->program_name_ + '_' + arg->get__name(); to_upper(env_name); std::cout << " [def.env: " << env_name << "]"; } @@ -211,21 +210,21 @@ void ClapParser::print_help() const { } // Helper methods -std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { - auto it = std::ranges::find_if(parser.args_, [&](Arg& arg) { - return ("--" + arg.get__long_name() == arg_name || "-" + arg.get__short_name() == arg_name); +std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { + auto it = std::ranges::find_if(parser.args_, [&](std::unique_ptr& arg) { + return ("--" + arg->get__long_name() == arg_name || "-" + arg->get__short_name() == arg_name); }); if (it == parser.args_.end()) { return std::nullopt; } - return &(*it); + return it->get(); } void ClapParser::apply_defaults() { for (auto& arg : args_) { - if (!arg.has_value() && arg.has_default()) { - arg.set__value(arg.get__default_value()); + if (!arg->has_value() && arg->has_default()) { + arg->set__value(arg->get__default_value()); } } } @@ -240,7 +239,7 @@ void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int in print_indent(os, indent + 1); os << "args: [\n"; for (const auto& arg : parser.args_) { - Arg::print_arg(os, arg, indent + 2); + arg->print_arg(os, indent + 2); os << ",\n"; } print_indent(os, indent + 1); From ebabdab81af06b69099a050b85d37015e1a129c8 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 3 Jun 2025 20:51:34 +0200 Subject: [PATCH 07/29] fix(meson): update build file --- meson.build | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 7d85bb1..766a218 100644 --- a/meson.build +++ b/meson.build @@ -16,7 +16,6 @@ include_dir = include_directories('include', 'include/arg') executable('claPlusPlus', sources: [ 'src/example.cpp', - 'src/Arg.cpp', 'src/Parser.cpp', 'src/BaseArg.cpp' ], @@ -25,7 +24,7 @@ executable('claPlusPlus', ) parser_lib = static_library('clap_parser', - sources: ['src/Parser.cpp', 'src/Arg.cpp'], + sources: ['src/Parser.cpp', 'src/BaseArg.cpp'], include_directories: include_dir, install: false ) From c16af1d6f4f4e9994caf295fd4306264ffb02d57 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 3 Jun 2025 20:52:44 +0200 Subject: [PATCH 08/29] fix(Parsables): parsing bool should return a std::optional --- include/Parsables.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/Parsables.hpp b/include/Parsables.hpp index e1b666d..f102cd5 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -42,7 +41,6 @@ struct Parse { template <> struct Parse { static std::optional parse(std::string_view s) { - auto as_int = Parse::parse(s).value(); - return as_int; + return Parse::parse(s); } }; From 57b8c3d2c03a2455a7d52f763024b5667fcc51a9 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 3 Jun 2025 21:02:12 +0200 Subject: [PATCH 09/29] fix(Parser): don't store leadign hyphens of options --- src/Parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index fe93aa6..e05482c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -56,6 +56,11 @@ void ClapParser::parse_cli_args(std::vector& args) { // solve --opt="value" stuff ClapParser::analyze_token(token, i, args); + if (token.starts_with("--")) { + token = token.substr(2); + } else if (token.starts_with("-")) { + token = token.substr(1); + } // std::cerr << "args vector AGAIN: ["; // for (const auto& i : args) { // std::cerr << quote(i) << " "; @@ -212,7 +217,7 @@ void ClapParser::print_help() const { // Helper methods std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { auto it = std::ranges::find_if(parser.args_, [&](std::unique_ptr& arg) { - return ("--" + arg->get__long_name() == arg_name || "-" + arg->get__short_name() == arg_name); + return (arg->get__long_name() == arg_name || arg->get__short_name() == arg_name); }); if (it == parser.args_.end()) { From 9658da9122c89b9ae30590504e6e7058890378db Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 3 Jun 2025 21:07:00 +0200 Subject: [PATCH 10/29] feat(Arg): simplify template for constructor with other 'Arg(other Arg)' --- include/arg/Arg.hpp | 48 ++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index 66cda7e..8a842bb 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -15,20 +15,6 @@ struct NotSet {}; struct Set {}; -// sth -template -struct CountMismatches; - -template -struct CountMismatches { - static constexpr int value = (!std::is_same_v) + CountMismatches::value; -}; - -template<> -struct CountMismatches<> { - static constexpr int value = 0; -}; - // === Builder class with full type-state enforcement === template friend class Arg; - template - Arg(const Arg& other) requires (CountMismatches< - ShortName, OSN, - LongName, OLN, - Help, OH, - IsRequired, OIR, - IsFlag, OIF, - AcceptsMany, OAM, - DefaultValue, ODV, - FromEnv, OFE, - AutoEnv, OAE - >::value == 1) { - name_ = other.name_; - long_name_ = other.long_name_; - help_ = other.help_; - is_required_ = other.is_required_; - is_flag_ = other.is_flag_; - accepts_many_ = other.accepts_many_; - value_ = other.value_; - auto_env_ = other.auto_env_; + template + Arg(const Arg& other) { + // name_ = std::move(other.name_); + short_name_ = std::move(other.short_name_); + long_name_ = std::move(other.long_name_); + help_ = std::move(other.help_); + is_required_ = std::move(other.is_required_); + is_flag_ = std::move(other.is_flag_); + default_value_ = std::move(other.default_value_); + accepts_many_ = std::move(other.accepts_many_); + value_ = std::move(other.value_); + auto_env_ = std::move(other.auto_env_); + env_name_ = std::move(other.env_name_); } ARG_USER_CUSTOM_FUNCTION_SAFE(short_name, std::string, ShortName, "Error: don't call short_name() more than once!", From 09c16ab5560ae48375fb5d4b7c575ce805575b20 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:35:17 +0200 Subject: [PATCH 11/29] fix(Macros): logical return value of Parse struct corrected --- include/Macros.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 66fd02d..319044a 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -6,7 +6,7 @@ static std::optional parse(std::string_view s) { \ TYPE value = 0; \ auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); \ - return ec == std::errc() ? std::nullopt : std::optional{value}; \ + return ec == std::errc() ? std::optional{value} : std::nullopt; \ } \ }; @@ -16,7 +16,7 @@ static std::optional parse(std::string_view s) { \ TYPE value = 0; \ auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value, std::chars_format::scientific); \ - return ec == std::errc() ? std::nullopt : std::optional{value}; \ + return ec == std::errc() ? std::optional{value} : std::nullopt; \ } \ }; From 46c3f6e5830180b92a4aa0d54e4ebc959d06fa13 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:36:58 +0200 Subject: [PATCH 12/29] fix(Macros): removed that constructor generator that's not used any more --- include/Macros.hpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 319044a..1bbe8c7 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -44,25 +44,4 @@ return next; \ } -#define ARG_STATE_CONVERSION_CTOR(ALLOW_DIFF_IDX) \ - template ) && \ - (ALLOW_DIFF_IDX != 1 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 2 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 3 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 4 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 5 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 6 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 7 || std::is_same_v) && \ - (ALLOW_DIFF_IDX != 8 || std::is_same_v)>* = nullptr> \ - Arg(const Arg& other) { \ - this->name_ = other.name_; \ - this->long_name_ = other.long_name_; \ - this->help_ = other.help_; \ - this->is_required_ = other.is_required_; \ - this->is_flag_ = other.is_flag_; \ - this->accepts_many_ = other.accepts_many_; \ - this->value_ = other.value_; \ - this->auto_env_ = other.auto_env_; \ } From 7f0d6fd8c1f35768f4c2e605d43bbbaf48087c8e Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:37:59 +0200 Subject: [PATCH 13/29] fix(Macros): less boilerplate with 'auto' --- include/Macros.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 1bbe8c7..760fc14 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -29,7 +29,7 @@ virtual void set__##NAME(const TYPE& NAME) = 0; #define ARG_USER_BOOL_FUNCTION_SAFE(NAME, TARGET_STATE, VALUE, ERROR_MSG, ...) \ - [[nodiscard]] inline Arg<__VA_ARGS__> NAME() { \ + [[nodiscard]] inline auto NAME() { \ static_assert(std::is_same_v, ERROR_MSG); \ this->NAME##_ = VALUE; \ Arg<__VA_ARGS__> next = std::move(*this); \ @@ -37,7 +37,7 @@ } #define ARG_USER_CUSTOM_FUNCTION_SAFE(NAME, TYPE, TARGET_STATE, ERROR_MSG, ...) \ - [[nodiscard]] inline Arg<__VA_ARGS__> NAME(const TYPE NAME) { \ + [[nodiscard]] inline auto NAME(TYPE NAME) { \ static_assert(std::is_same_v, ERROR_MSG); \ this->NAME##_ = std::move(NAME); \ Arg<__VA_ARGS__> next = *this; \ From 2cc69be43ff3fb82382808f37a7ce966ad7df156 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:48:14 +0200 Subject: [PATCH 14/29] fix(utils)!: removed ok_or(), wrong (probably) dangerous implementation --- include/utils.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/include/utils.hpp b/include/utils.hpp index 73e4093..1069a81 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -6,13 +6,11 @@ #include #include -template -inline T ok_or(std::optional opt, E&& err) { - if (!opt) { - std::forward(err)(); - } - return *opt; -} +// this is really dangerous and broken :D +// template +// inline T ok_or(std::optional opt, E&& err) { +// return opt.has_value() ? *opt : std::forward(err)(); +// } template inline T ok_or_throw_str(std::optional opt, const std::string& err) { From a7dbeaf66b99c1dbb76e63ee667c5fafb5137322 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:49:54 +0200 Subject: [PATCH 15/29] fix(Parser): remove ok_or(), slighly cleaner impl --- include/Parser.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 4ffeccf..9f6c506 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -24,11 +24,10 @@ class ClapParser { template requires Parseable std::optional get_one_as(const std::string& name) { - auto *arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; }); - - return Parse::parse( - ok_or_throw_str(arg->get__value(), "value for option: " + quote(arg->get__name()) + " is missing") - ); + auto *arg = ok_or_throw_str(ClapParser::find_arg(*this, name), "no option with name: " + quote(name) + " was found"); + auto value = ok_or_throw_str(arg->get__value(), "value for option: " + quote(arg->get__long_name()) + " is missing"); + + return Parse::parse(value); } static void print_parser(std::ostream& os, const ClapParser& parser, int indent); From b9b41301e677e647e654401fab29ea19f59e91a3 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:55:52 +0200 Subject: [PATCH 16/29] misc(Arg): moved 'print_arg()' to 'BaseArg' --- include/arg/Arg.hpp | 33 --------------------------------- include/arg/BaseArg.hpp | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index 8a842bb..d17e6ac 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -90,39 +90,6 @@ class Arg final : public BaseArg { // std::string default_value_; // std::optional value_; - void print_arg(std::ostream& os, int indent = 0) const override { - print_indent(os, indent); - os << "Arg {\n"; - - print_indent(os, indent + 1); - os << "name: \"" << name_ << "\",\n"; - print_indent(os, indent + 1); - os << "short: \"" << short_name_ << "\",\n"; - print_indent(os, indent + 1); - os << "long: \"" << long_name_ << "\",\n"; - print_indent(os, indent + 1); - os << "help: \"" << help_ << "\",\n"; - print_indent(os, indent + 1); - os << "required: " << std::boolalpha << is_required_ << ",\n"; - print_indent(os, indent + 1); - os << "is_flag: " << std::boolalpha << is_flag_ << ",\n"; - print_indent(os, indent + 1); - os << "accepts_many: " << std::boolalpha << accepts_many_ << ",\n"; - print_indent(os, indent + 1); - os << "default: \"" << default_value_ << "\",\n"; - print_indent(os, indent + 1); - os << "value: "; - if (value_) { - os << "\"" << value_.value() << "\""; - } else { - os << "std::nullopt"; - } - os << '\n'; - - print_indent(os, indent); - os << "}"; - } - friend std::ostream& operator<<(std::ostream& os, const BaseArg& arg); // ----| Getters & Setters |---- diff --git a/include/arg/BaseArg.hpp b/include/arg/BaseArg.hpp index 845e963..14d459c 100644 --- a/include/arg/BaseArg.hpp +++ b/include/arg/BaseArg.hpp @@ -2,6 +2,7 @@ #pragma once #include "Macros.hpp" +#include "utils.hpp" #include #include @@ -10,7 +11,40 @@ class BaseArg { BaseArg() = default; virtual ~BaseArg(); - virtual void print_arg(std::ostream& os, int indent = 0) const = 0; + void print_arg(std::ostream& os, int indent = 0) const { + print_indent(os, indent); + os << "Arg {\n"; + + print_indent(os, indent + 1); + os << "short: \"" << short_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "long: \"" << long_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "help: \"" << help_ << "\",\n"; + print_indent(os, indent + 1); + os << "required: " << std::boolalpha << is_required_ << ",\n"; + print_indent(os, indent + 1); + os << "is_flag: " << std::boolalpha << is_flag_ << ",\n"; + print_indent(os, indent + 1); + os << "accepts_many: " << std::boolalpha << accepts_many_ << ",\n"; + print_indent(os, indent + 1); + os << "default: \"" << default_value_ << "\",\n"; + print_indent(os, indent + 1); + os << "auto_env: \"" << std::boolalpha << auto_env_ << "\",\n"; + print_indent(os, indent + 1); + os << "env_name: \"" << env_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "value: "; + if (value_) { + os << "\"" << value_.value() << "\""; + } else { + os << "std::nullopt"; + } + os << '\n'; + + print_indent(os, indent); + os << "}"; + } DEFINE_GETTER_SETTER_VIRTUAL(name, std::string) DEFINE_GETTER_SETTER_VIRTUAL(short_name, std::string) From 2480a5483a4ab813a7ae51fb29f9437b5a5866b8 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 10:57:50 +0200 Subject: [PATCH 17/29] misc(Arg): cleaned up some unused stuff --- include/arg/Arg.hpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index d17e6ac..e6f3066 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -28,10 +28,6 @@ class Arg final : public BaseArg { ~Arg() override = default; - static inline std::unique_ptr builder(const std::string& name) { - return std::make_unique>(name); - } - template friend class Arg; @@ -78,18 +74,6 @@ class Arg final : public BaseArg { private: friend class ClapParser; - // std::string name_; - // std::string short_name_; - // std::string long_name_; - // std::string help_; - // bool is_required_; - // bool is_flag_; - // bool accepts_many_; - // std::string env_name_; - // bool auto_env_; - // std::string default_value_; - // std::optional value_; - friend std::ostream& operator<<(std::ostream& os, const BaseArg& arg); // ----| Getters & Setters |---- From 99ac7488c1c001b26a71cc4c65272b7d414b5a13 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:00:10 +0200 Subject: [PATCH 18/29] misc(Parser): comment out debuginfo --- src/Parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index e05482c..8399b5c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -78,8 +78,8 @@ void ClapParser::parse_cli_args(std::vector& args) { ClapParser::parse_value_for_flag(arg, i, args); } } - std::cerr << "cli arg parsing done\n"; - std::cerr << *this << "\n"; + // std::cerr << "cli arg parsing done\n"; + // std::cerr << *this << "\n"; } void ClapParser::parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector& args) { From ee40e36ede8448eff4c0e0fa7df1f1fd95ada5cb Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:02:26 +0200 Subject: [PATCH 19/29] misc(Parser): 4 lines -> 1 line, who wrote it that way? --- src/Parser.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 8399b5c..0088f9e 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -207,11 +207,7 @@ void ClapParser::print_help() const { } std::cout << "\n"; } - std::cout << " "; - std::cout << "-h" << ", "; - std::cout << "--help"; - std::cout << "\t" << "Prints this help message"; - std::cout << "\n"; + std::cout << " " << "-h" << ", " << "--help" << "\t" << "Prints this help message" << "\n"; } // Helper methods From 19404a809046bbbd17af758d9ba64f454ed2ccb7 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:04:06 +0200 Subject: [PATCH 20/29] fix(Parser): flags are (again) false when not specified --- src/Parser.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Parser.cpp b/src/Parser.cpp index 0088f9e..2c71450 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -224,6 +224,9 @@ std::optional ClapParser::find_arg(ClapParser& parser, const std::stri void ClapParser::apply_defaults() { for (auto& arg : args_) { + if (arg->get__is_flag() && !arg->has_default()) { + arg->set__default_value("0"); // flags are false by default always + } if (!arg->has_value() && arg->has_default()) { arg->set__value(arg->get__default_value()); } From ac34f2b895e7b88dd2bcca89f145aa47b625e4ec Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:07:13 +0200 Subject: [PATCH 21/29] feat(Arg): re-introduce from_env() --- include/arg/Arg.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index e6f3066..ed85592 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -69,7 +69,14 @@ class Arg final : public BaseArg { ARG_USER_BOOL_FUNCTION_SAFE(auto_env, AutoEnv, true, "Error: don't call auto_env() more than once!", ShortName, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, Set) - // ARG_USER_BOOL_FUNCTION_SAFE(from_env, FromEnv, false, "Error: don't call from_env() more than once!") + // this is expanded for its different functionality + [[nodiscard]] inline auto from_env(std::string env_var_name) { + static_assert(std ::is_same_v, "Error: don't call auto_env() more than once!"); + this->env_name_ = std::move(env_var_name); + Arg next = + std ::move(*this); + return next; + } private: friend class ClapParser; From c151c89511afd5b88f254af67cd53ef248b4f1fd Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:15:30 +0200 Subject: [PATCH 22/29] feat(Arg, Parser)!: starting to merge .name() into .long_name(), (they are the same) --- include/arg/Arg.hpp | 8 ++------ include/arg/BaseArg.hpp | 3 +-- src/Parser.cpp | 12 ++++++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index ed85592..616201c 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -21,11 +21,7 @@ template class Arg final : public BaseArg { public: - Arg(std::string name) { - name_ = std::move(name); - long_name_ = name_; - } - + Arg() = default; ~Arg() override = default; templateget__is_required() && !arg->has_value()) { - throw std::runtime_error("argument '" + arg->get__name() + "' is required"); + throw std::runtime_error("argument '" + arg->get__long_name() + "' is required"); } } } @@ -99,7 +99,7 @@ void ClapParser::parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const 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"); + throw std::runtime_error("option '" + arg->get__long_name() + "' requires a value but none was provided"); } } @@ -117,7 +117,7 @@ void ClapParser::parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std cli_index++; } else { // std::cerr << "WRONG VALUE, throwing\n"; - throw std::runtime_error("boolean option " + quote(arg->get__name()) + " strictly takes: true|false|1|0 (got: " + args.at(cli_index + 1) + ")"); + throw std::runtime_error("boolean option " + quote(arg->get__long_name()) + " strictly takes: true|false|1|0 (got: " + args.at(cli_index + 1) + ")"); } } else { // std::cerr << "no value for flag, fallback to 1\n"; @@ -160,7 +160,7 @@ void ClapParser::analyze_token(std::string& token, size_t& cli_index, std::vecto void ClapParser::check_env() { for (auto& arg : args_) { if (arg->get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg->get__name(); + std::string env_name = this->program_name_ + '_' + arg->get__long_name(); to_upper(env_name); auto* value_from_env = std::getenv(env_name.c_str()); if (value_from_env != nullptr) { @@ -192,7 +192,7 @@ void ClapParser::print_help() const { for (const auto& arg : args_) { arg->get__short_name().empty() ? std::cout << " " : std::cout << " -" << arg->get__short_name() << ", "; - std::cout << "--" << arg->get__long_name(); + arg->get__long_name().empty() ? std::cout << "\t" : std::cout << "--" << arg->get__long_name(); std::cout << "\t" << arg->get__help(); if (arg->has_default()) { std::cout << " (default: " << arg->get__default_value() << ")"; @@ -201,7 +201,7 @@ void ClapParser::print_help() const { std::cout << " [env: " << arg->get__env_name() << "]"; } if (arg->get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg->get__name(); + std::string env_name = this->program_name_ + '_' + arg->get__long_name(); to_upper(env_name); std::cout << " [def.env: " << env_name << "]"; } From 083ecf1908b04fa49c35f8f9d64f918c91f68492 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:17:27 +0200 Subject: [PATCH 23/29] fix(Arg, Parser): long_name() is main name, Arg() must call long_name() before anything, is template protected --- include/Macros.hpp | 8 ++++++++ include/arg/Arg.hpp | 24 +++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 760fc14..8d57332 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -30,6 +30,7 @@ #define ARG_USER_BOOL_FUNCTION_SAFE(NAME, TARGET_STATE, VALUE, ERROR_MSG, ...) \ [[nodiscard]] inline auto NAME() { \ + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); \ static_assert(std::is_same_v, ERROR_MSG); \ this->NAME##_ = VALUE; \ Arg<__VA_ARGS__> next = std::move(*this); \ @@ -44,4 +45,11 @@ return next; \ } +#define ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(NAME, TYPE, TARGET_STATE, ERROR_MSG, ...) \ + [[nodiscard]] inline auto NAME(TYPE NAME) { \ + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = std::move(NAME); \ + Arg<__VA_ARGS__> next = *this; \ + return next; \ } diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp index 616201c..9e576dd 100644 --- a/include/arg/Arg.hpp +++ b/include/arg/Arg.hpp @@ -2,14 +2,12 @@ #include #include -#include #include #include #include -#include "Macros.hpp" #include "BaseArg.hpp" -#include "utils.hpp" +#include "Macros.hpp" // === Type state flags === struct NotSet {}; @@ -43,30 +41,30 @@ class Arg final : public BaseArg { env_name_ = std::move(other.env_name_); } - ARG_USER_CUSTOM_FUNCTION_SAFE(short_name, std::string, ShortName, "Error: don't call short_name() more than once!", - Set, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, - AutoEnv) ARG_USER_CUSTOM_FUNCTION_SAFE(long_name, std::string, LongName, "Error: don't call long_name() more than once!", ShortName, Set, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) - ARG_USER_CUSTOM_FUNCTION_SAFE(help, std::string, Help, "Error: don't call help() more than once!", ShortName, + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(short_name, std::string, ShortName, "Error: don't call short_name() more than once!", + Set, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(help, std::string, Help, "Error: don't call help() more than once!", ShortName, LongName, Set, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) - ARG_USER_BOOL_FUNCTION_SAFE(is_required, IsRequired, true, "Error: don't call is_required() more than once!", - ShortName, LongName, Help, Set, IsFlag, AcceptsMany, DefaultValue, FromEnv, - AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(is_required, IsRequired, true, "Error: don't call is_required() more than once!", ShortName, + LongName, Help, Set, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) ARG_USER_BOOL_FUNCTION_SAFE(is_flag, IsFlag, true, "Error: don't call is_flag() more than once!", ShortName, LongName, Help, IsRequired, Set, AcceptsMany, DefaultValue, FromEnv, AutoEnv) ARG_USER_BOOL_FUNCTION_SAFE(accepts_many, AcceptsMany, true, "Error: don't call accepts_many() more than once!", ShortName, LongName, Help, IsRequired, IsFlag, Set, DefaultValue, FromEnv, AutoEnv) - ARG_USER_CUSTOM_FUNCTION_SAFE(default_value, std::string, DefaultValue, - "Error: don't call default_value() more than once!", ShortName, LongName, Help, - IsRequired, IsFlag, AcceptsMany, Set, FromEnv, AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(default_value, std::string, DefaultValue, "Error: don't call default_value() more than once!", + ShortName, LongName, Help, IsRequired, IsFlag, AcceptsMany, Set, FromEnv, + AutoEnv) ARG_USER_BOOL_FUNCTION_SAFE(auto_env, AutoEnv, true, "Error: don't call auto_env() more than once!", ShortName, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, Set) // this is expanded for its different functionality [[nodiscard]] inline auto from_env(std::string env_var_name) { + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); static_assert(std ::is_same_v, "Error: don't call auto_env() more than once!"); this->env_name_ = std::move(env_var_name); Arg next = From db834e74ad62f0a29a8f6e69a09d9f80618a2574 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:18:26 +0200 Subject: [PATCH 24/29] fix(tests): updated to latest api --- tests/test_combo.cpp | 4 ++-- tests/test_flag.cpp | 2 +- tests/test_priority.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_combo.cpp b/tests/test_combo.cpp index 6ad23e6..7c28342 100644 --- a/tests/test_combo.cpp +++ b/tests/test_combo.cpp @@ -10,8 +10,8 @@ int main(int argc, char* argv[]) { ClapParser p; // a value arg with full priority stack: - auto val = Arg("val").from_env("VAL").auto_env().default_value("10"); - auto boolean = Arg("flag").is_flag(); + auto val = Arg().long_name("val").from_env("VAL").auto_env().default_value("10"); + auto boolean = Arg().long_name("flag").is_flag(); p.add_arg(val); p.add_arg(boolean); p.parse(argc, argv); diff --git a/tests/test_flag.cpp b/tests/test_flag.cpp index 69fe8c7..78ce6ee 100644 --- a/tests/test_flag.cpp +++ b/tests/test_flag.cpp @@ -9,7 +9,7 @@ int main(int argc, char* argv[]) { ClapParser p; - auto f = Arg("opt").is_flag(); + auto f = Arg().long_name("opt").is_flag(); p.add_arg(f); p.parse(argc, argv); diff --git a/tests/test_priority.cpp b/tests/test_priority.cpp index 4d900ef..34c849f 100644 --- a/tests/test_priority.cpp +++ b/tests/test_priority.cpp @@ -18,7 +18,7 @@ int main(const int argc, char* argv[]) { ClapParser p; - auto a = Arg("val").from_env("VAL").auto_env().default_value("1"); + auto a = Arg().long_name("val").from_env("VAL").auto_env().default_value("1"); p.add_arg(a); p.parse(argc, argv); From a072a1265bfb2bae2f352c8f0ec65dbd4985853f Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:34:28 +0200 Subject: [PATCH 25/29] fix(Parsables): all numbers should be parsed with std::chars_format::general --- include/Macros.hpp | 20 ++++++++++---------- include/Parsables.hpp | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/include/Macros.hpp b/include/Macros.hpp index 8d57332..15e17e2 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -1,6 +1,6 @@ #pragma once -#define DEFINE_PARSABLE_INTEGER_TYPE(TYPE) \ +#define DEFINE_PARSABLE_NUM_TYPE(TYPE) \ template <> \ struct Parse { \ static std::optional parse(std::string_view s) { \ @@ -10,15 +10,15 @@ } \ }; -#define DEFINE_PARSABLE_FLOAT_TYPE(TYPE) \ - template <> \ - struct Parse { \ - static std::optional parse(std::string_view s) { \ - TYPE value = 0; \ - auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value, std::chars_format::scientific); \ - return ec == std::errc() ? std::optional{value} : std::nullopt; \ - } \ - }; +// #define DEFINE_PARSABLE_FLOAT_TYPE(TYPE) \ +// template <> \ +// struct Parse { \ +// static std::optional parse(std::string_view s) { \ +// TYPE value = 0; \ +// auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value, std::chars_format::scientific); \ +// return ec == std::errc() ? std::optional{value} : std::nullopt; \ +// } \ +// }; #define DEFINE_GETTER_SETTER_OVERRIDE(NAME, TYPE) \ [[nodiscard]] inline const TYPE& get__##NAME() const override { return this->NAME##_; } \ diff --git a/include/Parsables.hpp b/include/Parsables.hpp index f102cd5..cd87034 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -19,19 +19,19 @@ concept Parseable = requires(std::string_view s) { }; // Integer types -DEFINE_PARSABLE_INTEGER_TYPE(int8_t) -DEFINE_PARSABLE_INTEGER_TYPE(uint8_t) -DEFINE_PARSABLE_INTEGER_TYPE(int16_t) -DEFINE_PARSABLE_INTEGER_TYPE(uint16_t) -DEFINE_PARSABLE_INTEGER_TYPE(int32_t) -DEFINE_PARSABLE_INTEGER_TYPE(uint32_t) -DEFINE_PARSABLE_INTEGER_TYPE(int64_t) -DEFINE_PARSABLE_INTEGER_TYPE(uint64_t) +DEFINE_PARSABLE_NUM_TYPE(int8_t) +DEFINE_PARSABLE_NUM_TYPE(uint8_t) +DEFINE_PARSABLE_NUM_TYPE(int16_t) +DEFINE_PARSABLE_NUM_TYPE(uint16_t) +DEFINE_PARSABLE_NUM_TYPE(int32_t) +DEFINE_PARSABLE_NUM_TYPE(uint32_t) +DEFINE_PARSABLE_NUM_TYPE(int64_t) +DEFINE_PARSABLE_NUM_TYPE(uint64_t) // Floating-point types -DEFINE_PARSABLE_FLOAT_TYPE(float) -DEFINE_PARSABLE_FLOAT_TYPE(double) -DEFINE_PARSABLE_FLOAT_TYPE(long double) +DEFINE_PARSABLE_NUM_TYPE(float) +DEFINE_PARSABLE_NUM_TYPE(double) +DEFINE_PARSABLE_NUM_TYPE(long double) template <> struct Parse { From 87889cf8d23ee851ac544f21cb7067ffa1743176 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:35:09 +0200 Subject: [PATCH 26/29] feat(example): update example to use latest api --- src/example.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/example.cpp b/src/example.cpp index b16ce2a..e80707e 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -8,18 +8,18 @@ void run(ClapParser& arg_parser); int main(const int argc, char* argv[]) { ClapParser arg_parser; - auto num1 = Arg("num1").from_env("ASDF").auto_env().required(true); + auto num1 = Arg().long_name("num1").from_env("ASDF").auto_env().is_required(); // std::cerr << num1 << "\n"; arg_parser.add_arg(num1); - auto num2 = Arg("num2").short_name("N").from_env("TES").default_value("99"); + auto num2 = Arg().long_name("num2").short_name("N").from_env("TES").default_value("99.0"); arg_parser.add_arg(num2); - arg_parser.add_arg(Arg("test").is_flag()); + arg_parser.add_arg(Arg().long_name("test").is_flag()); // arg_parser.add_arg(Arg("test").is_flag(true)); try { arg_parser.parse(argc, argv); - // std::cerr << arg_parser; + std::cerr << arg_parser; run(arg_parser); } catch (const std::exception& e) { std::cerr << "\n\n\nerror: " << e.what() << "\n\n\n"; From 828a31986b5bc12eb199006a0ec24049655bb73c Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:45:32 +0200 Subject: [PATCH 27/29] feat(github): run tests on pull request to 'update' --- .github/workflows/meson-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/meson-test.yml b/.github/workflows/meson-test.yml index a1943fc..c066229 100644 --- a/.github/workflows/meson-test.yml +++ b/.github/workflows/meson-test.yml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - branches: [main] + branches: [main, update] jobs: test: From c54ac24afa0e170f120cafcef9a9bd34b1ff81ad Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 11:55:20 +0200 Subject: [PATCH 28/29] fix(Parsables): more explicit bool parsing, let's try to fix cross-platform tests :D --- include/Parsables.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/include/Parsables.hpp b/include/Parsables.hpp index cd87034..f0a6304 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -41,6 +41,13 @@ struct Parse { template <> struct Parse { static std::optional parse(std::string_view s) { - return Parse::parse(s); + auto value = Parse::parse(s); + if (!value.has_value()) { + return std::nullopt; + } + if (value.value() == 0) { + return std::optional{ false }; + } + return std::optional{ true }; } }; From e3985a7f9bc4435cc62d868de5b742a2efa44be2 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 4 Jun 2025 12:06:57 +0200 Subject: [PATCH 29/29] revert(Parsables): MacOs compatibility, revert to older float parser from 'fee7117' --- include/Parsables.hpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/include/Parsables.hpp b/include/Parsables.hpp index f0a6304..ec8e983 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -29,9 +29,33 @@ DEFINE_PARSABLE_NUM_TYPE(int64_t) DEFINE_PARSABLE_NUM_TYPE(uint64_t) // Floating-point types -DEFINE_PARSABLE_NUM_TYPE(float) -DEFINE_PARSABLE_NUM_TYPE(double) -DEFINE_PARSABLE_NUM_TYPE(long double) +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + float value = std::strtof(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + double value = std::strtod(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + long double value = std::strtold(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; template <> struct Parse {