Skip to content
14 changes: 12 additions & 2 deletions include/Arg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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,6 +34,7 @@ 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_;
Expand Down Expand Up @@ -80,14 +82,22 @@ class Arg {
this->is_required_ = is_required;
}

// takes_value_
// is_flag_
[[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;
}

// accepts_many_
[[nodiscard]] inline bool get__accepts_many() const {
return this->accepts_many_;
}
inline void set__accepts_many(const bool& accepts_many) {
this->accepts_many_ = accepts_many;
}

// env_name_
[[nodiscard]] inline const std::string& get__env_name() const {
return this->env_name_;
Expand All @@ -107,7 +117,7 @@ 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); });
// to_upper(env_name);
// return env_name;
// }

Expand Down
11 changes: 5 additions & 6 deletions include/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <iostream>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

class ClapParser {
Expand All @@ -30,19 +29,19 @@ class ClapParser {
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);
static void parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector<std::string>& args);
void handle_missing_positional(const Arg& arg);
};
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); });
}
8 changes: 7 additions & 1 deletion src/Arg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 33 additions & 50 deletions src/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
#include "utils.hpp"

#include <cctype>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <string>
#include <algorithm>
#include <vector>

void ClapParser::parse(const int& argc, char* argv[]) {
const std::string& raw_program_name = argv[0];
Expand All @@ -22,8 +24,7 @@ void ClapParser::parse(const int& argc, char* argv[]) {

this->apply_defaults();
this->check_env();
this->parse_options(args); // parse from cli (argc, argv)
// parse_positional_args(args);
this->parse_cli_args(args); // parse from cli (argc, argv)

// Validate all arguments that need values received them
for (const auto& arg : args_) {
Expand All @@ -36,7 +37,7 @@ void ClapParser::parse(const int& argc, char* argv[]) {

void ClapParser::add_arg(const Arg& arg) { args_.emplace_back(arg); }

void ClapParser::parse_options(const std::vector<std::string>& args) {
void ClapParser::parse_cli_args(const std::vector<std::string>& args) {
for (size_t i = 0; i < args.size(); ++i) {
const auto& token = args.at(i);

Expand All @@ -46,24 +47,38 @@ void ClapParser::parse_options(const std::vector<std::string>& args) {
}

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");
}
ClapParser::parse_value_for_non_flag(arg, i, args);
} else {
arg->set__value("1");
}
}
}

void ClapParser::parse_value_for_non_flag(Arg* arg, size_t& cli_index, const std::vector<std::string>& args) {
if (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) {
if (arg->get__accepts_many()) {
std::string value;
while (cli_index + 1 < args.size() && !is_option(args.at(cli_index + 1))) {
value += args.at(cli_index + 1) + ' ';
cli_index++;
}
arg->set__value(value);
} else {
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");
}
}

void ClapParser::check_env() {
for (auto& arg : args_) {
if (arg.get__auto_env()) {
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); });
to_upper(env_name);
auto value_from_env = std::getenv(env_name.c_str());
if (value_from_env) {
arg.set__value(value_from_env);
Expand All @@ -78,45 +93,17 @@ void ClapParser::check_env() {
}
};

// void ClapParser::parse_positional_args(const std::vector<std::string>& args) {
// std::vector<std::string> positional_args;

// // Collect positional arguments (tokens not starting with '-')
// for (const auto& token : args) {
// if (!is_option(token)) {
// positional_args.push_back(token);
// }
// }

// // Assign positional arguments to their respective slots
// auto positional_specs = get_positional_args();
// for (size_t j = 0; j < positional_specs.size(); ++j) {
// if (j < positional_args.size()) {
// values_[positional_specs[j].name()] = positional_specs[j].convert(positional_args[j]);
// } else {
// handle_missing_positional(positional_specs[j]);
// }
// }
// }

void ClapParser::handle_missing_positional(const Arg& arg) {
if (arg.get__is_required()) {
throw std::runtime_error("missing required positional argument: " + arg.get__name());
}
if (arg.has_default()) {
values_[arg.get__name()] = std::string(arg.get__default_value());
}
bool ClapParser::is_option(const std::string& token) {
return token.substr(0, 2) == "--" || (token[0] == '-' && token.size() > 1);
}

inline bool ClapParser::is_option(const std::string& token) const {
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) == "--"; }
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 {
return token[0] == '-' && token.size() > 1 && token[1] != '-';
}
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: " << this->program_name_ << " [OPTIONS]";
Expand Down Expand Up @@ -202,10 +189,6 @@ void ClapParser::print_parser(std::ostream& os, const ClapParser& parser, int in
}
print_indent(os, indent + 1); os << "],\n";

print_indent(os, indent + 1); os << "values: {\n";
for (const auto& [key, val] : parser.values_) {
print_indent(os, indent + 2); os << "\"" << key << "\": \"" << val << "\",\n";
}
print_indent(os, indent + 1); os << "}\n";

print_indent(os, indent); os << "}";
Expand Down
1 change: 1 addition & 0 deletions tests/test_combo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ int main(int argc, char* argv[]) {
auto actual_val = ok_or_throw_str(p.get_one_as<int>("val"), "test argument: 'val' is missing");
auto actual_boolean = ok_or_throw_str(p.get_one_as<bool>("flag"), "test argument: 'flag' is missing");

std::cerr << p << '\n';
assert(actual_val == expected_val);
assert(actual_boolean == expected_boolean);
return 0;
Expand Down
1 change: 0 additions & 1 deletion tests/test_priority.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include "Parser.hpp"
#include "Arg.hpp"
#include "utils.hpp"
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <iostream>
Expand Down