From b1ca8a5a2bc1724936a4a77e6ea643bb0bef23e9 Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 14 Apr 2025 13:20:57 +0200 Subject: [PATCH 01/41] misc: all error msg lowercase --- src/Parser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 5047260..2f09099 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -21,7 +21,7 @@ void ClapParser::parse(int argc, char* argv[]) { for (const auto& arg : args_) { if (arg.takes_value() && args_with_values.count(arg.name()) == 0) { if (arg.is_required() && !arg.has_default()) { - throw std::runtime_error("Argument '" + arg.name() + "' requires a value"); + throw std::runtime_error("argument '" + arg.name() + "' requires a value"); } } } @@ -102,7 +102,7 @@ size_t ClapParser::handle_long_option(const std::string& token, const std::vecto } const Arg* arg = find_option(opt_name); if (arg == nullptr) { - throw std::runtime_error("Unknown option: " + token); + throw std::runtime_error("unknown option: " + token); } if (arg->takes_value()) { @@ -145,7 +145,7 @@ size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vectorname()] = std::string(arg->default_value()); } else { - throw std::runtime_error("Option '" + token + "' requires a value but none was provided"); + throw std::runtime_error("option '" + token + "' requires a value but none was provided"); } return i; @@ -153,7 +153,7 @@ size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vector Date: Tue, 15 Apr 2025 17:19:43 +0200 Subject: [PATCH 02/41] feat(utils): get clean program name --- src/utils.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils.cpp b/src/utils.cpp index ab9cf8b..95a11b1 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -33,3 +33,8 @@ template std::string concat(Args&&... args) { (oss << std::forward(args), 0)...}; // using initializer_list for fold-like behavior return oss.str(); } + +const std::string PROGRAM_NAME() { + const std::string& raw_program_name = program_invocation_name; + return raw_program_name.substr(raw_program_name.rfind('/') + 1); +} From 0805eb49589921815429ea17e293fa73f846746a Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:20:29 +0200 Subject: [PATCH 03/41] misc(meson) don't build the static lib, simpler for now --- meson.build | 76 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/meson.build b/meson.build index ec4fc4e..ca4c312 100644 --- a/meson.build +++ b/meson.build @@ -1,41 +1,65 @@ project('claplusplus', 'cpp', - version : '0.0.3', - default_options : [ + default_options: [ 'cpp_std=c++23', 'warning_level=3', - 'werror=true', + # 'werror=true', 'optimization=g', 'debug=true', 'b_ndebug=if-release' ] ) -# Include directory +# Add include directory for headers include_dir = include_directories('include') -# Library -source_dir = files( - 'src/Arg.cpp', - 'src/Parser.cpp', - 'src/utils.cpp' -) - -claplusplus_lib = static_library('claplusplus', - sources: source_dir, +# Build executable directly from all necessary sources +executable('claPlusPlus', + sources: [ + 'src/old_main.cpp', + 'src/Arg.cpp', + 'src/Parser.cpp' + ], include_directories: include_dir, - install: true + install: false ) + +# project('claplusplus', 'cpp', +# version : '0.0.3', +# default_options : [ +# 'cpp_std=c++23', +# 'warning_level=3', +# 'optimization=g', +# 'debug=true', +# 'b_ndebug=if-release' +# ] +# ) + +# # Include directory +# include_dir = include_directories('include') + +# # Library +# source_dir = files( +# 'src/Arg.cpp', +# 'src/Parser.cpp', +# 'src/utils.cpp' +# ) + +# claplusplus_lib = static_library('claplusplus', +# sources: source_dir, +# include_directories: include_dir, +# install: true +# ) -# Install headers -install_headers( - 'include/Arg.hpp', - 'include/Parser.hpp', - subdir: 'claplusplus' -) +# # Install headers +# install_headers( +# 'include/Arg.hpp', +# 'include/Parser.hpp', +# subdir: 'claplusplus' +# ) -executable('app', - sources : 'src/old_main.cpp', - include_directories: include_dir, - link_with: claplusplus_lib, - install : false -) +# executable('app', +# sources : 'src/old_main.cpp', +# include_directories: include_dir, +# link_with: claplusplus_lib, +# install : false +# ) From 6513976b163d5ca02c1d0b5a8dca7f3d334ee8ba Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:29:32 +0200 Subject: [PATCH 04/41] BROKEN misc(Arg): better private member names --- include/Arg.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index f988cfb..be21dd2 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -36,16 +36,15 @@ class Arg { friend class ClapParser; std::string name_; - std::string short_; - std::string long_; + std::string short_name_; + std::string long_name_; std::string help_; - bool required_; + bool is_required_; bool takes_value_; - bool has_env_; std::string env_name_; bool try_env_; - std::string try_env_name_; - std::string default_; + // std::string try_env_name_; + std::string default_value_; std::optional value_; [[nodiscard]] bool has_default() const; From d076ad851e27093ccd49d517956d9d6cbb677cc3 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:34:32 +0200 Subject: [PATCH 05/41] BROKEN feat(Arg): getters and setters a A LOT better + more usable --- include/Arg.hpp | 117 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 13 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index be21dd2..2093aa8 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -20,15 +20,6 @@ class Arg { Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); Arg& try_env(); - void set_try_env_name(const std::string& s); - - // Getters - [[nodiscard]] const std::string& short_name() const; - [[nodiscard]] const std::string& long_name() const; - [[nodiscard]] const std::string& help() const; - [[nodiscard]] const std::string& default_value() const; - [[nodiscard]] bool is_required() const; - [[nodiscard]] bool requires_value() const; friend std::ostream& operator<<(std::ostream& os, const Arg& arg); @@ -47,8 +38,108 @@ class Arg { std::string default_value_; std::optional value_; - [[nodiscard]] bool has_default() const; - [[nodiscard]] bool has_env() const; - [[nodiscard]] bool takes_value() const; - [[nodiscard]] const std::string& name() const; + // ----| 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; + } + + // takes_value_ + [[nodiscard]] inline bool get__takes_value() const { + return this->takes_value_; + } + inline void set__takes_value(const bool& takes_value) { + this->takes_value_ = takes_value; + } + + // 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; + } + + // try_env_ + [[nodiscard]] inline bool get__try_env() const { + return this->try_env_; + } + inline void set__try_env(const bool& try_env) { + this->try_env_ = try_env; + } + + // try_env_name_ + [[nodiscard]] inline const std::string get__try_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); }); + 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(); + } + + // has_default_ + [[nodiscard]] inline bool has_default() const { + return !this->default_value_.empty(); + } + + // has_value_ + [[nodiscard]] inline bool has_value() const { + return !this->value_.has_value(); + } + }; From f68891845858a1035eaf53bb232829838fceeb8c Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:36:37 +0200 Subject: [PATCH 06/41] BROKEN misc(Arg): better nameing in source file --- src/Arg.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Arg.cpp b/src/Arg.cpp index 0f08f6a..3bd2626 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -10,24 +10,24 @@ Arg::Arg(std::string name) : name_(std::move(name)), long_(name_), required_(false), takes_value_(true), has_env_(false), try_env_(false), value_(std::nullopt) {} // Setters -Arg& Arg::short_name(const std::string& s) { - short_ = s; +Arg& Arg::short_name(const std::string& short_name) { + short_name_ = short_name; return *this; } -Arg& Arg::help(const std::string& h) { - help_ = h; +Arg& Arg::help(const std::string& help) { + help_ = help; return *this; } Arg& Arg::required(bool is_required) { - required_ = is_required; + is_required_ = is_required; return *this; } -Arg& Arg::takes_value(bool takes) { - takes_value_ = takes; +Arg& Arg::takes_value(bool takes_value) { + takes_value_ = takes_value; return *this; } -Arg& Arg::default_value(const std::string& default_val) { - default_ = default_val; +Arg& Arg::default_value(const std::string& default_value) { + default_value_ = default_value; return *this; } bool Arg::requires_value() const { return takes_value_; } From ef7c2f8ea9b64f684ca2f3f8c268e05a8cfd06bd Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:38:30 +0200 Subject: [PATCH 07/41] BROKEN fix(Arg): adjust '<<' overload for new privates --- src/Arg.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Arg.cpp b/src/Arg.cpp index 3bd2626..c8e37a1 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -64,17 +64,17 @@ const std::string& Arg::default_value() const { return default_; } std::ostream& operator<<(std::ostream& os, const Arg& arg) { os << "Arg {\n" << " name: \"" << arg.name_ << "\",\n" - << " short: \"" << arg.short_ << "\",\n" - << " long: \"" << arg.long_ << "\",\n" + << " short: \"" << arg.short_name_ << "\",\n" + << " long: \"" << arg.long_name_ << "\",\n" << " help: \"" << arg.help_ << "\",\n" - << " required: " << std::boolalpha << arg.required_ << ",\n" + << " required: " << std::boolalpha << arg.is_required_ << ",\n" << " takes_value: " << std::boolalpha << arg.takes_value_ << ",\n" - << " default: \"" << arg.default_ << "\",\n" + << " default: \"" << arg.default_value_ << "\",\n" << " value: "; if (arg.value_) os << "\"" << arg.value_.value() << "\""; else - os << "nullopt"; + os << "std::nullopt"; os << "\n}"; return os; } From 57eaf45adfb7e83ebb6d685795f1427c70c35727 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:40:40 +0200 Subject: [PATCH 08/41] BROKEN fix(Arg): removed old getter/setter defs --- src/Arg.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Arg.cpp b/src/Arg.cpp index c8e37a1..b08ce90 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -30,9 +30,7 @@ Arg& Arg::default_value(const std::string& default_value) { default_value_ = default_value; return *this; } -bool Arg::requires_value() const { return takes_value_; } Arg& Arg::from_env(const char* env_var_name) { - this->has_env_ = true; this->env_name_ = env_var_name; // std::string value_from_env = ptr_unwrap_or(std::getenv(env_var_name), concat("value \'", env_var_name, "\' not present in env for: ", this->name_)); // std::optional value_from_env = ptr_unwrap_or(std::getenv(env_var_name), std::nullopt); @@ -50,17 +48,6 @@ Arg& Arg::try_env() { return *this; }; -// Getters -const std::string& Arg::name() const { return name_; } -const std::string& Arg::short_name() const { return short_; } -const std::string& Arg::long_name() const { return long_; } -const std::string& Arg::help() const { return help_; } -bool Arg::is_required() const { return required_; } -bool Arg::takes_value() const { return takes_value_; } -bool Arg::has_default() const { return !default_.empty(); } -bool Arg::has_env() const { return has_env_; } -const std::string& Arg::default_value() const { return default_; } - std::ostream& operator<<(std::ostream& os, const Arg& arg) { os << "Arg {\n" << " name: \"" << arg.name_ << "\",\n" @@ -78,7 +65,3 @@ std::ostream& operator<<(std::ostream& os, const Arg& arg) { os << "\n}"; return os; } - -void Arg::set_try_env_name(const std::string& s){ - this->try_env_name_ = s; -} From 3403cb5cddcb84ef71a98ec64fdaf557ca63c75e Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:42:23 +0200 Subject: [PATCH 09/41] BROKEN fix(Arg): remove unneeded redeclaration --- include/Arg.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 2093aa8..3ea6761 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -6,8 +6,6 @@ #include #include -class ClapParser; - class Arg { public: explicit Arg(std::string name); From 84ddf8976456b8b8d8e08fe368e00f3ddb0478f5 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:43:25 +0200 Subject: [PATCH 10/41] BROKEN misc(Arg): use more explicit names again and even more --- include/Arg.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 3ea6761..3ced495 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -10,8 +10,8 @@ class Arg { public: explicit Arg(std::string name); - Arg& short_name(const std::string& s); - Arg& long_name(const std::string& l); + 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& takes_value(bool takes); From a87a18f7353163c571f16fee35e737f15f0f3317 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:44:17 +0200 Subject: [PATCH 11/41] BROKEN fix(Arg): reworked the constructor --- include/Arg.hpp | 2 +- src/Arg.cpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 3ced495..285f06a 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -8,7 +8,7 @@ class Arg { public: - explicit Arg(std::string name); + Arg(const std::string& name); Arg& short_name(const std::string& short_name); Arg& long_name(const std::string& long_name); diff --git a/src/Arg.cpp b/src/Arg.cpp index b08ce90..21305cd 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -7,7 +7,18 @@ #include "../include/Arg.hpp" #include "../src/utils.cpp" -Arg::Arg(std::string name) : name_(std::move(name)), long_(name_), required_(false), takes_value_(true), has_env_(false), try_env_(false), value_(std::nullopt) {} +Arg::Arg(const std::string& name) : + name_(name), + short_name_(""), + long_name_(this->name_), + help_(""), + is_required_(false), + takes_value_(true), + env_name_(""), + try_env_(false), + default_value_(""), + value_(std::nullopt) +{} // Setters Arg& Arg::short_name(const std::string& short_name) { From 4d4b668ad0c1d9ccf5b9f002ceccede493fe8f2b Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:45:15 +0200 Subject: [PATCH 12/41] BROKEN fix(Arg): clean up includes --- include/Arg.hpp | 3 +-- src/Arg.cpp | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 285f06a..dc9e11f 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -1,8 +1,7 @@ #pragma once +#include #include #include -#include -#include #include #include diff --git a/src/Arg.cpp b/src/Arg.cpp index 21305cd..86699bf 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -1,11 +1,9 @@ +#include "Arg.hpp" + #include #include #include #include -#include - -#include "../include/Arg.hpp" -#include "../src/utils.cpp" Arg::Arg(const std::string& name) : name_(name), From ac7ca5b926174362484b5b8111c5507ef3a768c9 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 18:47:06 +0200 Subject: [PATCH 13/41] BROKEN feat(utils): move src/utils.cpp -> include/utils.hpp --- src/utils.cpp => include/utils.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) rename src/utils.cpp => include/utils.hpp (69%) diff --git a/src/utils.cpp b/include/utils.hpp similarity index 69% rename from src/utils.cpp rename to include/utils.hpp index 95a11b1..f8c3993 100644 --- a/src/utils.cpp +++ b/include/utils.hpp @@ -1,40 +1,42 @@ +#pragma once + #include #include #include #include template -T ok_or(std::optional opt, E&& err) { +inline T ok_or(std::optional opt, E&& err) { if (!opt) std::forward(err)(); return *opt; } template -T ok_or_throw_str(std::optional opt, const std::string& err) { +inline T ok_or_throw_str(std::optional opt, const std::string& err) { if (!opt) throw std::runtime_error(err); return *opt; } template -T ptr_ok_or_throw_str(T pointer, const std::string& err) { +inline T ptr_ok_or_throw_str(T pointer, const std::string& err) { if (!pointer) throw std::runtime_error(err); return pointer; } template -E ptr_unwrap_or(P pointer, const E other) { +inline E ptr_unwrap_or(P pointer, const E other) { if (!pointer) return other; } // variadic template function to concatenate any number of arguments -template std::string concat(Args&&... args) { +template inline std::string concat(Args&&... args) { std::ostringstream oss; (void)std::initializer_list{ (oss << std::forward(args), 0)...}; // using initializer_list for fold-like behavior return oss.str(); } -const std::string PROGRAM_NAME() { +inline const std::string PROGRAM_NAME() { const std::string& raw_program_name = program_invocation_name; return raw_program_name.substr(raw_program_name.rfind('/') + 1); } From 4f46a229cb41f3d1f412f96a73d836ce4efc8477 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 19:03:59 +0200 Subject: [PATCH 14/41] BROKEN fix: cleaned all includes in all files --- include/Arg.hpp | 3 +++ include/Parser.hpp | 3 ++- src/Parser.cpp | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index dc9e11f..b13ae2b 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -1,4 +1,7 @@ #pragma once + +#include "utils.hpp" + #include #include #include diff --git a/include/Parser.hpp b/include/Parser.hpp index fec0746..7d8d88e 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -1,9 +1,10 @@ #pragma once + #include "Arg.hpp" + #include #include #include -#include #include #include #include diff --git a/src/Parser.cpp b/src/Parser.cpp index 2f09099..230185b 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -1,8 +1,9 @@ -#include "../include/Parser.hpp" +#include "Parser.hpp" #include "Arg.hpp" +#include "utils.hpp" + #include #include -#include #include #include #include From 078905f74786bb31f6ea9b257c03c66f8c64e23a Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 19:06:30 +0200 Subject: [PATCH 15/41] BROKEN misc(Parser): added a const ref, removed unneeded --- include/Parser.hpp | 2 +- src/Parser.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 7d8d88e..3434aa1 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -12,7 +12,7 @@ class ClapParser { public: void add_arg(const Arg& arg); - void parse(int argc, char* argv[]); + void parse(const int& argc, char* argv[]); void print_help() const; template std::optional get_one_as(const std::string& name) const; diff --git a/src/Parser.cpp b/src/Parser.cpp index 230185b..1d2695d 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -8,8 +8,7 @@ #include #include -void ClapParser::parse(int argc, char* argv[]) { - program_name_ = argv[0]; +void ClapParser::parse(const int& argc, char* argv[]) { std::vector args(argv + 1, argv + argc); std::unordered_set args_with_values; From ee66d93280a480d4d4cc83f8dece12b75444934e Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 19:41:29 +0200 Subject: [PATCH 16/41] BROKEN fix(Parser): using new arg getters/setters, we gettin' there! --- src/Parser.cpp | 62 ++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 1d2695d..60e595c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -19,9 +19,9 @@ void ClapParser::parse(const int& argc, char* argv[]) { // Validate all arguments that need values received them for (const auto& arg : args_) { - if (arg.takes_value() && args_with_values.count(arg.name()) == 0) { - if (arg.is_required() && !arg.has_default()) { - throw std::runtime_error("argument '" + arg.name() + "' requires a value"); + if (arg.get__takes_value() && args_with_values.count(arg.get__name()) == 0) { + if (arg.get__is_required() && !arg.has_default()) { + throw std::runtime_error("argument '" + arg.get__name() + "' requires a value"); } } } @@ -52,15 +52,13 @@ void ClapParser::check_env() { std::string program_name = this->program_name_.substr(this->program_name_.rfind('/') + 1); std::string env_name = program_name + '_' + arg.name(); std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); - // std::cerr << env_name << "\n"; - arg.set_try_env_name(env_name); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { - values_[arg.name()] = value_from_env; + values_[arg.get__name()] = value_from_env; } } if (arg.has_env() && arg.value_.has_value()) { - values_[arg.name()] = arg.value_.value(); + values_[arg.get__name()] = arg.value_.value(); } } }; @@ -88,8 +86,8 @@ void ClapParser::check_env() { void ClapParser::check_required_args() { for (const auto& arg : args_) { - if (arg.is_required() && values_.find(arg.name()) == values_.end()) { - throw std::runtime_error("missing required argument: " + arg.name()); + if (arg.get__is_required() && values_.find(arg.get__name()) == values_.end()) { + throw std::runtime_error("missing required argument: " + arg.get__name()); } } } @@ -105,10 +103,10 @@ size_t ClapParser::handle_long_option(const std::string& token, const std::vecto throw std::runtime_error("unknown option: " + token); } - if (arg->takes_value()) { + if (arg->get__takes_value()) { i = handle_option_with_value(arg, args, i, token); } else { - values_[arg->name()] = true; // Boolean flag + values_[arg->get__name()] = true; // Boolean flag } return i; @@ -125,10 +123,10 @@ size_t ClapParser::handle_short_option(const std::string& token, const std::vect throw std::runtime_error("unknown option: " + token); } - if (arg->takes_value()) { + if (arg->get__takes_value()) { i = handle_option_with_value(arg, args, i, token); } else { - values_[arg->name()] = true; // Boolean flag + values_[arg->get__name()] = true; // Boolean flag } return i; @@ -138,12 +136,12 @@ size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vectorname()] = std::string(args[i + 1]); + values_[arg->get__name()] = std::string(args[i + 1]); return i + 1; // Skip the value in the next iteration } if (arg->has_default()) { // Use default value - values_[arg->name()] = std::string(arg->default_value()); + values_[arg->get__name()] = std::string(arg->get__default_value()); } else { throw std::runtime_error("option '" + token + "' requires a value but none was provided"); } @@ -152,11 +150,11 @@ size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vector ClapParser::get_positional_args() const { std::vector positional; for (const auto& arg : args_) { - if (arg.short_name().empty() && arg.long_name().empty()) { + if (arg.get__short_name().empty() && arg.get__long_name().empty()) { positional.push_back(arg); } } @@ -232,8 +230,8 @@ std::vector ClapParser::get_positional_args() const { void ClapParser::apply_defaults() { for (const auto& arg : args_) { - if (values_.find(arg.name()) == values_.end() && arg.has_default()) { - values_[arg.name()] = std::string(arg.default_value()); + if (values_.find(arg.get__name()) == values_.end() && arg.has_default()) { + values_[arg.get__name()] = std::string(arg.get__default_value()); } } } From 11d7ca27297430bc80730a571b6cf527a6123a2d Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 19:42:32 +0200 Subject: [PATCH 17/41] fix(Parser): switch to PROGRAM_NAME() util, all fixes done maybe? --- src/Parser.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 60e595c..722eec4 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -49,8 +49,7 @@ void ClapParser::parse_options(const std::vector& args) { void ClapParser::check_env() { for (auto& arg : args_) { if (arg.try_env_) { - std::string program_name = this->program_name_.substr(this->program_name_.rfind('/') + 1); - std::string env_name = program_name + '_' + arg.name(); + std::string env_name = PROGRAM_NAME() + '_' + arg.get__name(); std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { @@ -169,7 +168,7 @@ void ClapParser::handle_missing_positional(const Arg& arg) { } void ClapParser::print_help() const { - std::cout << "Usage: " << program_name_ << " [OPTIONS]"; + std::cout << "Usage: " << PROGRAM_NAME() << " [OPTIONS]"; auto positionals = get_positional_args(); for (const auto& pos : positionals) { std::cout << " [" << pos.get__name() << "]"; @@ -240,7 +239,7 @@ bool ClapParser::has(const std::string& name) const { return values_.find(name) std::ostream& operator<<(std::ostream& os, const ClapParser& parser) { os << "ClapParser {\n"; - os << " program_name: \"" << parser.program_name_ << "\",\n"; + os << " program_name: \"" << PROGRAM_NAME() << "\",\n"; os << " args: [\n"; for (const auto& arg : parser.args_) { From ea9cf88b459f6c7269c52a6d478a44c8dd15fc67 Mon Sep 17 00:00:00 2001 From: csboo Date: Tue, 15 Apr 2025 19:46:46 +0200 Subject: [PATCH 18/41] misc(Arg): rename try_env -> auto_env --- include/Arg.hpp | 20 ++++++++++---------- src/Arg.cpp | 6 +++--- src/Parser.cpp | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index b13ae2b..be8d3f4 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -19,7 +19,7 @@ class Arg { Arg& takes_value(bool takes); Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); - Arg& try_env(); + Arg& auto_env(); friend std::ostream& operator<<(std::ostream& os, const Arg& arg); @@ -33,8 +33,8 @@ class Arg { bool is_required_; bool takes_value_; std::string env_name_; - bool try_env_; - // std::string try_env_name_; + bool auto_env_; + // std::string auto_env_name_; std::string default_value_; std::optional value_; @@ -95,16 +95,16 @@ class Arg { this->env_name_ = env_name; } - // try_env_ - [[nodiscard]] inline bool get__try_env() const { - return this->try_env_; + // auto_env_ + [[nodiscard]] inline bool get__auto_env() const { + return this->auto_env_; } - inline void set__try_env(const bool& try_env) { - this->try_env_ = try_env; + inline void set__auto_env(const bool& auto_env) { + this->auto_env_ = auto_env; } - // try_env_name_ - [[nodiscard]] inline const std::string get__try_env_name() const { + // 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); }); return env_name; diff --git a/src/Arg.cpp b/src/Arg.cpp index 86699bf..2958857 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -13,7 +13,7 @@ Arg::Arg(const std::string& name) : is_required_(false), takes_value_(true), env_name_(""), - try_env_(false), + auto_env_(false), default_value_(""), value_(std::nullopt) {} @@ -52,8 +52,8 @@ Arg& Arg::from_env(const char* env_var_name) { } return *this; }; -Arg& Arg::try_env() { - this -> try_env_ = true; +Arg& Arg::auto_env() { + this -> auto_env_ = true; return *this; }; diff --git a/src/Parser.cpp b/src/Parser.cpp index 722eec4..07af2d6 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -48,7 +48,7 @@ void ClapParser::parse_options(const std::vector& args) { void ClapParser::check_env() { for (auto& arg : args_) { - if (arg.try_env_) { + if (arg.auto_env_) { std::string env_name = PROGRAM_NAME() + '_' + arg.get__name(); std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); auto value_from_env = std::getenv(env_name.c_str()); @@ -185,8 +185,8 @@ void ClapParser::print_help() const { if (arg.has_env()) { std::cout << " [env: " << arg.get__env_name() << "]"; } - if (arg.get__try_env()) { - std::cout << " [def.env: " << arg.get__try_env_name() << "]"; + if (arg.get__auto_env()) { + std::cout << " [def.env: " << arg.get__auto_env_name() << "]"; } std::cout << "\n"; } From 3006b4c2e11e56c14ac6b1a7c3c59fcbe1e87fa1 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 16 Apr 2025 11:28:23 +0200 Subject: [PATCH 19/41] feat(example): WIP added example --- meson.build | 2 +- src/example.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/example.cpp diff --git a/meson.build b/meson.build index ca4c312..c949920 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,7 @@ include_dir = include_directories('include') # Build executable directly from all necessary sources executable('claPlusPlus', sources: [ - 'src/old_main.cpp', + 'src/example.cpp', 'src/Arg.cpp', 'src/Parser.cpp' ], diff --git a/src/example.cpp b/src/example.cpp new file mode 100644 index 0000000..5a48700 --- /dev/null +++ b/src/example.cpp @@ -0,0 +1,39 @@ +#include "Parser.hpp" +#include "Arg.hpp" +#include "utils.hpp" + +#include +#include + +void run(const ClapParser& parsed_args); + +int main(const int argc, char* argv[]) { + ClapParser arg_parser; + auto num1 = Arg("num1").from_env("ASDF").auto_env(); + // std::cerr << num1 << "\n"; + arg_parser.add_arg(num1); + auto num2 = Arg("num2").short_name("N").from_env("TES").default_value("99"); + arg_parser.add_arg(num2); + + Arg test("testarg"); + arg_parser.add_arg(test); + + try { + arg_parser.parse(argc, argv); + // std::cerr << arg_parser; + run(arg_parser); + } + catch (const std::exception& e) { + std::cout << "\n\n\nerror: " << e.what() << "\n\n\n"; + // arg_parser.print_help(); + } +} + +void run(const ClapParser& parsed_args) { + auto num1 = ok_or_throw_str(parsed_args.get_one_as("num1"), "num1 not defined"); + auto num2 = ok_or_throw_str(parsed_args.get_one_as("num2"), "num2 not defined"); + + std::cout << "num1: " << num1 << '\n'; + std::cout.precision(5); + std::cout << std::fixed << "num2: " << num2 << '\n'; +} From df464eb51b8efeca1c56b93d5e30b117835a6aa3 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 16 Apr 2025 13:21:13 +0200 Subject: [PATCH 20/41] misc(Arg): rename takes_value() -> is_flag() --- include/Arg.hpp | 12 ++++++------ src/Arg.cpp | 8 ++++---- src/Parser.cpp | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index be8d3f4..3646413 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -16,7 +16,7 @@ class Arg { Arg& long_name(const std::string& long_name); Arg& help(const std::string& help); Arg& required(bool is_required); - Arg& takes_value(bool takes); + Arg& is_flag(bool takes); Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); Arg& auto_env(); @@ -31,7 +31,7 @@ class Arg { std::string long_name_; std::string help_; bool is_required_; - bool takes_value_; + bool is_flag_; std::string env_name_; bool auto_env_; // std::string auto_env_name_; @@ -80,11 +80,11 @@ class Arg { } // takes_value_ - [[nodiscard]] inline bool get__takes_value() const { - return this->takes_value_; + [[nodiscard]] inline bool get__is_flag() const { + return this->is_flag_; } - inline void set__takes_value(const bool& takes_value) { - this->takes_value_ = takes_value; + inline void set__is_flag(const bool& takes_value) { + this->is_flag_ = takes_value; } // env_name_ diff --git a/src/Arg.cpp b/src/Arg.cpp index 2958857..7800918 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -11,7 +11,7 @@ Arg::Arg(const std::string& name) : long_name_(this->name_), help_(""), is_required_(false), - takes_value_(true), + is_flag_(false), env_name_(""), auto_env_(false), default_value_(""), @@ -31,8 +31,8 @@ Arg& Arg::required(bool is_required) { is_required_ = is_required; return *this; } -Arg& Arg::takes_value(bool takes_value) { - takes_value_ = takes_value; +Arg& Arg::is_flag(bool is_flag) { + is_flag_ = is_flag; return *this; } Arg& Arg::default_value(const std::string& default_value) { @@ -64,7 +64,7 @@ std::ostream& operator<<(std::ostream& os, const Arg& arg) { << " long: \"" << arg.long_name_ << "\",\n" << " help: \"" << arg.help_ << "\",\n" << " required: " << std::boolalpha << arg.is_required_ << ",\n" - << " takes_value: " << std::boolalpha << arg.takes_value_ << ",\n" + << " is_flag: " << std::boolalpha << arg.is_flag_ << ",\n" << " default: \"" << arg.default_value_ << "\",\n" << " value: "; if (arg.value_) diff --git a/src/Parser.cpp b/src/Parser.cpp index 07af2d6..6329df6 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -102,7 +102,7 @@ size_t ClapParser::handle_long_option(const std::string& token, const std::vecto throw std::runtime_error("unknown option: " + token); } - if (arg->get__takes_value()) { + if (arg->get__is_flag()) { i = handle_option_with_value(arg, args, i, token); } else { values_[arg->get__name()] = true; // Boolean flag @@ -122,7 +122,7 @@ size_t ClapParser::handle_short_option(const std::string& token, const std::vect throw std::runtime_error("unknown option: " + token); } - if (arg->get__takes_value()) { + if (arg->get__is_flag()) { i = handle_option_with_value(arg, args, i, token); } else { values_[arg->get__name()] = true; // Boolean flag From 8561feaf2dc3e152b2d3f47c012bd9369f0dcda6 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 17 Apr 2025 00:26:58 +0200 Subject: [PATCH 21/41] fix(Arg): fixed a major logical mistake --- include/Arg.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 3646413..bd337d0 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -139,7 +139,7 @@ class Arg { // has_value_ [[nodiscard]] inline bool has_value() const { - return !this->value_.has_value(); + return this->value_.has_value(); } }; From 59c17ac97b44a2c57e1bac7a7adab7c71c992f82 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 17 Apr 2025 00:27:36 +0200 Subject: [PATCH 22/41] feat(Parser): less code, using new functions --- src/Parser.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 6329df6..af4d87c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -6,23 +6,20 @@ #include #include #include -#include void ClapParser::parse(const int& argc, char* argv[]) { std::vector args(argv + 1, argv + argc); - std::unordered_set args_with_values; apply_defaults(); check_env(); - parse_options(args); + parse_options(args); // parse from cli (argc, argv) // parse_positional_args(args); // Validate all arguments that need values received them for (const auto& arg : args_) { - if (arg.get__takes_value() && args_with_values.count(arg.get__name()) == 0) { - if (arg.get__is_required() && !arg.has_default()) { - throw std::runtime_error("argument '" + arg.get__name() + "' requires a value"); - } + std::cerr << arg << "\n\n\n"; + if (arg.get__is_required() && !arg.has_value()) { + throw std::runtime_error("argument '" + arg.get__name() + "' requires a value"); } } From fcfdf3fe1db67e2da174ad38c8483b7f9a002867 Mon Sep 17 00:00:00 2001 From: csboo Date: Thu, 17 Apr 2025 12:21:06 +0200 Subject: [PATCH 23/41] fix(Arg): corrected names --- include/Arg.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index bd337d0..61682cf 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -16,7 +16,7 @@ class Arg { Arg& long_name(const std::string& long_name); Arg& help(const std::string& help); Arg& required(bool is_required); - Arg& is_flag(bool takes); + Arg& is_flag(bool is_flag); Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); Arg& auto_env(); @@ -122,7 +122,7 @@ class Arg { [[nodiscard]] inline const std::optional get__value() const { return this->value_; } - inline void set__value_(const std::string& value) { + inline void set__value(const std::string& value) { this->value_ = value; } From 4845e4f67daa89e9b779976a0aa205f9fb41aed1 Mon Sep 17 00:00:00 2001 From: csboo Date: Sun, 20 Apr 2025 01:45:52 +0200 Subject: [PATCH 24/41] feat(utils): added a utility to simply get a \' + varname + \' like a quote --- include/utils.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/utils.hpp b/include/utils.hpp index f8c3993..b11e797 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -40,3 +40,7 @@ inline const std::string PROGRAM_NAME() { const std::string& raw_program_name = program_invocation_name; return raw_program_name.substr(raw_program_name.rfind('/') + 1); } + +inline const std::string quote(const std::string& name) { + return '\'' + name + '\''; +} From d6a08c6386c440338292a0b494dcb6c075321d92 Mon Sep 17 00:00:00 2001 From: csboo Date: Sun, 20 Apr 2025 01:49:14 +0200 Subject: [PATCH 25/41] misc(parser): added 'this->' where needed --- src/Parser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index af4d87c..c730310 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -10,9 +10,9 @@ void ClapParser::parse(const int& argc, char* argv[]) { std::vector args(argv + 1, argv + argc); - apply_defaults(); - check_env(); - parse_options(args); // parse from cli (argc, argv) + this->apply_defaults(); + this->check_env(); + this->parse_options(args); // parse from cli (argc, argv) // parse_positional_args(args); // Validate all arguments that need values received them From 28c6c22700c1417f9f5964639db6a49b10b839f3 Mon Sep 17 00:00:00 2001 From: csboo Date: Sun, 20 Apr 2025 01:50:47 +0200 Subject: [PATCH 26/41] fix(parser): big update, way better code, way less code, many optimizations --- include/Parser.hpp | 37 ++++++--------- src/Parser.cpp | 113 +++++++++++---------------------------------- 2 files changed, 41 insertions(+), 109 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 3434aa1..0ef6970 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -1,10 +1,10 @@ #pragma once #include "Arg.hpp" +#include "utils.hpp" #include #include -#include #include #include #include @@ -15,8 +15,16 @@ class ClapParser { void parse(const int& argc, char* argv[]); void print_help() const; - template std::optional get_one_as(const std::string& name) const; - bool has(const std::string& name) const; + template 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; + } friend std::ostream& operator<<(std::ostream& os, const ClapParser& parser); private: @@ -28,31 +36,12 @@ class ClapParser { 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 ; - const Arg* find_option(const std::string& name) const; + static std::optional find_arg(ClapParser& parser, const std::string& name); std::vector get_positional_args() const; void apply_defaults(); void parse_options(const std::vector& args); - void parse_positional_args(const std::vector& args); - void check_required_args(); void check_env(); + void parse_positional_args(const std::vector& args); void handle_missing_positional(const Arg& arg); - - size_t handle_long_option(const std::string& token, const std::vector& args, size_t i); - size_t handle_short_option(const std::string& token, const std::vector& args, size_t i); - size_t handle_option_with_value(const Arg* arg, const std::vector& args, size_t i, - const std::string& token); - }; - -template std::optional ClapParser::get_one_as(const std::string& name) const { - auto it = values_.find(name); - if (it == values_.end()) { - return std::nullopt; - } - - T value; - std::istringstream(it->second) >> value; - return value; - // return std::any_cast(it->second); -} diff --git a/src/Parser.cpp b/src/Parser.cpp index c730310..32c573c 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include void ClapParser::parse(const int& argc, char* argv[]) { std::vector args(argv + 1, argv + argc); @@ -17,28 +19,34 @@ 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"; + // std::cerr << arg << "\n\n\n"; if (arg.get__is_required() && !arg.has_value()) { - throw std::runtime_error("argument '" + arg.get__name() + "' requires a value"); + throw std::runtime_error("argument '" + arg.get__name() + "' is required"); } } - - check_required_args(); } -void ClapParser::add_arg(const Arg& arg) { args_.push_back(arg); } +void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } void ClapParser::parse_options(const std::vector& args) { for (size_t i = 0; i < args.size(); ++i) { - const std::string& token = args[i]; + const auto& token = args.at(i); + + if (token == "--help" || token == "-h") { + print_help(); + exit(0); + } - if (is_long_option(token)) { - i = handle_long_option(token, args, i); - } else if (is_short_option(token)) { - i = handle_short_option(token, args, i); + 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"); + } } else { - // Positional arguments are handled separately - break; + arg->set__value("1"); } } } @@ -80,71 +88,6 @@ void ClapParser::check_env() { // } // } -void ClapParser::check_required_args() { - for (const auto& arg : args_) { - if (arg.get__is_required() && values_.find(arg.get__name()) == values_.end()) { - throw std::runtime_error("missing required argument: " + arg.get__name()); - } - } -} - -size_t ClapParser::handle_long_option(const std::string& token, const std::vector& args, size_t i) { - std::string opt_name = token.substr(2); - if (opt_name == "help") { - print_help(); - exit(0); - } - const Arg* arg = find_option(opt_name); - if (arg == nullptr) { - throw std::runtime_error("unknown option: " + token); - } - - if (arg->get__is_flag()) { - i = handle_option_with_value(arg, args, i, token); - } else { - values_[arg->get__name()] = true; // Boolean flag - } - - return i; -} - -size_t ClapParser::handle_short_option(const std::string& token, const std::vector& args, size_t i) { - std::string opt_name = token.substr(1); - if (opt_name == "h") { - print_help(); - exit(0); - } - const Arg* arg = find_option(opt_name); - if (arg == nullptr) { - throw std::runtime_error("unknown option: " + token); - } - - if (arg->get__is_flag()) { - i = handle_option_with_value(arg, args, i, token); - } else { - values_[arg->get__name()] = true; // Boolean flag - } - - return i; -} - -size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vector& args, size_t i, - const std::string& token) { - if (i + 1 < args.size() && !is_option(args[i + 1])) { - // Use next argument as value - values_[arg->get__name()] = std::string(args[i + 1]); - return i + 1; // Skip the value in the next iteration - } - if (arg->has_default()) { - // Use default value - values_[arg->get__name()] = std::string(arg->get__default_value()); - } else { - throw std::runtime_error("option '" + token + "' requires a value but none was provided"); - } - - return i; -} - void ClapParser::handle_missing_positional(const Arg& arg) { if (arg.get__is_required()) { throw std::runtime_error("missing required positional argument: " + arg.get__name()); @@ -205,13 +148,15 @@ void ClapParser::print_help() const { } // Helper methods -const Arg* ClapParser::find_option(const std::string& name) const { - for (const auto& arg : args_) { - if (arg.get__long_name() == name || arg.get__short_name() == name) { - return &arg; - } +std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { + auto it = std::find_if(parser.args_.begin(), parser.args_.end(), [&](Arg& arg) { + return ( "--" + arg.get__long_name() == arg_name || "-" + arg.get__short_name() == arg_name ); + }); + + if (it == parser.args_.end()) { + return std::nullopt; } - return nullptr; + return &(*it); } std::vector ClapParser::get_positional_args() const { @@ -232,8 +177,6 @@ void ClapParser::apply_defaults() { } } -bool ClapParser::has(const std::string& name) const { return values_.find(name) != values_.end(); } - std::ostream& operator<<(std::ostream& os, const ClapParser& parser) { os << "ClapParser {\n"; os << " program_name: \"" << PROGRAM_NAME() << "\",\n"; From 39fa72d304463e5595f84551542dbb4de99852c8 Mon Sep 17 00:00:00 2001 From: csboo Date: Sun, 20 Apr 2025 01:52:01 +0200 Subject: [PATCH 27/41] feat(example): updated example --- src/example.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/example.cpp b/src/example.cpp index 5a48700..24c06ce 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -5,18 +5,18 @@ #include #include -void run(const ClapParser& parsed_args); +void run(ClapParser& parsed_args); int main(const int argc, char* argv[]) { ClapParser arg_parser; - auto num1 = Arg("num1").from_env("ASDF").auto_env(); + auto num1 = Arg("num1").from_env("ASDF").auto_env().required(true); // std::cerr << num1 << "\n"; arg_parser.add_arg(num1); auto num2 = Arg("num2").short_name("N").from_env("TES").default_value("99"); arg_parser.add_arg(num2); - Arg test("testarg"); - arg_parser.add_arg(test); + arg_parser.add_arg(Arg("test").is_flag(true)); + // arg_parser.add_arg(Arg("test").is_flag(true)); try { arg_parser.parse(argc, argv); @@ -24,16 +24,19 @@ int main(const int argc, char* argv[]) { run(arg_parser); } catch (const std::exception& e) { - std::cout << "\n\n\nerror: " << e.what() << "\n\n\n"; + std::cerr << "\n\n\nerror: " << e.what() << "\n\n\n"; // arg_parser.print_help(); } } -void run(const ClapParser& parsed_args) { - auto num1 = ok_or_throw_str(parsed_args.get_one_as("num1"), "num1 not defined"); - auto num2 = ok_or_throw_str(parsed_args.get_one_as("num2"), "num2 not defined"); +void run(ClapParser& arg_parser) { + // std::cerr << "running\n"; + auto num1 = ok_or_throw_str(arg_parser.get_one_as("num1"), "num1 not defined"); + auto num2 = ok_or_throw_str(arg_parser.get_one_as("num2"), "num2 not defined"); std::cout << "num1: " << num1 << '\n'; std::cout.precision(5); std::cout << std::fixed << "num2: " << num2 << '\n'; + + // std::cerr << arg_parser; } From caca10355a528562516ea3bf78e481a8cdbb39f8 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 30 Apr 2025 09:58:30 +0200 Subject: [PATCH 28/41] fix(parser): use new logic instead of old (wrong) functions --- src/Parser.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 32c573c..4d52380 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -53,17 +53,14 @@ void ClapParser::parse_options(const std::vector& args) { void ClapParser::check_env() { for (auto& arg : args_) { - if (arg.auto_env_) { + if (arg.get__auto_env()) { std::string env_name = PROGRAM_NAME() + '_' + arg.get__name(); std::transform(env_name.begin(), env_name.end(), env_name.begin(), [](const unsigned char& c) { return std::toupper(c); }); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { - values_[arg.get__name()] = value_from_env; + arg.set__value(value_from_env); } } - if (arg.has_env() && arg.value_.has_value()) { - values_[arg.get__name()] = arg.value_.value(); - } } }; @@ -170,9 +167,9 @@ std::vector ClapParser::get_positional_args() const { } void ClapParser::apply_defaults() { - for (const auto& arg : args_) { - if (values_.find(arg.get__name()) == values_.end() && arg.has_default()) { - values_[arg.get__name()] = std::string(arg.get__default_value()); + for (auto& arg : args_) { + if (!arg.has_value() && arg.has_default()) { + arg.set__value(arg.get__default_value()); } } } From 81b740050416b1f3b79e06117187c91fd8d1b783 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:01:08 +0200 Subject: [PATCH 29/41] feat(Arg): proper debug print indentation for Arg --- include/Arg.hpp | 1 + include/utils.hpp | 5 +++++ src/Arg.cpp | 29 ++++++++++++++++++----------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 61682cf..3890cd5 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -21,6 +21,7 @@ class Arg { 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: diff --git a/include/utils.hpp b/include/utils.hpp index b11e797..a7dd752 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -44,3 +44,8 @@ inline const std::string PROGRAM_NAME() { inline const std::string quote(const std::string& name) { return '\'' + name + '\''; } + +inline void print_indent(std::ostream& os, int indent_level) { + for (int i = 0; i < indent_level; ++i) + os << '\t'; +} diff --git a/src/Arg.cpp b/src/Arg.cpp index 7800918..110ef9c 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -57,20 +57,27 @@ Arg& Arg::auto_env() { return *this; }; -std::ostream& operator<<(std::ostream& os, const Arg& arg) { - os << "Arg {\n" - << " name: \"" << arg.name_ << "\",\n" - << " short: \"" << arg.short_name_ << "\",\n" - << " long: \"" << arg.long_name_ << "\",\n" - << " help: \"" << arg.help_ << "\",\n" - << " required: " << std::boolalpha << arg.is_required_ << ",\n" - << " is_flag: " << std::boolalpha << arg.is_flag_ << ",\n" - << " default: \"" << arg.default_value_ << "\",\n" - << " value: "; +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 << "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}"; + os << '\n'; + + print_indent(os, indent); os << "}"; +} + +std::ostream& operator<<(std::ostream& os, const Arg& arg) { + Arg::print_arg(os, arg, 0); return os; } From 1f4c6d3083f9f34cdb7ff6d355fd41ef8f3d9abe Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:02:34 +0200 Subject: [PATCH 30/41] feat(Parser): proper debug print indentation for Parser --- include/Parser.hpp | 1 + src/Parser.cpp | 26 ++++++++++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/include/Parser.hpp b/include/Parser.hpp index 0ef6970..fc8d483 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -26,6 +26,7 @@ class ClapParser { return std::nullopt; } + static void print_parser(std::ostream& os, const ClapParser& parser, int indent); friend std::ostream& operator<<(std::ostream& os, const ClapParser& parser); private: std::vector args_; diff --git a/src/Parser.cpp b/src/Parser.cpp index 4d52380..5313468 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -174,22 +174,28 @@ void ClapParser::apply_defaults() { } } -std::ostream& operator<<(std::ostream& os, const ClapParser& parser) { - os << "ClapParser {\n"; - os << " program_name: \"" << PROGRAM_NAME() << "\",\n"; +void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int indent) { + print_indent(os, indent); os << "ClapParser {\n"; + + print_indent(os, indent + 1); os << "program_name: \"" << PROGRAM_NAME() << "\",\n"; - os << " args: [\n"; + print_indent(os, indent + 1); os << "args: [\n"; for (const auto& arg : parser.args_) { - os << " " << arg << ",\n"; + Arg::print_arg(os, arg, indent + 2); + os << ",\n"; } - os << " ],\n"; + print_indent(os, indent + 1); os << "],\n"; - os << " values: {\n"; + print_indent(os, indent + 1); os << "values: {\n"; for (const auto& [key, val] : parser.values_) { - os << " \"" << key << "\": \"" << val << "\",\n"; + print_indent(os, indent + 2); os << "\"" << key << "\": \"" << val << "\",\n"; } - os << " }\n"; + print_indent(os, indent + 1); os << "}\n"; - os << "}"; + print_indent(os, indent); os << "}"; +} + +std::ostream& operator<<(std::ostream& os, const ClapParser& parser) { + ClapParser::print_parser(os, parser, 0); return os; } From 8d3050062f8a818bd230514518a54e8caffad63d Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:48:14 +0200 Subject: [PATCH 31/41] fix(Arg): is_flag doesn't need an arg --- include/Arg.hpp | 2 +- src/Arg.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 3890cd5..4cd2bb5 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -16,7 +16,7 @@ class Arg { Arg& long_name(const std::string& long_name); Arg& help(const std::string& help); Arg& required(bool is_required); - Arg& is_flag(bool is_flag); + Arg& is_flag(); Arg& default_value(const std::string& default_val); Arg& from_env(const char* env_var_name); Arg& auto_env(); diff --git a/src/Arg.cpp b/src/Arg.cpp index 110ef9c..66e6a2a 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -31,8 +31,8 @@ Arg& Arg::required(bool is_required) { is_required_ = is_required; return *this; } -Arg& Arg::is_flag(bool is_flag) { - is_flag_ = is_flag; +Arg& Arg::is_flag() { + is_flag_ = true; return *this; } Arg& Arg::default_value(const std::string& default_value) { From f1cbe85917d9bd1d4411c897f7e4b5fa937006df Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:48:55 +0200 Subject: [PATCH 32/41] fix(example): adjusted example code --- src/example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/example.cpp b/src/example.cpp index 24c06ce..89b609f 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -15,7 +15,7 @@ int main(const int argc, char* argv[]) { auto num2 = Arg("num2").short_name("N").from_env("TES").default_value("99"); arg_parser.add_arg(num2); - arg_parser.add_arg(Arg("test").is_flag(true)); + arg_parser.add_arg(Arg("test").is_flag()); // arg_parser.add_arg(Arg("test").is_flag(true)); try { From 48823a00ddbfae216640d96adf25be81184bfc22 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:50:16 +0200 Subject: [PATCH 33/41] feat(Arg): is_flag sets deafult value to '0' (false) --- src/Arg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Arg.cpp b/src/Arg.cpp index 66e6a2a..edda651 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -33,6 +33,7 @@ Arg& Arg::required(bool is_required) { } Arg& Arg::is_flag() { is_flag_ = true; + default_value_ = "0"; return *this; } Arg& Arg::default_value(const std::string& default_value) { From 17f04a86a9aea23ce04e0a4d502444de13ff1a41 Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:53:08 +0200 Subject: [PATCH 34/41] fix(Parser): arg env value gets parsed (better) in the ClapParser::parse() function --- src/Arg.cpp | 2 ++ src/Parser.cpp | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/Arg.cpp b/src/Arg.cpp index edda651..b7766e9 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -45,12 +45,14 @@ Arg& Arg::from_env(const char* env_var_name) { // std::string value_from_env = ptr_unwrap_or(std::getenv(env_var_name), concat("value \'", env_var_name, "\' not present in env for: ", this->name_)); // std::optional value_from_env = ptr_unwrap_or(std::getenv(env_var_name), std::nullopt); // this->value_ = ptr_unwrap_or(std::getenv(env_var_name), std::nullopt); +/* auto ptr = std::getenv(env_var_name); if (!ptr) { this->value_ = std::nullopt; } else { this->value_ = ptr; } +*/ return *this; }; Arg& Arg::auto_env() { diff --git a/src/Parser.cpp b/src/Parser.cpp index 5313468..895898f 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -61,6 +62,12 @@ void ClapParser::check_env() { arg.set__value(value_from_env); } } + if (arg.has_env()) { + auto value_from_env = std::getenv(arg.get__env_name().c_str()); + if (value_from_env) { + arg.set__value(value_from_env); + } + } } }; From cb34fdf49df93198036f7724e6d64b34c252d33f Mon Sep 17 00:00:00 2001 From: csboo Date: Wed, 7 May 2025 12:54:14 +0200 Subject: [PATCH 35/41] feat(tests): added many tests and a lot more to come, (meson test -C build) --- meson.build | 94 +++++++++++++++++++++++++---------------- tests/test_combo.cpp | 33 +++++++++++++++ tests/test_flag.cpp | 24 +++++++++++ tests/test_priority.cpp | 34 +++++++++++++++ 4 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 tests/test_combo.cpp create mode 100644 tests/test_flag.cpp create mode 100644 tests/test_priority.cpp diff --git a/meson.build b/meson.build index c949920..ee36240 100644 --- a/meson.build +++ b/meson.build @@ -23,43 +23,65 @@ executable('claPlusPlus', install: false ) -# project('claplusplus', 'cpp', -# version : '0.0.3', -# default_options : [ -# 'cpp_std=c++23', -# 'warning_level=3', -# 'optimization=g', -# 'debug=true', -# 'b_ndebug=if-release' -# ] -# ) +parser_lib = static_library('clap_parser', + sources: ['src/Parser.cpp', 'src/Arg.cpp'], + include_directories: include_dir, + install: false +) + +tests = [ + 'test_priority.cpp', + 'test_flag.cpp', + 'test_combo.cpp', +] -# # Include directory -# include_dir = include_directories('include') +test_bins = [] +foreach test : tests + name = test.replace('.cpp', '') + bin = executable( + name, + 'tests/' + test, + link_with: parser_lib, + include_directories: include_dir, + install: false + ) + test_bins += [bin] +endforeach -# # Library -# source_dir = files( -# 'src/Arg.cpp', -# 'src/Parser.cpp', -# 'src/utils.cpp' -# ) +# register a bunch of test cases +# TODO basic tests for constructing stuff +# 1) priority tests (default value '1') +test('priority_default', test_bins[0], + args: [], + env: ['EXPECTED=1']) +test('priority_auto_env', test_bins[0], + args: [], + env: ['TEST_PRIORITY_VAL=2', 'EXPECTED=2']) +test('priority_from_env', test_bins[0], + args: [], + env: ['VAL=3', 'TEST_PRIORITY_VAL=2', 'EXPECTED=3']) +test('priority_manual', test_bins[0], + args: ['--val', '4'], + env: ['VAL=3', 'TEST_PRIORITY_VAL=2', 'EXPECTED=4']) -# claplusplus_lib = static_library('claplusplus', -# sources: source_dir, -# include_directories: include_dir, -# install: true -# ) - -# # Install headers -# install_headers( -# 'include/Arg.hpp', -# 'include/Parser.hpp', -# subdir: 'claplusplus' -# ) +# 2) flag tests +test('flag_absent', test_bins[1], + args: [], + env: ['EXPECTED=0']) +test('flag_present', test_bins[1], + args: ['--opt'], + env: ['EXPECTED=1']) -# executable('app', -# sources : 'src/old_main.cpp', -# include_directories: include_dir, -# link_with: claplusplus_lib, -# install : false -# ) +# 3) combo tests +test('combo_default', test_bins[2], + args: [], + env: ['EXPECTED_V=10', 'EXPECTED_B=0']) +test('combo_flag_first', test_bins[2], + args: ['--flag', '--val', '7'], + env: ['EXPECTED_V=7', 'EXPECTED_B=1']) +test('combo_value_first', test_bins[2], + args: ['--val', '8', '--flag'], + env: ['EXPECTED_V=8', 'EXPECTED_B=1']) +test('combo_env_and_manual', test_bins[2], + args: ['--val', '9'], + env: ['VAL=4', 'TEST_COMBO_VAL=5', 'EXPECTED_V=9', 'EXPECTED_B=0']) diff --git a/tests/test_combo.cpp b/tests/test_combo.cpp new file mode 100644 index 0000000..b07170d --- /dev/null +++ b/tests/test_combo.cpp @@ -0,0 +1,33 @@ +// tests/test_combo.cpp +#include "Parser.hpp" +#include "Arg.hpp" +#include "utils.hpp" +#include +#include +#include + +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(); + p.add_arg(val); + p.add_arg(boolean); + p.parse(argc, argv); + + // EXPECTED_V -> any int + // EXPECTED_B -> "1" or "0" + const auto ev = std::getenv("EXPECTED_V"); + const auto eb = std::getenv("EXPECTED_B"); + assert(ev && eb && "EXPECTED_V/B must be set"); + + int expected_val = std::stoi(ev); + bool expected_boolean = std::stoi(eb); + + 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"); + + assert(actual_val == expected_val); + assert(actual_boolean == expected_boolean); + return 0; +} diff --git a/tests/test_flag.cpp b/tests/test_flag.cpp new file mode 100644 index 0000000..9c2676d --- /dev/null +++ b/tests/test_flag.cpp @@ -0,0 +1,24 @@ +// tests/test_flag.cpp +#include "Parser.hpp" +#include "Arg.hpp" +#include "utils.hpp" +#include +#include +#include + +int main(int argc, char* argv[]) { + ClapParser p; + auto f = Arg("opt").is_flag(); + p.add_arg(f); + p.parse(argc, argv); + + // EXPECTED should be "1" or "0" (present or absent) + const auto e = std::getenv("EXPECTED"); + assert(e && "EXPECTED must be set"); + bool expected = std::string(e) == "1"; + + auto actual = ok_or_throw_str(p.get_one_as("opt"), "test argument: 'opt' is missing"); + std::cerr << p << '\n'; + assert(actual == expected); + return 0; +} diff --git a/tests/test_priority.cpp b/tests/test_priority.cpp new file mode 100644 index 0000000..49d3c89 --- /dev/null +++ b/tests/test_priority.cpp @@ -0,0 +1,34 @@ +// tests/test_priority.cpp +#include "Parser.hpp" +#include "Arg.hpp" +#include "utils.hpp" +#include +#include +#include +#include +#include + +/* VALUE PRIORITY: + 1. cli arg: '--val' + 2. env var: 'VAL' + 3. auto env var: 'PRIORITY_MANUAL_VAL' + 4. default value: '1' +*/ + +int main(const int argc, char* argv[]) { + ClapParser p; + + auto a = Arg("val").from_env("VAL").auto_env().default_value("1"); + p.add_arg(a); + p.parse(argc, argv); + + // gets read from meson test env + const auto e = std::getenv("EXPECTED"); + assert(e && "EXPECTED must be set"); + int expected = std::stoi(e); + + auto actual = ok_or_throw_str(p.get_one_as("val"), "test argument: 'val' is missing"); + std::cerr << p << '\n'; + assert(actual == expected); + return 0; +} From b12723caa10031301a431a53fb26065914b985d5 Mon Sep 17 00:00:00 2001 From: csboo Date: Sat, 10 May 2025 11:43:51 +0200 Subject: [PATCH 36/41] feat(Parser): cross platfrom program name maybe --- include/Arg.hpp | 10 +++++----- include/utils.hpp | 5 ----- src/Parser.cpp | 12 ++++++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/Arg.hpp b/include/Arg.hpp index 4cd2bb5..4809ff1 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -105,11 +105,11 @@ 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); }); - return 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); }); + // return env_name; + // } // default_ [[nodiscard]] inline const std::string& get__default_value() const { diff --git a/include/utils.hpp b/include/utils.hpp index a7dd752..cfd0dec 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -36,11 +36,6 @@ template inline std::string concat(Args&&... args) { return oss.str(); } -inline const std::string PROGRAM_NAME() { - const std::string& raw_program_name = program_invocation_name; - return raw_program_name.substr(raw_program_name.rfind('/') + 1); -} - inline const std::string quote(const std::string& name) { return '\'' + name + '\''; } diff --git a/src/Parser.cpp b/src/Parser.cpp index 895898f..84e557a 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -11,6 +11,8 @@ #include void ClapParser::parse(const int& argc, char* argv[]) { + const std::string& raw_program_name = argv[0]; + this->program_name_ = raw_program_name.substr(raw_program_name.rfind('/') + 1); std::vector args(argv + 1, argv + argc); this->apply_defaults(); @@ -55,7 +57,7 @@ void ClapParser::parse_options(const std::vector& args) { void ClapParser::check_env() { for (auto& arg : args_) { if (arg.get__auto_env()) { - std::string env_name = PROGRAM_NAME() + '_' + arg.get__name(); + 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); }); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { @@ -112,7 +114,7 @@ void ClapParser::handle_missing_positional(const Arg& arg) { } void ClapParser::print_help() const { - std::cout << "Usage: " << PROGRAM_NAME() << " [OPTIONS]"; + std::cout << "Usage: " << this->program_name_ << " [OPTIONS]"; auto positionals = get_positional_args(); for (const auto& pos : positionals) { std::cout << " [" << pos.get__name() << "]"; @@ -130,7 +132,9 @@ void ClapParser::print_help() const { std::cout << " [env: " << arg.get__env_name() << "]"; } if (arg.get__auto_env()) { - std::cout << " [def.env: " << arg.get__auto_env_name() << "]"; + 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); }); + std::cout << " [def.env: " << env_name << "]"; } std::cout << "\n"; } @@ -184,7 +188,7 @@ void ClapParser::apply_defaults() { void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int indent) { print_indent(os, indent); os << "ClapParser {\n"; - print_indent(os, indent + 1); os << "program_name: \"" << PROGRAM_NAME() << "\",\n"; + print_indent(os, indent + 1); os << "program_name: \"" << parser.program_name_ << "\",\n"; print_indent(os, indent + 1); os << "args: [\n"; for (const auto& arg : parser.args_) { From b2f5f749c169d66c55acb294af9403cabeb0c795 Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 12 May 2025 10:28:18 +0200 Subject: [PATCH 37/41] fix(Parser): fix file path handling on Windows --- src/Parser.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 84e557a..f827ef0 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -12,7 +12,12 @@ void ClapParser::parse(const int& argc, char* argv[]) { const std::string& raw_program_name = argv[0]; - this->program_name_ = raw_program_name.substr(raw_program_name.rfind('/') + 1); +#ifdef _WIN32 + const char path_separator = '\\'; +#else + const char path_separator = '/'; +#endif + this->program_name_ = raw_program_name.substr(raw_program_name.find_last_of(path_separator) + 1); std::vector args(argv + 1, argv + argc); this->apply_defaults(); From 953cc0e1124ddff711271f9deb61c27ca7cc8c8d Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 12 May 2025 10:40:53 +0200 Subject: [PATCH 38/41] fix(Parser): remove .exe from env name on windows --- src/Parser.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index f827ef0..3930179 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -13,11 +12,14 @@ void ClapParser::parse(const int& argc, char* argv[]) { const std::string& raw_program_name = argv[0]; #ifdef _WIN32 - const char path_separator = '\\'; + raw_program_name.substr(raw_program_name.find_last_of('\\') + 1); + if (std::string_view(raw_program_name).ends_with(".exe")) { + raw_program_name.erase(raw_program_name.size() - 4); + } + this->program_name_ = raw_program_name; #else - const char path_separator = '/'; + this->program_name_ = raw_program_name.substr(raw_program_name.find_last_of('/') + 1); #endif - this->program_name_ = raw_program_name.substr(raw_program_name.find_last_of(path_separator) + 1); std::vector args(argv + 1, argv + argc); this->apply_defaults(); From df68f3e54fd257cfeadf0011b070ad5dab7bc2b4 Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 12 May 2025 11:10:28 +0200 Subject: [PATCH 39/41] fix(Parser): remove windows .exe from names (for real this time?) --- src/Parser.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Parser.cpp b/src/Parser.cpp index 3930179..dcdfc44 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -12,14 +12,12 @@ void ClapParser::parse(const int& argc, char* argv[]) { const std::string& raw_program_name = argv[0]; #ifdef _WIN32 - raw_program_name.substr(raw_program_name.find_last_of('\\') + 1); - if (std::string_view(raw_program_name).ends_with(".exe")) { - raw_program_name.erase(raw_program_name.size() - 4); - } - this->program_name_ = raw_program_name; + std::string parsed_name = raw_program_name.substr(raw_program_name.find_last_of('\\') + 1); + parsed_name.erase(parsed_name.size() - 4); #else - this->program_name_ = raw_program_name.substr(raw_program_name.find_last_of('/') + 1); + std::string parsed_name = raw_program_name.substr(raw_program_name.find_last_of('/') + 1); #endif + this->program_name_ = parsed_name; std::vector args(argv + 1, argv + argc); this->apply_defaults(); From 4a0b152aff2d7a3fe2b789885308bed02bfaef96 Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 12 May 2025 10:10:08 +0200 Subject: [PATCH 40/41] feat(github): added push tester workflow --- .github/workflows/meson-test.yml | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/meson-test.yml diff --git a/.github/workflows/meson-test.yml b/.github/workflows/meson-test.yml new file mode 100644 index 0000000..988954e --- /dev/null +++ b/.github/workflows/meson-test.yml @@ -0,0 +1,38 @@ +name: Meson Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Meson tests (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Meson and Ninja + run: | + python -m pip install --upgrade pip + pip install meson ninja + + - name: Setup build directory + run: meson setup build + + - name: Build + run: meson compile -C build + + - name: Run tests + run: meson test -C build --print-errorlogs From 7544a4a5f267609a514845023370c36d15a9efea Mon Sep 17 00:00:00 2001 From: csboo Date: Mon, 12 May 2025 11:18:24 +0200 Subject: [PATCH 41/41] feat(github): always print test logs for verbosity --- .github/workflows/meson-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/meson-test.yml b/.github/workflows/meson-test.yml index 988954e..a1943fc 100644 --- a/.github/workflows/meson-test.yml +++ b/.github/workflows/meson-test.yml @@ -36,3 +36,6 @@ jobs: - name: Run tests run: meson test -C build --print-errorlogs + + - name: Print test logs + run: cat build/meson-logs/testlog.txt || echo "No meson logs found"