Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f29799d
feat(utils): add and use 'to_upper' util
jarjk Apr 16, 2025
fcffbc6
fix(Parser): removed unneeded value array
csboo May 13, 2025
b830bc0
fix(tests): test_priority: remove unused import
csboo May 13, 2025
1859a56
fix(tests): test_combo: debug print parser for logs
csboo May 13, 2025
9d3efa2
misc(Parser): rename parse_options() -> parse_cli_args()
csboo May 13, 2025
a30b12d
feat(Arg): add a functionality to accept many values
csboo May 14, 2025
9cb5427
fix(Arg): print 'accepts_many' as well
csboo May 14, 2025
176c0f7
misc(Arg): correct a comment, remove a space
csboo May 14, 2025
1b40040
fix(Parser): corrected some helper func def and decl
csboo May 14, 2025
f69c213
fix(Parser): remove old/unused logic
csboo May 14, 2025
77d2741
feat(Parser): WIP added ability to parse positionals from cli
csboo May 14, 2025
52f1f06
feat(Parser): impl Parse for: int, std::string, bool
csboo May 14, 2025
1bc25c8
feat(Parser): added a 'Parseable' c++ concept
csboo May 14, 2025
950d657
feat(Parser): parser uses way better parsing method!
csboo May 14, 2025
0da288c
feat(Parser): impl Parse for (most) numeric types
csboo May 14, 2025
8545e16
fix(Parser): include 'charconv', remove 'cmath'
csboo May 14, 2025
fee7117
fix(Parser): more explicit number casting for MacOS compatibility
csboo May 14, 2025
807243d
feat(Parser): add macros for code generation
csboo May 14, 2025
5a211ac
feat(Parser): move 'Parsable' and implementations --> Parsables.hpp
csboo May 14, 2025
4626943
feat(Arg): use macro for getters and setters
csboo May 14, 2025
a8f2458
fix(Arg): remove unused code
csboo May 14, 2025
851903f
fix(Parser): cleanup old/unused code
csboo May 14, 2025
7fac396
fix: remove all unused imports everywhere
csboo May 14, 2025
80beb8c
fix(Parsables): remove redefinition of i64 and u64
csboo May 15, 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
120 changes: 19 additions & 101 deletions include/Arg.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#pragma once

#include "utils.hpp"
#include "Macros.hpp"

#include <cerrno>
#include <ostream>
#include <iostream>
#include <optional>
Expand All @@ -17,6 +16,7 @@ class Arg {
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();
Expand All @@ -33,114 +33,32 @@ class Arg {
std::string help_;
bool is_required_;
bool is_flag_;
bool accepts_many_;
std::string env_name_;
bool auto_env_;
// std::string auto_env_name_;
std::string default_value_;
std::optional<std::string> value_;

// ----| 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__is_flag() const {
return this->is_flag_;
}
inline void set__is_flag(const bool& takes_value) {
this->is_flag_ = 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();
// 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<std::string> get__value() const {
return this->value_;
}
inline void set__value(const std::string& value) {
this->value_ = value;
}

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<std::string>)

// ----| Checkers |----
// has_env_
[[nodiscard]] inline bool has_env() const {
return !this->env_name_.empty();
}

[[nodiscard]] inline bool has_env() const { return !this->env_name_.empty(); }

// has_default_
[[nodiscard]] inline bool has_default() const {
return !this->default_value_.empty();
}
[[nodiscard]] inline bool has_default() const { return !this->default_value_.empty(); }

// has_value_
[[nodiscard]] inline bool has_value() const {
return this->value_.has_value();
}

[[nodiscard]] inline bool has_value() const { return this->value_.has_value(); }
};
27 changes: 27 additions & 0 deletions include/Macros.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#define DEFINE_PARSABLE_BASIC_TYPE(TYPE) \
template<> \
struct Parse<TYPE> { \
static std::optional<TYPE> parse(std::string_view s) { \
TYPE value; \
auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value); \
if (ec == std::errc()) return value; \
return 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_GETTER_SETTER(NAME, TYPE) \
[[nodiscard]] inline const TYPE& get__##NAME() const { return this->NAME##_; } \
inline void set__##NAME(const TYPE& NAME) { this->NAME##_ = NAME; }
50 changes: 50 additions & 0 deletions include/Parsables.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include "Macros.hpp"

#include <charconv>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>

template<typename T>
struct Parse {
static_assert(sizeof(T) == 0, "No Parse<T> specialization defined for this type");
};

template<typename T>
concept Parseable = requires(std::string_view s) {
{ Parse<T>::parse(s) } -> std::convertible_to<std::optional<T>>;
};

// 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)

// 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<std::string> {
static std::optional<std::string> parse(std::string_view s) {
return std::string(s.data());
}
};

template<>
struct Parse<bool> {
static std::optional<bool> parse(std::string_view s) {
auto as_int = Parse<int>::parse(s).value();
return as_int;
}
};
27 changes: 12 additions & 15 deletions include/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

#include "Arg.hpp"
#include "utils.hpp"
#include "Parsables.hpp"

#include <optional>
#include <cstdlib>
#include <iostream>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

class ClapParser {
Expand All @@ -15,34 +17,29 @@ class ClapParser {
void parse(const int& argc, char* argv[]);
void print_help() const;

template <typename T> inline std::optional<T> get_one_as(const std::string& name) {
template <typename T>
requires Parseable<T>
inline std::optional<T> 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;
return Parse<T>::parse(arg->get__value().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::unordered_map<std::string, std::string> values_;
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 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);
std::vector<Arg> get_positional_args() const;
void apply_defaults();

void parse_options(const std::vector<std::string>& args);
void parse_cli_args(const std::vector<std::string>& args);
void check_env();
void parse_positional_args(const std::vector<std::string>& args);
void handle_missing_positional(const Arg& arg);
static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector<std::string>& args);
};
7 changes: 6 additions & 1 deletion include/utils.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#pragma once

#include <algorithm>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <sstream>

template <typename T, typename E>
inline T ok_or(std::optional<T> opt, E&& err) {
Expand Down Expand Up @@ -44,3 +45,7 @@ inline void print_indent(std::ostream& os, int indent_level) {
for (int i = 0; i < indent_level; ++i)
os << '\t';
}

inline void to_upper(std::string &s) {
std::ranges::transform(s, s.begin(), [](const unsigned char& c) { return std::toupper(c); });
}
10 changes: 8 additions & 2 deletions src/Arg.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "Arg.hpp"
#include "utils.hpp"

#include <cstdlib>
#include <optional>
#include <pthread.h>
#include <string>
Expand All @@ -12,6 +12,7 @@ Arg::Arg(const std::string& name) :
help_(""),
is_required_(false),
is_flag_(false),
accepts_many_(false),
env_name_(""),
auto_env_(false),
default_value_(""),
Expand All @@ -31,11 +32,15 @@ Arg& Arg::required(bool is_required) {
is_required_ = is_required;
return *this;
}
Arg& Arg::is_flag() {
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;
Expand Down Expand Up @@ -69,6 +74,7 @@ void Arg::print_arg(std::ostream& os, const Arg& arg, int indent) {
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_)
Expand Down
Loading