diff --git a/.github/workflows/meson-test.yml b/.github/workflows/meson-test.yml index a1943fc..c066229 100644 --- a/.github/workflows/meson-test.yml +++ b/.github/workflows/meson-test.yml @@ -4,7 +4,7 @@ on: push: branches: [main] pull_request: - branches: [main] + branches: [main, update] jobs: test: diff --git a/include/Arg.hpp b/include/Arg.hpp deleted file mode 100644 index 49b3275..0000000 --- a/include/Arg.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "Macros.hpp" - -class Arg { - public: - Arg(std::string name); - - Arg& short_name(const std::string& short_name); - Arg& long_name(const std::string& long_name); - Arg& help(const std::string& help); - Arg& required(bool is_required); - Arg& is_flag(); - Arg& accepts_many(); - Arg& default_value(const std::string& default_val); - Arg& from_env(const char* env_var_name); - Arg& auto_env(); - - static void print_arg(std::ostream& os, const Arg& arg, int indent); - friend std::ostream& operator<<(std::ostream& os, const Arg& arg); - - private: - friend class ClapParser; - - std::string name_; - std::string short_name_; - std::string long_name_; - std::string help_; - bool is_required_; - bool is_flag_; - bool accepts_many_; - std::string env_name_; - bool auto_env_; - std::string default_value_; - std::optional value_; - - // ----| Getters & Setters |---- - DEFINE_GETTER_SETTER(name, std::string) - DEFINE_GETTER_SETTER(short_name, std::string) - DEFINE_GETTER_SETTER(long_name, std::string) - DEFINE_GETTER_SETTER(help, std::string) - DEFINE_GETTER_SETTER(is_required, bool) - DEFINE_GETTER_SETTER(is_flag, bool) - DEFINE_GETTER_SETTER(accepts_many, bool) - DEFINE_GETTER_SETTER(env_name, std::string) - DEFINE_GETTER_SETTER(auto_env, bool) - DEFINE_GETTER_SETTER(default_value, std::string) - DEFINE_GETTER_SETTER(value, std::optional) - - // ----| Checkers |---- - // has_env_ - [[nodiscard]] bool has_env() const { return !this->env_name_.empty(); } - - // has_default_ - [[nodiscard]] bool has_default() const { return !this->default_value_.empty(); } - - // has_value_ - [[nodiscard]] bool has_value() const { return this->value_.has_value(); } -}; diff --git a/include/Macros.hpp b/include/Macros.hpp index 49e1049..15e17e2 100644 --- a/include/Macros.hpp +++ b/include/Macros.hpp @@ -1,29 +1,55 @@ #pragma once -#define DEFINE_PARSABLE_BASIC_TYPE(TYPE) \ +#define DEFINE_PARSABLE_NUM_TYPE(TYPE) \ template <> \ struct Parse { \ static std::optional parse(std::string_view s) { \ - TYPE value; \ + TYPE value = 0; \ auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); \ - if (ec == std::errc()) return value; \ - return std::nullopt; \ + return ec == std::errc() ? std::optional{value} : std::nullopt; \ } \ }; -#define DEFINE_PARSABLE_FLOAT_TYPE(TYPE, CONVERT_FN) \ - template <> \ - struct Parse { \ - static std::optional parse(std::string_view s) { \ - char* end = nullptr; \ - TYPE value = CONVERT_FN(s.data(), &end); \ - if (end == s.data() + s.size()) { \ - return value; \ - } \ - return std::nullopt; \ - } \ - }; +// #define DEFINE_PARSABLE_FLOAT_TYPE(TYPE) \ +// template <> \ +// struct Parse { \ +// static std::optional parse(std::string_view s) { \ +// TYPE value = 0; \ +// auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value, std::chars_format::scientific); \ +// return ec == std::errc() ? std::optional{value} : std::nullopt; \ +// } \ +// }; + +#define DEFINE_GETTER_SETTER_OVERRIDE(NAME, TYPE) \ + [[nodiscard]] inline const TYPE& get__##NAME() const override { return this->NAME##_; } \ + inline void set__##NAME(const TYPE& NAME) override { this->NAME##_ = NAME; } + +#define DEFINE_GETTER_SETTER_VIRTUAL(NAME, TYPE) \ + [[nodiscard]] virtual const TYPE& get__##NAME() const = 0; \ + virtual void set__##NAME(const TYPE& NAME) = 0; + +#define ARG_USER_BOOL_FUNCTION_SAFE(NAME, TARGET_STATE, VALUE, ERROR_MSG, ...) \ + [[nodiscard]] inline auto NAME() { \ + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = VALUE; \ + Arg<__VA_ARGS__> next = std::move(*this); \ + return next; \ + } + +#define ARG_USER_CUSTOM_FUNCTION_SAFE(NAME, TYPE, TARGET_STATE, ERROR_MSG, ...) \ + [[nodiscard]] inline auto NAME(TYPE NAME) { \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = std::move(NAME); \ + Arg<__VA_ARGS__> next = *this; \ + return next; \ + } -#define DEFINE_GETTER_SETTER(NAME, TYPE) \ - [[nodiscard]] inline const TYPE& get__##NAME() const { return this->NAME##_; } \ - inline void set__##NAME(const TYPE& NAME) { this->NAME##_ = NAME; } +#define ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(NAME, TYPE, TARGET_STATE, ERROR_MSG, ...) \ + [[nodiscard]] inline auto NAME(TYPE NAME) { \ + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); \ + static_assert(std::is_same_v, ERROR_MSG); \ + this->NAME##_ = std::move(NAME); \ + Arg<__VA_ARGS__> next = *this; \ + return next; \ + } diff --git a/include/Parsables.hpp b/include/Parsables.hpp index 343545f..ec8e983 100644 --- a/include/Parsables.hpp +++ b/include/Parsables.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -20,29 +19,59 @@ concept Parseable = requires(std::string_view s) { }; // Integer types -DEFINE_PARSABLE_BASIC_TYPE(int8_t) -DEFINE_PARSABLE_BASIC_TYPE(uint8_t) -DEFINE_PARSABLE_BASIC_TYPE(int16_t) -DEFINE_PARSABLE_BASIC_TYPE(uint16_t) -DEFINE_PARSABLE_BASIC_TYPE(int32_t) -DEFINE_PARSABLE_BASIC_TYPE(uint32_t) -DEFINE_PARSABLE_BASIC_TYPE(int64_t) -DEFINE_PARSABLE_BASIC_TYPE(uint64_t) +DEFINE_PARSABLE_NUM_TYPE(int8_t) +DEFINE_PARSABLE_NUM_TYPE(uint8_t) +DEFINE_PARSABLE_NUM_TYPE(int16_t) +DEFINE_PARSABLE_NUM_TYPE(uint16_t) +DEFINE_PARSABLE_NUM_TYPE(int32_t) +DEFINE_PARSABLE_NUM_TYPE(uint32_t) +DEFINE_PARSABLE_NUM_TYPE(int64_t) +DEFINE_PARSABLE_NUM_TYPE(uint64_t) // Floating-point types -DEFINE_PARSABLE_FLOAT_TYPE(float, std::strtof) -DEFINE_PARSABLE_FLOAT_TYPE(double, std::strtod) -DEFINE_PARSABLE_FLOAT_TYPE(long double, std::strtold) +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + float value = std::strtof(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + double value = std::strtod(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; +template <> +struct Parse { + static std ::optional parse(std ::string_view s) { + char* end = nullptr; + long double value = std::strtold(s.data(), &end); + if (end == s.data() + s.size()) return value; + return std::nullopt; + } +}; template <> struct Parse { - static std::optional parse(std::string_view s) { return std::string(s.data()); } + static std::optional parse(std::string_view s) { return std::string(s.data(), s.data() + s.size()); } }; template <> struct Parse { static std::optional parse(std::string_view s) { - auto as_int = Parse::parse(s).value(); - return as_int; + auto value = Parse::parse(s); + if (!value.has_value()) { + return std::nullopt; + } + if (value.value() == 0) { + return std::optional{ false }; + } + return std::optional{ true }; } }; diff --git a/include/Parser.hpp b/include/Parser.hpp index 2c29de0..9f6c506 100644 --- a/include/Parser.hpp +++ b/include/Parser.hpp @@ -1,44 +1,54 @@ #pragma once +#include +#include #include #include +#include #include #include #include -#include "Arg.hpp" +#include "arg/Arg.hpp" #include "Parsables.hpp" +#include "arg/BaseArg.hpp" #include "utils.hpp" class ClapParser { public: - void add_arg(const Arg& arg); + template + void add_arg(Arg arg) { args_.emplace_back(std::make_unique>(std::move(arg))); } void parse(const int& argc, char* argv[]); void print_help() const; template requires Parseable std::optional get_one_as(const std::string& name) { - Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; }); - return Parse::parse(arg->get__value().value()); + auto *arg = ok_or_throw_str(ClapParser::find_arg(*this, name), "no option with name: " + quote(name) + " was found"); + auto value = ok_or_throw_str(arg->get__value(), "value for option: " + quote(arg->get__long_name()) + " is missing"); + + return Parse::parse(value); } static void print_parser(std::ostream& os, const ClapParser& parser, int indent); friend std::ostream& operator<<(std::ostream& os, const ClapParser& parser); private: - std::vector args_; + std::vector> args_; std::string program_name_; // Helper methods static bool is_option(const std::string& token); static bool is_long_option(const std::string& token); static bool is_short_option(const std::string& token); - static std::optional find_arg(ClapParser& parser, const std::string& name); + static std::optional find_arg(ClapParser& parser, const std::string& arg_name); + // static std::optional find_arg(ClapParser& parser, const std::string& name); void apply_defaults(); - void parse_cli_args(const std::vector& args); + void parse_cli_args(std::vector& args); void check_env(); void parse_positional_args(const std::vector& args); - static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args); + static void parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector& args); + static void parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std::vector& args); + static void analyze_token(std::string& token, size_t& cli_index, std::vector& args); }; diff --git a/include/arg/Arg.hpp b/include/arg/Arg.hpp new file mode 100644 index 0000000..9e576dd --- /dev/null +++ b/include/arg/Arg.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "BaseArg.hpp" +#include "Macros.hpp" + +// === Type state flags === +struct NotSet {}; +struct Set {}; + +// === Builder class with full type-state enforcement === +template +class Arg final : public BaseArg { + public: + Arg() = default; + ~Arg() override = default; + + template + friend class Arg; + + template + Arg(const Arg& other) { + // name_ = std::move(other.name_); + short_name_ = std::move(other.short_name_); + long_name_ = std::move(other.long_name_); + help_ = std::move(other.help_); + is_required_ = std::move(other.is_required_); + is_flag_ = std::move(other.is_flag_); + default_value_ = std::move(other.default_value_); + accepts_many_ = std::move(other.accepts_many_); + value_ = std::move(other.value_); + auto_env_ = std::move(other.auto_env_); + env_name_ = std::move(other.env_name_); + } + + ARG_USER_CUSTOM_FUNCTION_SAFE(long_name, std::string, LongName, "Error: don't call long_name() more than once!", + ShortName, Set, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(short_name, std::string, ShortName, "Error: don't call short_name() more than once!", + Set, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(help, std::string, Help, "Error: don't call help() more than once!", ShortName, + LongName, Set, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(is_required, IsRequired, true, "Error: don't call is_required() more than once!", ShortName, + LongName, Help, Set, IsFlag, AcceptsMany, DefaultValue, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(is_flag, IsFlag, true, "Error: don't call is_flag() more than once!", ShortName, + LongName, Help, IsRequired, Set, AcceptsMany, DefaultValue, FromEnv, AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(accepts_many, AcceptsMany, true, "Error: don't call accepts_many() more than once!", + ShortName, LongName, Help, IsRequired, IsFlag, Set, DefaultValue, FromEnv, + AutoEnv) + ARG_USER_CUSTOM_FUNCTION_SAFE_ISNAMED(default_value, std::string, DefaultValue, "Error: don't call default_value() more than once!", + ShortName, LongName, Help, IsRequired, IsFlag, AcceptsMany, Set, FromEnv, + AutoEnv) + ARG_USER_BOOL_FUNCTION_SAFE(auto_env, AutoEnv, true, "Error: don't call auto_env() more than once!", ShortName, + LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, FromEnv, Set) + + // this is expanded for its different functionality + [[nodiscard]] inline auto from_env(std::string env_var_name) { + static_assert(std ::is_same_v, "Error: I need a name! [.long_name(), .short_name()]"); + static_assert(std ::is_same_v, "Error: don't call auto_env() more than once!"); + this->env_name_ = std::move(env_var_name); + Arg next = + std ::move(*this); + return next; + } + + private: + friend class ClapParser; + + friend std::ostream& operator<<(std::ostream& os, const BaseArg& arg); + + // ----| Getters & Setters |---- + // DEFINE_GETTER_SETTER_OVERRIDE(name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(short_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(long_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(help, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(is_required, bool) + DEFINE_GETTER_SETTER_OVERRIDE(is_flag, bool) + DEFINE_GETTER_SETTER_OVERRIDE(accepts_many, bool) + DEFINE_GETTER_SETTER_OVERRIDE(env_name, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(auto_env, bool) + DEFINE_GETTER_SETTER_OVERRIDE(default_value, std::string) + DEFINE_GETTER_SETTER_OVERRIDE(value, std::optional) + + // ----| Checkers |---- + // has_env_ + [[nodiscard]] bool has_env() const override { return !this->env_name_.empty(); } + + // has_default_ + [[nodiscard]] bool has_default() const override { return !this->default_value_.empty(); } + + // has_value_ + [[nodiscard]] bool has_value() const override { return this->value_.has_value(); } +}; diff --git a/include/arg/BaseArg.hpp b/include/arg/BaseArg.hpp new file mode 100644 index 0000000..3afb4ce --- /dev/null +++ b/include/arg/BaseArg.hpp @@ -0,0 +1,87 @@ +// BaseArg.hpp +#pragma once + +#include "Macros.hpp" +#include "utils.hpp" +#include +#include + +class BaseArg { + public: + BaseArg() = default; + virtual ~BaseArg(); + + void print_arg(std::ostream& os, int indent = 0) const { + print_indent(os, indent); + os << "Arg {\n"; + + print_indent(os, indent + 1); + os << "short: \"" << short_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "long: \"" << long_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "help: \"" << help_ << "\",\n"; + print_indent(os, indent + 1); + os << "required: " << std::boolalpha << is_required_ << ",\n"; + print_indent(os, indent + 1); + os << "is_flag: " << std::boolalpha << is_flag_ << ",\n"; + print_indent(os, indent + 1); + os << "accepts_many: " << std::boolalpha << accepts_many_ << ",\n"; + print_indent(os, indent + 1); + os << "default: \"" << default_value_ << "\",\n"; + print_indent(os, indent + 1); + os << "auto_env: \"" << std::boolalpha << auto_env_ << "\",\n"; + print_indent(os, indent + 1); + os << "env_name: \"" << env_name_ << "\",\n"; + print_indent(os, indent + 1); + os << "value: "; + if (value_) { + os << "\"" << value_.value() << "\""; + } else { + os << "std::nullopt"; + } + os << '\n'; + + print_indent(os, indent); + os << "}"; + } + + // DEFINE_GETTER_SETTER_VIRTUAL(name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(short_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(long_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(help, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(is_required, bool) + DEFINE_GETTER_SETTER_VIRTUAL(is_flag, bool) + DEFINE_GETTER_SETTER_VIRTUAL(accepts_many, bool) + DEFINE_GETTER_SETTER_VIRTUAL(env_name, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(auto_env, bool) + DEFINE_GETTER_SETTER_VIRTUAL(default_value, std::string) + DEFINE_GETTER_SETTER_VIRTUAL(value, std::optional) + + // ----| Checkers |---- + // has_env_ + [[nodiscard]] virtual bool has_env() const = 0; + + // has_default_ + [[nodiscard]] virtual bool has_default() const = 0; + + // has_value_ + [[nodiscard]] virtual bool has_value() const = 0; + + protected: + std::string short_name_; + std::string long_name_; + std::string help_; + bool is_required_{}; + bool is_flag_{}; + bool accepts_many_{}; + std::string env_name_; + bool auto_env_{}; + std::string default_value_; + std::optional value_ = std::nullopt; +}; + +inline std::ostream& operator<<(std::ostream& os, const BaseArg& arg) { + arg.print_arg(os, 0); + return os; +} diff --git a/include/utils.hpp b/include/utils.hpp index 73e4093..1069a81 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -6,13 +6,11 @@ #include #include -template -inline T ok_or(std::optional opt, E&& err) { - if (!opt) { - std::forward(err)(); - } - return *opt; -} +// this is really dangerous and broken :D +// template +// inline T ok_or(std::optional opt, E&& err) { +// return opt.has_value() ? *opt : std::forward(err)(); +// } template inline T ok_or_throw_str(std::optional opt, const std::string& err) { diff --git a/meson.build b/meson.build index ee36240..766a218 100644 --- a/meson.build +++ b/meson.build @@ -10,21 +10,21 @@ project('claplusplus', 'cpp', ) # Add include directory for headers -include_dir = include_directories('include') +include_dir = include_directories('include', 'include/arg') # Build executable directly from all necessary sources executable('claPlusPlus', sources: [ 'src/example.cpp', - 'src/Arg.cpp', - 'src/Parser.cpp' + 'src/Parser.cpp', + 'src/BaseArg.cpp' ], include_directories: include_dir, install: false ) parser_lib = static_library('clap_parser', - sources: ['src/Parser.cpp', 'src/Arg.cpp'], + sources: ['src/Parser.cpp', 'src/BaseArg.cpp'], include_directories: include_dir, install: false ) diff --git a/src/Arg.cpp b/src/Arg.cpp deleted file mode 100644 index b4b3811..0000000 --- a/src/Arg.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "Arg.hpp" - -#include - -#include -#include - -#include "utils.hpp" - -Arg::Arg(std::string name) - : name_(std::move(name)), - long_name_(this->name_), - is_required_(false), - is_flag_(false), - accepts_many_(false), - auto_env_(false), - value_(std::nullopt) {} - -// Setters -Arg& Arg::short_name(const std::string& short_name) { - short_name_ = short_name; - return *this; -} -Arg& Arg::help(const std::string& help) { - help_ = help; - return *this; -} -Arg& Arg::required(bool is_required) { - is_required_ = is_required; - return *this; -} -Arg& Arg::is_flag() { - is_flag_ = true; - default_value_ = "0"; - return *this; -} -Arg& Arg::accepts_many() { - accepts_many_ = true; - return *this; -} -Arg& Arg::default_value(const std::string& default_value) { - default_value_ = default_value; - return *this; -} -Arg& Arg::from_env(const char* env_var_name) { - this->env_name_ = env_var_name; - return *this; -}; -Arg& Arg::auto_env() { - this->auto_env_ = true; - return *this; -}; - -void Arg::print_arg(std::ostream& os, const Arg& arg, int indent) { - print_indent(os, indent); - os << "Arg {\n"; - - print_indent(os, indent + 1); - os << "name: \"" << arg.name_ << "\",\n"; - print_indent(os, indent + 1); - os << "short: \"" << arg.short_name_ << "\",\n"; - print_indent(os, indent + 1); - os << "long: \"" << arg.long_name_ << "\",\n"; - print_indent(os, indent + 1); - os << "help: \"" << arg.help_ << "\",\n"; - print_indent(os, indent + 1); - os << "required: " << std::boolalpha << arg.is_required_ << ",\n"; - print_indent(os, indent + 1); - os << "is_flag: " << std::boolalpha << arg.is_flag_ << ",\n"; - print_indent(os, indent + 1); - os << "accepts_many: " << std::boolalpha << arg.accepts_many_ << ",\n"; - print_indent(os, indent + 1); - os << "default: \"" << arg.default_value_ << "\",\n"; - print_indent(os, indent + 1); - os << "value: "; - if (arg.value_) { - os << "\"" << arg.value_.value() << "\""; - } else { - os << "std::nullopt"; - } - os << '\n'; - - print_indent(os, indent); - os << "}"; -} - -std::ostream& operator<<(std::ostream& os, const Arg& arg) { - Arg::print_arg(os, arg, 0); - return os; -} diff --git a/src/BaseArg.cpp b/src/BaseArg.cpp new file mode 100644 index 0000000..43c27cf --- /dev/null +++ b/src/BaseArg.cpp @@ -0,0 +1,3 @@ +#include "arg/BaseArg.hpp" + +BaseArg::~BaseArg() = default; diff --git a/src/Parser.cpp b/src/Parser.cpp index cdfb31c..1cc2ed0 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -8,7 +8,8 @@ #include #include -#include "Arg.hpp" +#include "arg/Arg.hpp" +#include "arg/BaseArg.hpp" #include "utils.hpp" void ClapParser::parse(const int& argc, char* argv[]) { @@ -29,34 +30,60 @@ void ClapParser::parse(const int& argc, char* argv[]) { // Validate all arguments that need values received them for (const auto& arg : args_) { // std::cerr << arg << "\n\n\n"; - if (arg.get__is_required() && !arg.has_value()) { - throw std::runtime_error("argument '" + arg.get__name() + "' is required"); + if (arg->get__is_required() && !arg->has_value()) { + throw std::runtime_error("argument '" + arg->get__long_name() + "' is required"); } } } -void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); } +void ClapParser::parse_cli_args(std::vector& args) { + // std::cerr << "\nstart cli arg parsing\n"; + // std::cerr << "args vector: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; -void ClapParser::parse_cli_args(const std::vector& args) { for (size_t i = 0; i < args.size(); ++i) { - const auto& token = args.at(i); + std::string token = args.at(i); + // std::cerr << "\ngoing through args: size=" << args.size() << ", current token=" << quote(token) << ", index=" << i << "\n"; + // TODO this could be better with string view contains? if (token == "--help" || token == "-h") { print_help(); exit(0); } - auto* arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: \'" + token); + // solve --opt="value" stuff + ClapParser::analyze_token(token, i, args); + if (token.starts_with("--")) { + token = token.substr(2); + } else if (token.starts_with("-")) { + token = token.substr(1); + } + // std::cerr << "args vector AGAIN: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; + + + auto* arg = ok_or_throw_str(ClapParser::find_arg(*this, token), "unknown option: " + quote(token)); if (!arg->get__is_flag()) { + // std::cerr << "\nparsing non-flag\n"; ClapParser::parse_value_for_non_flag(arg, i, args); } else { - arg->set__value("1"); + // std::cerr << "\nparsing flag\n"; + ClapParser::parse_value_for_flag(arg, i, args); } } + // std::cerr << "cli arg parsing done\n"; + // std::cerr << *this << "\n"; } -void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector& args) { +void ClapParser::parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector& args) { + // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { if (arg->get__accepts_many()) { std::string value; @@ -64,30 +91,86 @@ void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std value += args.at(cli_index + 1) + ' '; cli_index++; } + // std::cerr << "value " << value << "\n"; arg->set__value(value); } else { + // std::cerr << "value " << args.at(cli_index + 1) << "\n"; arg->set__value(args.at(cli_index + 1)); cli_index++; // Skip the value in the next iteration } } else { - throw std::runtime_error("option '" + arg->get__name() + "' requires a value but none was provided"); + throw std::runtime_error("option '" + arg->get__long_name() + "' requires a value but none was provided"); + } +} + +void ClapParser::parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std::vector& args) { + // std::cerr << "\ncli_ind, and arg size: " << cli_index << " " << args.size() << "\n"; + if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) { + // std::cerr << "found flag with value in cli value: ("; + if (args.at(cli_index + 1) == "true" || args.at(cli_index + 1) == "1") { + // std::cerr << args.at(cli_index + 1) << ")\n"; + arg->set__value("1"); + cli_index++; + } else if (args.at(cli_index + 1) == "false" || args.at(cli_index + 1) == "0") { + // std::cerr << args.at(cli_index + 1) << ")\n"; + arg->set__value("0"); + cli_index++; + } else { + // std::cerr << "WRONG VALUE, throwing\n"; + throw std::runtime_error("boolean option " + quote(arg->get__long_name()) + " strictly takes: true|false|1|0 (got: " + args.at(cli_index + 1) + ")"); + } + } else { + // std::cerr << "no value for flag, fallback to 1\n"; + arg->set__value("1"); + } +} + +void ClapParser::analyze_token(std::string& token, size_t& cli_index, std::vector& args) { + // std::cerr << "\nstarting token analysis for: " << quote(token) << "\n"; + // std::cerr << "[WARNING]: might mess up arg vector!\n"; + if (token.contains('=')) { + // std::cerr << "'=' found, separating token ( "; + const auto middle = token.find('='); + + std::string token_name = token.substr(0, middle); + // std::cerr << "name: " << quote(token_name) << ", "; + std::string token_value = token.substr(middle + 1); + if (token_value.empty()) { + throw std::runtime_error("value not specified after '='"); + } + // std::cerr << "value: " << quote(token_value) << " )\n"; + + args.at(cli_index) = token_value; + // std::cerr << "TEST: " << cli_index << "\n"; + cli_index--; + + // std::cerr << "args vector: ["; + // for (const auto& i : args) { + // std::cerr << quote(i) << " "; + // } + // std::cerr << "]\n"; + + // std::cerr << "ending token analysis\n"; + token = token_name; + } else { + // std::cerr << "ending token analysis, left alone\n"; } } void ClapParser::check_env() { for (auto& arg : args_) { - if (arg.get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg.get__name(); + if (arg->get__auto_env()) { + std::string env_name = this->program_name_ + '_' + arg->get__long_name(); to_upper(env_name); auto* value_from_env = std::getenv(env_name.c_str()); if (value_from_env != nullptr) { - arg.set__value(value_from_env); + arg->set__value(value_from_env); } } - if (arg.has_env()) { - auto* value_from_env = std::getenv(arg.get__env_name().c_str()); + if (arg->has_env()) { + auto* value_from_env = std::getenv(arg->get__env_name().c_str()); if (value_from_env != nullptr) { - arg.set__value(value_from_env); + arg->set__value(value_from_env); } } } @@ -108,45 +191,44 @@ void ClapParser::print_help() const { std::cout << "\n\nOptions:\n"; for (const auto& arg : args_) { - arg.get__short_name().empty() ? std::cout << " " : std::cout << " -" << arg.get__short_name() << ", "; - std::cout << "--" << arg.get__long_name(); - std::cout << "\t" << arg.get__help(); - if (arg.has_default()) { - std::cout << " (default: " << arg.get__default_value() << ")"; + arg->get__short_name().empty() ? std::cout << " " : std::cout << " -" << arg->get__short_name() << ", "; + arg->get__long_name().empty() ? std::cout << "\t" : std::cout << "--" << arg->get__long_name(); + std::cout << "\t" << arg->get__help(); + if (arg->has_default()) { + std::cout << " (default: " << arg->get__default_value() << ")"; } - if (arg.has_env()) { - std::cout << " [env: " << arg.get__env_name() << "]"; + if (arg->has_env()) { + std::cout << " [env: " << arg->get__env_name() << "]"; } - if (arg.get__auto_env()) { - std::string env_name = this->program_name_ + '_' + arg.get__name(); + if (arg->get__auto_env()) { + std::string env_name = this->program_name_ + '_' + arg->get__long_name(); to_upper(env_name); std::cout << " [def.env: " << env_name << "]"; } std::cout << "\n"; } - std::cout << " "; - std::cout << "-h" << ", "; - std::cout << "--help"; - std::cout << "\t" << "Prints this help message"; - std::cout << "\n"; + std::cout << " " << "-h" << ", " << "--help" << "\t" << "Prints this help message" << "\n"; } // Helper methods -std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { - auto it = std::ranges::find_if(parser.args_, [&](Arg& arg) { - return ("--" + arg.get__long_name() == arg_name || "-" + arg.get__short_name() == arg_name); +std::optional ClapParser::find_arg(ClapParser& parser, const std::string& arg_name) { + auto it = std::ranges::find_if(parser.args_, [&](std::unique_ptr& arg) { + return (arg->get__long_name() == arg_name || arg->get__short_name() == arg_name); }); if (it == parser.args_.end()) { return std::nullopt; } - return &(*it); + return it->get(); } void ClapParser::apply_defaults() { for (auto& arg : args_) { - if (!arg.has_value() && arg.has_default()) { - arg.set__value(arg.get__default_value()); + if (arg->get__is_flag() && !arg->has_default()) { + arg->set__default_value("0"); // flags are false by default always + } + if (!arg->has_value() && arg->has_default()) { + arg->set__value(arg->get__default_value()); } } } @@ -161,7 +243,7 @@ void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int in print_indent(os, indent + 1); os << "args: [\n"; for (const auto& arg : parser.args_) { - Arg::print_arg(os, arg, indent + 2); + arg->print_arg(os, indent + 2); os << ",\n"; } print_indent(os, indent + 1); diff --git a/src/example.cpp b/src/example.cpp index b16ce2a..e80707e 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -8,18 +8,18 @@ void run(ClapParser& arg_parser); int main(const int argc, char* argv[]) { ClapParser arg_parser; - auto num1 = Arg("num1").from_env("ASDF").auto_env().required(true); + auto num1 = Arg().long_name("num1").from_env("ASDF").auto_env().is_required(); // std::cerr << num1 << "\n"; arg_parser.add_arg(num1); - auto num2 = Arg("num2").short_name("N").from_env("TES").default_value("99"); + auto num2 = Arg().long_name("num2").short_name("N").from_env("TES").default_value("99.0"); arg_parser.add_arg(num2); - arg_parser.add_arg(Arg("test").is_flag()); + arg_parser.add_arg(Arg().long_name("test").is_flag()); // arg_parser.add_arg(Arg("test").is_flag(true)); try { arg_parser.parse(argc, argv); - // std::cerr << arg_parser; + std::cerr << arg_parser; run(arg_parser); } catch (const std::exception& e) { std::cerr << "\n\n\nerror: " << e.what() << "\n\n\n"; diff --git a/tests/test_combo.cpp b/tests/test_combo.cpp index 6ad23e6..7c28342 100644 --- a/tests/test_combo.cpp +++ b/tests/test_combo.cpp @@ -10,8 +10,8 @@ int main(int argc, char* argv[]) { ClapParser p; // a value arg with full priority stack: - auto val = Arg("val").from_env("VAL").auto_env().default_value("10"); - auto boolean = Arg("flag").is_flag(); + auto val = Arg().long_name("val").from_env("VAL").auto_env().default_value("10"); + auto boolean = Arg().long_name("flag").is_flag(); p.add_arg(val); p.add_arg(boolean); p.parse(argc, argv); diff --git a/tests/test_flag.cpp b/tests/test_flag.cpp index 69fe8c7..78ce6ee 100644 --- a/tests/test_flag.cpp +++ b/tests/test_flag.cpp @@ -9,7 +9,7 @@ int main(int argc, char* argv[]) { ClapParser p; - auto f = Arg("opt").is_flag(); + auto f = Arg().long_name("opt").is_flag(); p.add_arg(f); p.parse(argc, argv); diff --git a/tests/test_priority.cpp b/tests/test_priority.cpp index 4d900ef..34c849f 100644 --- a/tests/test_priority.cpp +++ b/tests/test_priority.cpp @@ -18,7 +18,7 @@ int main(const int argc, char* argv[]) { ClapParser p; - auto a = Arg("val").from_env("VAL").auto_env().default_value("1"); + auto a = Arg().long_name("val").from_env("VAL").auto_env().default_value("1"); p.add_arg(a); p.parse(argc, argv);