Skip to content

More test #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: update
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7f18f42
Merge pull request #17 from csboo/update
csboo May 15, 2025
7fbd662
fix(Parser): throw, when user wants a value of a non-specified, but n…
csboo May 22, 2025
998c115
feat(Parser): introduce much improved value parsing (--val=abc and al…
csboo May 22, 2025
969429e
fix(Parser): removed unused import
csboo May 22, 2025
c9cba4d
misc(Parser): don't debug to std::cerr, proper logging soon...
csboo May 22, 2025
fca738f
feat(Macros): improved number parsing algorithms
csboo May 31, 2025
76a2783
feat(Parser): whole-new, safe, templated, arg-creating logic"
csboo Jun 1, 2025
ebabdab
fix(meson): update build file
csboo Jun 3, 2025
c16af1d
fix(Parsables): parsing bool should return a std::optional<bool>
csboo Jun 3, 2025
57b8c3d
fix(Parser): don't store leadign hyphens of options
csboo Jun 3, 2025
9658da9
feat(Arg): simplify template for constructor with other 'Arg(other Arg)'
csboo Jun 3, 2025
09c16ab
fix(Macros): logical return value of Parse struct corrected
csboo Jun 4, 2025
46c3f6e
fix(Macros): removed that constructor generator that's not used any more
csboo Jun 4, 2025
7f0d6fd
fix(Macros): less boilerplate with 'auto'
csboo Jun 4, 2025
2cc69be
fix(utils)!: removed ok_or(), wrong (probably) dangerous implementation
csboo Jun 4, 2025
a7dbeaf
fix(Parser): remove ok_or(), slighly cleaner impl
csboo Jun 4, 2025
b9b4130
misc(Arg): moved 'print_arg()' to 'BaseArg'
csboo Jun 4, 2025
2480a54
misc(Arg): cleaned up some unused stuff
csboo Jun 4, 2025
99ac748
misc(Parser): comment out debuginfo
csboo Jun 4, 2025
ee40e36
misc(Parser): 4 lines -> 1 line, who wrote it that way?
csboo Jun 4, 2025
19404a8
fix(Parser): flags are (again) false when not specified
csboo Jun 4, 2025
ac34f2b
feat(Arg): re-introduce from_env()
csboo Jun 4, 2025
c151c89
feat(Arg, Parser)!: starting to merge .name() into .long_name(), (the…
csboo Jun 4, 2025
083ecf1
fix(Arg, Parser): long_name() is main name, Arg() must call long_name…
csboo Jun 4, 2025
db834e7
fix(tests): updated to latest api
csboo Jun 4, 2025
a072a12
fix(Parsables): all numbers should be parsed with std::chars_format::…
csboo Jun 4, 2025
87889cf
feat(example): update example to use latest api
csboo Jun 4, 2025
828a319
feat(github): run tests on pull request to 'update'
csboo Jun 4, 2025
c54ac24
fix(Parsables): more explicit bool parsing, let's try to fix cross-pl…
csboo Jun 4, 2025
e3985a7
revert(Parsables): MacOs compatibility, revert to older float parser …
csboo Jun 4, 2025
d99c62f
feat(tests): added 2 more tests for checking number parsing capabilities
csboo Jun 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/meson-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
branches: [main, update]

jobs:
test:
Expand Down
64 changes: 0 additions & 64 deletions include/Arg.hpp

This file was deleted.

64 changes: 45 additions & 19 deletions include/Macros.hpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
#pragma once

#define DEFINE_PARSABLE_BASIC_TYPE(TYPE) \
#define DEFINE_PARSABLE_NUM_TYPE(TYPE) \
template <> \
struct Parse<TYPE> { \
static std::optional<TYPE> 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<TYPE> { \
static std::optional<TYPE> 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<TYPE> { \
// static std::optional<TYPE> 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<LongName, Set>, "Error: I need a name! [.long_name(), .short_name()]"); \
static_assert(std::is_same_v<TARGET_STATE, NotSet>, 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<TARGET_STATE, NotSet>, 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<LongName, Set>, "Error: I need a name! [.long_name(), .short_name()]"); \
static_assert(std::is_same_v<TARGET_STATE, NotSet>, ERROR_MSG); \
this->NAME##_ = std::move(NAME); \
Arg<__VA_ARGS__> next = *this; \
return next; \
}
59 changes: 44 additions & 15 deletions include/Parsables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
Expand All @@ -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<float> {
static std ::optional<float> 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<double> {
static std ::optional<double> 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<long double> {
static std ::optional<long double> 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<std::string> {
static std::optional<std::string> parse(std::string_view s) { return std::string(s.data()); }
static std::optional<std::string> parse(std::string_view s) { return std::string(s.data(), s.data() + s.size()); }
};

template <>
struct Parse<bool> {
static std::optional<bool> parse(std::string_view s) {
auto as_int = Parse<int>::parse(s).value();
return as_int;
auto value = Parse<int>::parse(s);
if (!value.has_value()) {
return std::nullopt;
}
if (value.value() == 0) {
return std::optional{ false };
}
return std::optional{ true };
}
};
26 changes: 18 additions & 8 deletions include/Parser.hpp
Original file line number Diff line number Diff line change
@@ -1,44 +1,54 @@
#pragma once

#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#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<typename... States>
void add_arg(Arg<States...> arg) { args_.emplace_back(std::make_unique<Arg<States...>>(std::move(arg))); }
void parse(const int& argc, char* argv[]);
void print_help() const;

template <typename T>
requires Parseable<T>
std::optional<T> get_one_as(const std::string& name) {
Arg* arg = ok_or(ClapParser::find_arg(*this, "--" + name), [] { return std::nullopt; });
return Parse<T>::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<T>::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<Arg> args_;
std::vector<std::unique_ptr<BaseArg>> 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<Arg*> find_arg(ClapParser& parser, const std::string& name);
static std::optional<BaseArg*> find_arg(ClapParser& parser, const std::string& arg_name);
// static std::optional<BaseArg*> find_arg(ClapParser& parser, const std::string& name);
void apply_defaults();

void parse_cli_args(const std::vector<std::string>& args);
void parse_cli_args(std::vector<std::string>& args);
void check_env();
void parse_positional_args(const std::vector<std::string>& args);
static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector<std::string>& args);
static void parse_value_for_non_flag(BaseArg* arg, size_t& cli_index, const std::vector<std::string>& args);
static void parse_value_for_flag(BaseArg* arg, size_t& cli_index, const std::vector<std::string>& args);
static void analyze_token(std::string& token, size_t& cli_index, std::vector<std::string>& args);
};
102 changes: 102 additions & 0 deletions include/arg/Arg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#pragma once

#include <algorithm>
#include <iostream>
#include <optional>
#include <ostream>
#include <string>

#include "BaseArg.hpp"
#include "Macros.hpp"

// === Type state flags ===
struct NotSet {};
struct Set {};

// === Builder class with full type-state enforcement ===
template <typename ShortName = NotSet, typename LongName = NotSet, typename Help = NotSet, typename IsRequired = NotSet,
typename IsFlag = NotSet, typename AcceptsMany = NotSet, typename DefaultValue = NotSet,
typename FromEnv = NotSet, typename AutoEnv = NotSet>
class Arg final : public BaseArg {
public:
Arg() = default;
~Arg() override = default;

template<typename SN, typename LN, typename H, typename IR, typename IF,
typename AM, typename DV, typename FE, typename AE>
friend class Arg;

template<typename... States>
Arg(const Arg<States...>& 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<LongName, Set>, "Error: I need a name! [.long_name(), .short_name()]");
static_assert(std ::is_same_v<FromEnv, NotSet>, "Error: don't call auto_env() more than once!");
this->env_name_ = std::move(env_var_name);
Arg<ShortName, LongName, Help, IsRequired, IsFlag, AcceptsMany, DefaultValue, Set, AutoEnv> 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<std::string>)

// ----| 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(); }
};
Loading