diff --git a/include/Arg.hpp b/include/Arg.hpp index f988cfb..41aa96a 100644 --- a/include/Arg.hpp +++ b/include/Arg.hpp @@ -1,34 +1,25 @@ #pragma once + +#include "utils.hpp" + +#include #include #include -#include -#include #include #include -class ClapParser; - class Arg { public: - explicit Arg(std::string name); + Arg(const 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); 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; + Arg& auto_env(); friend std::ostream& operator<<(std::ostream& os, const Arg& arg); @@ -36,20 +27,119 @@ 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_; + bool auto_env_; + // std::string auto_env_name_; + 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; + } + + // auto_env_ + [[nodiscard]] inline bool get__auto_env() const { + return this->auto_env_; + } + inline void set__auto_env(const bool& auto_env) { + this->auto_env_ = auto_env; + } + + // auto_env_name_ + [[nodiscard]] inline const std::string get__auto_env_name() const { + std::string env_name = PROGRAM_NAME() + '_' + this->get__name(); + to_upper(env_name); + 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(); + } + }; diff --git a/include/Parser.hpp b/include/Parser.hpp index fec0746..0a1dd02 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 @@ -11,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; @@ -24,16 +25,16 @@ class ClapParser { std::string program_name_; // Helper methods - inline bool is_option(const std::string& token) const ; - inline bool is_long_option(const std::string& token) const ; - inline bool is_short_option(const std::string& token) const ; + static inline bool is_option(const std::string& token); + static inline bool is_long_option(const std::string& token); + static inline bool is_short_option(const std::string& token); const Arg* find_option(const std::string& name) const; 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_required_args() const; void check_env(); void handle_missing_positional(const Arg& arg); @@ -45,8 +46,8 @@ class ClapParser { }; template std::optional ClapParser::get_one_as(const std::string& name) const { - auto it = values_.find(name); - if (it == values_.end()) { + auto it = this->values_.find(name); + if (it == this->values_.end()) { return std::nullopt; } diff --git a/src/utils.cpp b/include/utils.hpp similarity index 50% rename from src/utils.cpp rename to include/utils.hpp index ab9cf8b..61f08f7 100644 --- a/src/utils.cpp +++ b/include/utils.hpp @@ -1,35 +1,47 @@ +#pragma once + +#include #include +#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(); } + +inline void to_upper(std::string &s) { + std::ranges::transform(s, s.begin(), [](const unsigned char& c) { return std::toupper(c); }); +} + +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); +} diff --git a/meson.build b/meson.build index ec4fc4e..c949920 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/example.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 +# ) diff --git a/src/Arg.cpp b/src/Arg.cpp index 0f08f6a..2958857 100644 --- a/src/Arg.cpp +++ b/src/Arg.cpp @@ -1,38 +1,45 @@ +#include "Arg.hpp" + #include #include #include #include -#include - -#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_(""), + auto_env_(false), + default_value_(""), + 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_; } 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); @@ -45,40 +52,25 @@ 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; }; -// 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" - << " 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; } - -void Arg::set_try_env_name(const std::string& s){ - this->try_env_name_ = s; -} diff --git a/src/Parser.cpp b/src/Parser.cpp index 5047260..47b7c70 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -1,44 +1,44 @@ -#include "../include/Parser.hpp" +#include "Parser.hpp" #include "Arg.hpp" +#include "utils.hpp" + #include #include -#include #include #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; - apply_defaults(); - check_env(); - parse_options(args); - // parse_positional_args(args); + this->apply_defaults(); + this->check_env(); + this->parse_options(args); + // this->parse_positional_args(args); // 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"); + for (const auto& arg : this->args_) { + if (arg.get__takes_value() && !args_with_values.contains(arg.get__name())) { + if (arg.get__is_required() && !arg.has_default()) { + throw std::runtime_error("argument '" + arg.get__name() + "' requires a value"); } } } - check_required_args(); + this->check_required_args(); } -void ClapParser::add_arg(const Arg& arg) { args_.push_back(arg); } +void ClapParser::add_arg(const Arg& arg) { this->args_.push_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]; - 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); + if (ClapParser::is_long_option(token)) { + i = this->handle_long_option(token, args, i); + } else if (ClapParser::is_short_option(token)) { + i = this->handle_short_option(token, args, i); } else { // Positional arguments are handled separately break; @@ -47,20 +47,17 @@ 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::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); + for (auto& arg : this->args_) { + if (arg.auto_env_) { + std::string env_name = PROGRAM_NAME() + '_' + arg.get__name(); + to_upper(env_name); auto value_from_env = std::getenv(env_name.c_str()); if (value_from_env) { - values_[arg.name()] = value_from_env; + this->values_[arg.get__name()] = value_from_env; } } if (arg.has_env() && arg.value_.has_value()) { - values_[arg.name()] = arg.value_.value(); + this->values_[arg.get__name()] = arg.value_.value(); } } }; @@ -86,10 +83,10 @@ 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()); +void ClapParser::check_required_args() const { + for (const auto& arg : this->args_) { + if (arg.get__is_required() && this->values_.find(arg.get__name()) == this->values_.end()) { + throw std::runtime_error("missing required argument: " + arg.get__name()); } } } @@ -97,18 +94,18 @@ void ClapParser::check_required_args() { 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(); + this->print_help(); exit(0); } - const Arg* arg = find_option(opt_name); + const Arg* arg = this->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()) { - i = handle_option_with_value(arg, args, i, token); + if (arg->get__takes_value()) { + i = this->handle_option_with_value(arg, args, i, token); } else { - values_[arg->name()] = true; // Boolean flag + this->values_[arg->get__name()] = true; // Boolean flag } return i; @@ -117,18 +114,18 @@ size_t ClapParser::handle_long_option(const std::string& token, const std::vecto 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(); + this->print_help(); exit(0); } - const Arg* arg = find_option(opt_name); + const Arg* arg = this->find_option(opt_name); if (arg == nullptr) { throw std::runtime_error("unknown option: " + token); } - if (arg->takes_value()) { - i = handle_option_with_value(arg, args, i, token); + if (arg->get__takes_value()) { + i = this->handle_option_with_value(arg, args, i, token); } else { - values_[arg->name()] = true; // Boolean flag + this->values_[arg->get__name()] = true; // Boolean flag } return i; @@ -138,58 +135,58 @@ size_t ClapParser::handle_option_with_value(const Arg* arg, const std::vectorname()] = std::string(args[i + 1]); + this->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()); + this->values_[arg->get__name()] = std::string(arg->get__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; } void ClapParser::handle_missing_positional(const Arg& arg) { - if (arg.is_required()) { - throw std::runtime_error("Missing required positional argument: " + arg.name()); + if (arg.get__is_required()) { + throw std::runtime_error("missing required positional argument: " + arg.get__name()); } if (arg.has_default()) { - values_[arg.name()] = std::string(arg.default_value()); + this->values_[arg.get__name()] = std::string(arg.get__default_value()); } } - inline bool ClapParser::is_option(const std::string& token) const { + inline bool ClapParser::is_option(const std::string& token) { return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1); } - inline bool ClapParser::is_long_option(const std::string& token) const { return token.substr(0, 2) == "--"; } + inline bool ClapParser::is_long_option(const std::string& token) { return token.substr(0, 2) == "--"; } - inline bool ClapParser::is_short_option(const std::string& token) const { + inline bool ClapParser::is_short_option(const std::string& token) { return token[0] == '-' && token.size() > 1 && token[1] != '-'; } void ClapParser::print_help() const { - std::cout << "Usage: " << program_name_ << " [OPTIONS]"; - auto positionals = get_positional_args(); + std::cout << "Usage: " << PROGRAM_NAME() << " [OPTIONS]"; + auto positionals = this->get_positional_args(); for (const auto& pos : positionals) { - std::cout << " [" << pos.name() << "]"; + std::cout << " [" << pos.get__name() << "]"; } std::cout << "\n\nOptions:\n"; - for (const auto& arg : args_) { - arg.short_name().empty()? std::cout << " " : std::cout << " -" << arg.short_name() << ", "; - std::cout << "--" << arg.long_name(); - std::cout << "\t" << arg.help(); + for (const auto& arg : this->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.default_value() << ")"; + std::cout << " (default: " << arg.get__default_value() << ")"; } if (arg.has_env()) { - std::cout << " [env: " << arg.env_name_ << "]"; + std::cout << " [env: " << arg.get__env_name() << "]"; } - if (arg.try_env_) { - std::cout << " [def.env: " << arg.try_env_name_ << "]"; + if (arg.get__auto_env()) { + std::cout << " [def.env: " << arg.get__auto_env_name() << "]"; } std::cout << "\n"; } @@ -202,9 +199,9 @@ void ClapParser::print_help() const { if (!positionals.empty()) { std::cout << "\nPositional arguments:\n"; for (const auto& pos : positionals) { - std::cout << " " << pos.name() << "\t" << pos.help(); + std::cout << " " << pos.get__name() << "\t" << pos.get__help(); if (pos.has_default()) - std::cout << " (default: " << pos.default_value() << ")"; + std::cout << " (default: " << pos.get__default_value() << ")"; std::cout << "\n"; } } @@ -212,8 +209,8 @@ void ClapParser::print_help() const { // Helper methods const Arg* ClapParser::find_option(const std::string& name) const { - for (const auto& arg : args_) { - if (arg.long_name() == name || arg.short_name() == name) { + for (const auto& arg : this->args_) { + if (arg.get__long_name() == name || arg.get__short_name() == name) { return &arg; } } @@ -222,8 +219,8 @@ const Arg* ClapParser::find_option(const std::string& name) 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()) { + for (const auto& arg : this->args_) { + if (arg.get__short_name().empty() && arg.get__long_name().empty()) { positional.push_back(arg); } } @@ -231,9 +228,9 @@ 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()); + for (const auto& arg : this->args_) { + if (this->values_.find(arg.get__name()) == this->values_.end() && arg.has_default()) { + this->values_[arg.get__name()] = std::string(arg.get__default_value()); } } } @@ -242,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_) { 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'; +}