better type safety for action types.
This commit is contained in:
@@ -14,5 +14,6 @@ include_directories(include/parser)
|
||||
include_directories(include/conventions)
|
||||
include_directories(include/conventions/implementations)
|
||||
include_directories(include/parser/platform_headers)
|
||||
include_directories(include/parser/parsing_traits)
|
||||
|
||||
add_executable(test src/main.cpp)
|
||||
8
compile_commands.json
Normal file
8
compile_commands.json
Normal file
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"directory": "/Users/killua/Projects/argument-parser/build",
|
||||
"command": "/usr/bin/clang++ -I/Users/killua/Projects/argument-parser/include -I/Users/killua/Projects/argument-parser/include/parser -I/Users/killua/Projects/argument-parser/include/conventions -I/Users/killua/Projects/argument-parser/include/conventions/implementations -I/Users/killua/Projects/argument-parser/include/parser/platform_headers -I/Users/killua/Projects/argument-parser/include/parser/parsing_traits -g -std=gnu++2b -arch arm64 -o CMakeFiles/test.dir/src/main.cpp.o -c /Users/killua/Projects/argument-parser/src/main.cpp",
|
||||
"file": "/Users/killua/Projects/argument-parser/src/main.cpp",
|
||||
"output": "/Users/killua/Projects/argument-parser/build/CMakeFiles/test.dir/src/main.cpp.o"
|
||||
}
|
||||
]
|
||||
@@ -1,392 +1,209 @@
|
||||
#pragma once
|
||||
#ifndef ARGUMENT_PARSER_HPP
|
||||
#define ARGUMENT_PARSER_HPP
|
||||
|
||||
#include <traits.hpp>
|
||||
#include <base_convention.hpp>
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#ifndef ARGUMENT_PARSER_HPP
|
||||
#define ARGUMENT_PARSER_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace argument_parser {
|
||||
namespace function {
|
||||
template<typename fn>
|
||||
concept no_param = std::is_invocable_r_v<void, fn>;
|
||||
|
||||
template<typename fn, typename param_type>
|
||||
concept with_param = std::is_invocable_r_v<void, fn, param_type const&>;
|
||||
|
||||
template<typename fn>
|
||||
concept string_parameter = with_param<fn, std::string>;
|
||||
template <typename T>
|
||||
class parametered_action {
|
||||
public:
|
||||
// Type alias to expose the parameter type, crucial for the visitor.
|
||||
using parameter_type = T;
|
||||
|
||||
template<typename fn, typename param_type>
|
||||
concept integral_parameter = with_param<fn, param_type> && std::is_integral_v<param_type>;
|
||||
parametered_action(std::function<void(const T&)> const& function) : handler(function) {}
|
||||
|
||||
template<typename T_>
|
||||
using parametered_action = std::function<void(T_ const&)>;
|
||||
|
||||
using non_parametered_action = std::function<void()>;
|
||||
}
|
||||
|
||||
namespace type {
|
||||
enum class parameter_type {
|
||||
NONE,
|
||||
INT,
|
||||
FLOAT,
|
||||
BOOL,
|
||||
STRING
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct param_type_map;
|
||||
|
||||
template<> struct param_type_map<int> { static constexpr parameter_type value = parameter_type::INT; };
|
||||
template<> struct param_type_map<float> { static constexpr parameter_type value = parameter_type::FLOAT; };
|
||||
template<> struct param_type_map<bool> { static constexpr parameter_type value = parameter_type::BOOL; };
|
||||
template<> struct param_type_map<std::string> { static constexpr parameter_type value = parameter_type::STRING; };
|
||||
|
||||
template<typename T>
|
||||
concept supported_parameter_type = requires {
|
||||
{ param_type_map<T>::value } -> std::convertible_to<parameter_type>;
|
||||
};
|
||||
}
|
||||
|
||||
namespace error {
|
||||
enum class type {
|
||||
missing_parameter,
|
||||
key_redefined
|
||||
};
|
||||
|
||||
constexpr type missing_parameter = type::missing_parameter;
|
||||
constexpr type key_redefined = type::key_redefined;
|
||||
}
|
||||
|
||||
template <typename T_> class parametered_action;
|
||||
class non_parametered_action;
|
||||
|
||||
class base_action {};
|
||||
|
||||
template<typename child>
|
||||
class action_impl : public base_action {
|
||||
public:
|
||||
void invoke() {
|
||||
static_cast<child*>(this)->invoke_impl();
|
||||
}
|
||||
|
||||
template<typename T_>
|
||||
void invoke(T_ const& var) {
|
||||
if (!validate_type(parametered_action<T_>{}))
|
||||
throw std::runtime_error("this action does not take any parameter, or given parameters do not match the required parameter type.");
|
||||
static_cast<child*>(this)->invoke_impl(var);
|
||||
}
|
||||
|
||||
std::type_info const& get_type() const {
|
||||
return static_cast<child const*>(this)->get_type_impl();
|
||||
void invoke(const T& arg) const {
|
||||
handler(arg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool validate_type(action_impl<T> const& other) {
|
||||
return get_type() == other.get_type();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T_>
|
||||
class parametered_action : public action_impl<parametered_action<T_>> {
|
||||
public:
|
||||
parametered_action() {}
|
||||
parametered_action(function::parametered_action<T_> const& function) : handler(function) {}
|
||||
template<typename T>
|
||||
static std::type_info const& type() {
|
||||
return typeid(parametered_action<T>);
|
||||
}
|
||||
private:
|
||||
std::function<void(const T&)> handler;
|
||||
};
|
||||
|
||||
std::type_info const& get_type_impl() const {
|
||||
return type<T_>();
|
||||
}
|
||||
private:
|
||||
function::parametered_action<T_> handler;
|
||||
template<typename arg_type>
|
||||
void invoke_impl(arg_type const& arg) {
|
||||
handler(arg);
|
||||
}
|
||||
|
||||
friend class action_impl<parametered_action<T_>>;
|
||||
};
|
||||
|
||||
class non_parametered_action : public action_impl<non_parametered_action> {
|
||||
public:
|
||||
class non_parametered_action {
|
||||
public:
|
||||
non_parametered_action(std::function<void()> const& function) : handler(function) {}
|
||||
static std::type_info const& type() {
|
||||
return typeid(non_parametered_action);
|
||||
}
|
||||
|
||||
std::type_info const& get_type_impl() const {
|
||||
return type();
|
||||
}
|
||||
private:
|
||||
std::function<void()> handler;
|
||||
void invoke_impl() {
|
||||
this->handler();
|
||||
}
|
||||
|
||||
friend class action_impl<non_parametered_action>;
|
||||
};
|
||||
|
||||
class helpers {
|
||||
public:
|
||||
template<typename T_>
|
||||
static std::shared_ptr<parametered_action<T_>> make_parametered_action_ptr(function::parametered_action<T_> const& function) {
|
||||
return std::make_shared<parametered_action<T_>>(parametered_action<T_>(function));
|
||||
}
|
||||
|
||||
template<typename T_>
|
||||
static parametered_action<T_> make_parametered_action(function::parametered_action<T_> const& function) {
|
||||
return parametered_action<T_>(function);
|
||||
void invoke() const {
|
||||
handler();
|
||||
}
|
||||
|
||||
static non_parametered_action make_non_parametered_action(function::non_parametered_action const& function) {
|
||||
return non_parametered_action(function);
|
||||
}
|
||||
|
||||
static std::shared_ptr<non_parametered_action> make_non_parametered_action_ptr(function::non_parametered_action const& function) {
|
||||
return std::make_shared<non_parametered_action>(non_parametered_action(function));
|
||||
}
|
||||
private:
|
||||
std::function<void()> handler;
|
||||
};
|
||||
|
||||
using action_variant = std::variant<
|
||||
non_parametered_action,
|
||||
parametered_action<std::string>,
|
||||
parametered_action<int>,
|
||||
parametered_action<float>,
|
||||
parametered_action<bool>
|
||||
>;
|
||||
|
||||
class base_parser;
|
||||
|
||||
class argument {
|
||||
using parameter_type = type::parameter_type;
|
||||
public:
|
||||
argument() : name(), id(0) {}
|
||||
argument(std::string const& name, int id) : name(name), id(id) {}
|
||||
public:
|
||||
argument() : id(0), name(), required(false), invoked(false), action(non_parametered_action([](){})) {}
|
||||
|
||||
// parametered action group
|
||||
template<type::supported_parameter_type T_>
|
||||
argument(int id, std::string const& name, parametered_action<T_> const& action) : argument(name, id) {
|
||||
type = extract_type<T_>();
|
||||
this->action = std::make_shared<parametered_action<T_>>(action);
|
||||
}
|
||||
template <typename ActionType>
|
||||
argument(int id, std::string const& name, ActionType const& action)
|
||||
: id(id), name(name), action(action), required(false), invoked(false) {}
|
||||
|
||||
template<type::supported_parameter_type T_>
|
||||
argument(int id, std::string const& name, std::shared_ptr<parametered_action<T_>> const& action) : argument(name, id) {
|
||||
type = extract_type<T_>();
|
||||
this->action = action;
|
||||
}
|
||||
|
||||
template<type::supported_parameter_type T_>
|
||||
argument(std::string const& name, parametered_action<T_> const& action) : argument(name, 0) {
|
||||
type = extract_type<T_>();
|
||||
this->action = std::make_shared<parametered_action<T_>>(action);
|
||||
}
|
||||
|
||||
template<type::supported_parameter_type T_>
|
||||
argument(std::string const& name, std::shared_ptr<parametered_action<T_>> const& action) : argument(name, 0) {
|
||||
type = extract_type<T_>();
|
||||
this->action = action;
|
||||
}
|
||||
|
||||
// non parametered action group
|
||||
argument(int id, std::string const& name, non_parametered_action const& action) : argument(name, id) {
|
||||
type = parameter_type::NONE;
|
||||
this->action = std::make_shared<non_parametered_action>(action);
|
||||
}
|
||||
|
||||
argument(int id, std::string const& name, std::shared_ptr<non_parametered_action> const& action) : argument(name, id) {
|
||||
type = parameter_type::NONE;
|
||||
this->action = action;
|
||||
}
|
||||
|
||||
argument(std::string const& name, non_parametered_action const& action) : argument(name, 0) {
|
||||
type = parameter_type::NONE;
|
||||
this->action = std::make_shared<non_parametered_action>(action);
|
||||
}
|
||||
|
||||
argument(std::string const& name, std::shared_ptr<non_parametered_action> const& action) : argument(name, 0) {
|
||||
type = parameter_type::NONE;
|
||||
this->action = action;
|
||||
}
|
||||
|
||||
argument(argument const& other) :
|
||||
id(other.id),
|
||||
name(other.name),
|
||||
action(other.action),
|
||||
required(other.required),
|
||||
type(other.type),
|
||||
help_text(other.help_text),
|
||||
invoked(other.invoked)
|
||||
{}
|
||||
argument& operator=(argument const& other) {
|
||||
if (this == &other) return *this;
|
||||
this->~argument();
|
||||
new(this) argument{other};
|
||||
return *this;
|
||||
}
|
||||
|
||||
void toggle_required() {
|
||||
required = !required;
|
||||
}
|
||||
|
||||
|
||||
bool is_required() const {
|
||||
return required;
|
||||
}
|
||||
|
||||
std::string get_name() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
parameter_type expected_type() const {
|
||||
return type;
|
||||
}
|
||||
bool is_required() const { return required; }
|
||||
std::string get_name() const { return name; }
|
||||
bool is_invoked() const { return invoked; }
|
||||
|
||||
bool expects_parameter() const {
|
||||
return type != parameter_type::NONE;
|
||||
return !std::holds_alternative<non_parametered_action>(action);
|
||||
}
|
||||
|
||||
bool is_invoked() const {
|
||||
return invoked;
|
||||
}
|
||||
private:
|
||||
void set_required(bool val) { required = val; }
|
||||
void set_invoked(bool val) { invoked = val; }
|
||||
void set_help_text(std::string const& text) { help_text = text; }
|
||||
|
||||
template<typename T_>
|
||||
void invoke(T_ const& argument) {
|
||||
if (type == parameter_type::NONE) throw std::runtime_error("this argument does not expect any parameter.");
|
||||
invoked = true;
|
||||
static_cast<action_impl<parametered_action<T_>>*>(action.get())->invoke(argument);
|
||||
}
|
||||
friend class base_parser;
|
||||
|
||||
void invoke() {
|
||||
if (type != parameter_type::NONE) throw std::runtime_error("this argument expects parameter.");
|
||||
invoked = true;
|
||||
static_cast<action_impl<non_parametered_action>*>(action.get())->invoke();
|
||||
}
|
||||
int id;
|
||||
std::string name;
|
||||
action_variant action;
|
||||
bool required;
|
||||
bool invoked;
|
||||
std::string help_text;
|
||||
};
|
||||
|
||||
private:
|
||||
void set_required(bool val) {
|
||||
required = val;
|
||||
}
|
||||
|
||||
void set_help_text(std::string const& help_text) {
|
||||
this->help_text = help_text;
|
||||
}
|
||||
argument(type::parameter_type const& parameter_type) : id(0), name(), type(parameter_type) {}
|
||||
friend class base_parser;
|
||||
template<type::supported_parameter_type T_>
|
||||
constexpr type::parameter_type extract_type() {
|
||||
if constexpr (std::is_same_v<T_, int>)
|
||||
return type::parameter_type::INT;
|
||||
else if constexpr (std::is_same_v<T_, float> || std::is_convertible_v<T_, float>)
|
||||
return type::parameter_type::FLOAT;
|
||||
else if constexpr (std::is_same_v<T_, bool>)
|
||||
return type::parameter_type::BOOL;
|
||||
else if constexpr (std::is_same_v<T_, std::string> || std::is_convertible_v<T_, std::string>)
|
||||
return type::parameter_type::STRING;
|
||||
}
|
||||
|
||||
|
||||
int const id;
|
||||
std::string const name;
|
||||
std::shared_ptr<base_action> action;
|
||||
bool required = false;
|
||||
parameter_type type;
|
||||
bool invoked = false;
|
||||
std::string help_text;
|
||||
};
|
||||
|
||||
class base_parser {
|
||||
public:
|
||||
template<typename T_>
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action<T_> const& action) {
|
||||
base_add_argument(short_arg, long_arg, long_arg, action, false);
|
||||
}
|
||||
|
||||
template<typename T_>
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action<T_> const& action, bool required) {
|
||||
base_add_argument(short_arg, long_arg, long_arg, action, required);
|
||||
}
|
||||
|
||||
template<typename T_>
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action<T_> const& action, bool required) {
|
||||
base_add_argument(short_arg, long_arg, help_text, action, required);
|
||||
}
|
||||
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action) {
|
||||
base_add_argument(short_arg, long_arg, long_arg, action, false);
|
||||
}
|
||||
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action, bool required) {
|
||||
base_add_argument(short_arg, long_arg, long_arg, action, required);
|
||||
class base_parser {
|
||||
public:
|
||||
template <typename T>
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action<T> const& action, bool required) {
|
||||
base_add_argument(short_arg, long_arg, help_text, action, required);
|
||||
}
|
||||
|
||||
void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, non_parametered_action const& action, bool required) {
|
||||
base_add_argument(short_arg, long_arg, help_text, action, required);
|
||||
base_add_argument(short_arg, long_arg, help_text, action, required);
|
||||
}
|
||||
|
||||
std::string build_help_text(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
std::stringstream ss;
|
||||
ss << "Usage: " << program_name << " [OPTIONS]...\n";
|
||||
|
||||
for (auto const& [id, arg] : argument_map) {
|
||||
auto short_arg = reverse_short_arguments[id];
|
||||
auto long_arg = reverse_long_arguments[id];
|
||||
ss << "\t";
|
||||
ss << "-" << short_arg << ", --" << long_arg;
|
||||
ss << "\t\t" << arg.help_text << "\n";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
argument& get_argument(conventions::parsed_argument const& arg) {
|
||||
if (arg.first == conventions::argument_type::LONG) {
|
||||
auto long_pos = long_arguments.find(arg.second);
|
||||
if (long_pos != long_arguments.end()) return argument_map.at(long_pos->second);
|
||||
} else if (arg.first == conventions::argument_type::SHORT) {
|
||||
auto short_pos = short_arguments.find(arg.second);
|
||||
if (short_pos != short_arguments.end()) return argument_map.at(short_pos->second);
|
||||
}
|
||||
throw std::runtime_error("Unknown argument: " + arg.second);
|
||||
}
|
||||
|
||||
void handle_arguments(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) {
|
||||
std::stringstream ss;
|
||||
bool arg_correctly_handled = false;
|
||||
for(auto const& convention_type : convention_types) {
|
||||
auto extracted = convention_type->get_argument(*it);
|
||||
std::stringstream error_stream;
|
||||
bool arg_correctly_handled = false;
|
||||
|
||||
for (auto const& convention_type : convention_types) {
|
||||
auto extracted = convention_type->get_argument(*it);
|
||||
if (extracted.first == conventions::argument_type::ERROR) {
|
||||
ss << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n";
|
||||
error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
argument& coresponding_argument = get_argument(extracted);
|
||||
if (coresponding_argument.expects_parameter()) {
|
||||
auto type = coresponding_argument.expected_type();
|
||||
if (convention_type->requires_next_token() and (it + 1) == parsed_arguments.end())
|
||||
throw std::runtime_error("expected value for argument " + extracted.second + " argument");
|
||||
auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it);
|
||||
if (type == type::parameter_type::BOOL) {
|
||||
auto value = parse_bool(value_raw);
|
||||
coresponding_argument.invoke<bool>(value);
|
||||
arg_correctly_handled = true;
|
||||
break;
|
||||
argument& corresponding_argument = get_argument(extracted);
|
||||
|
||||
std::visit([&](auto&& action) {
|
||||
using ActionType = std::decay_t<decltype(action)>;
|
||||
|
||||
if constexpr (std::is_same_v<ActionType, non_parametered_action>) {
|
||||
action.invoke();
|
||||
} else {
|
||||
if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) {
|
||||
throw std::runtime_error("expected value for argument " + extracted.second);
|
||||
}
|
||||
auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it);
|
||||
|
||||
using ParamType = typename ActionType::parameter_type;
|
||||
|
||||
auto value = parsing_traits::parser_trait<ParamType>::parse(value_raw);
|
||||
|
||||
action.invoke(value);
|
||||
}
|
||||
else if (type == type::parameter_type::INT) {
|
||||
auto value = parse_int(value_raw);
|
||||
coresponding_argument.invoke<int>(value);
|
||||
arg_correctly_handled = true;
|
||||
break;
|
||||
}
|
||||
else if (type == type::parameter_type::FLOAT) {
|
||||
auto value = parse_float(value_raw);
|
||||
coresponding_argument.invoke<float>(value);
|
||||
arg_correctly_handled = true;
|
||||
break;
|
||||
}
|
||||
else if (type == type::parameter_type::STRING) {
|
||||
coresponding_argument.invoke<std::string>(value_raw);
|
||||
arg_correctly_handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
coresponding_argument.invoke();
|
||||
arg_correctly_handled = true;
|
||||
break;
|
||||
}
|
||||
} catch(std::runtime_error e) {
|
||||
ss << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n";
|
||||
continue;
|
||||
}, corresponding_argument.action);
|
||||
|
||||
corresponding_argument.set_invoked(true);
|
||||
arg_correctly_handled = true;
|
||||
break; // Convention succeeded, move to the next argument token
|
||||
|
||||
} catch (const std::runtime_error& e) {
|
||||
error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!arg_correctly_handled) {
|
||||
throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + ss.str());
|
||||
throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + error_stream.str());
|
||||
}
|
||||
}
|
||||
check_for_required_arguments(convention_types);
|
||||
}
|
||||
|
||||
void display_help(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
std::cout << build_help_text(convention_types);
|
||||
}
|
||||
|
||||
protected:
|
||||
base_parser() = default;
|
||||
|
||||
std::string program_name;
|
||||
std::vector<std::string> parsed_arguments;
|
||||
|
||||
private:
|
||||
template <typename ActionType>
|
||||
void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, ActionType const& action, bool required) {
|
||||
if (short_arguments.count(short_arg) || long_arguments.count(long_arg)) {
|
||||
throw std::runtime_error("The key already exists!");
|
||||
}
|
||||
|
||||
int id = id_counter.fetch_add(1);
|
||||
|
||||
argument arg(id, short_arg + "|" + long_arg, action);
|
||||
arg.set_required(required);
|
||||
arg.set_help_text(help_text);
|
||||
|
||||
argument_map[id] = arg;
|
||||
short_arguments[short_arg] = id;
|
||||
reverse_short_arguments[id] = short_arg;
|
||||
long_arguments[long_arg] = id;
|
||||
reverse_long_arguments[id] = long_arg;
|
||||
}
|
||||
|
||||
void check_for_required_arguments(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
std::vector<std::pair<std::string, std::string>> required_args;
|
||||
for (auto const& [_, arg] : argument_map) {
|
||||
if (arg.is_required() and not arg.is_invoked()) {
|
||||
@@ -407,104 +224,30 @@ namespace argument_parser {
|
||||
}
|
||||
}
|
||||
|
||||
void display_help(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
std::cout << build_help_text(convention_types);
|
||||
}
|
||||
inline static std::atomic_int id_counter = 0;
|
||||
|
||||
std::unordered_map<int, argument> argument_map;
|
||||
std::unordered_map<std::string, int> short_arguments;
|
||||
std::unordered_map<int, std::string> reverse_short_arguments;
|
||||
std::unordered_map<std::string, int> long_arguments;
|
||||
std::unordered_map<int, std::string> reverse_long_arguments;
|
||||
|
||||
std::string build_help_text(std::initializer_list<conventions::convention const* const> convention_types) {
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "Usage: " << program_name
|
||||
<< " [OPTIONS]... [ARGS]...\n";
|
||||
|
||||
|
||||
for (auto const& [id, arg] : argument_map) {
|
||||
auto short_arg = reverse_short_arguments[id];
|
||||
auto long_arg = reverse_long_arguments[id];
|
||||
ss << "\t";
|
||||
for (auto const& convention : convention_types) {
|
||||
ss << convention->short_prec() << short_arg << ", ";
|
||||
}
|
||||
bool first = true;
|
||||
for (auto const& convention : convention_types) {
|
||||
ss << (first ? "" : ", ") << convention->long_prec() << long_arg;
|
||||
first = false;
|
||||
}
|
||||
|
||||
ss << "\t\t" << arg.help_text << "\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
bool parse_bool(std::string const& val) {
|
||||
if (val == "t" || val == "true" || val == "1") return true;
|
||||
if (val == "f" || val == "false" || val == "0") return false;
|
||||
throw std::runtime_error("expected boolean parsable, got:" + val);
|
||||
}
|
||||
|
||||
int parse_int(std::string const& val) {
|
||||
return std::stoi(val);
|
||||
}
|
||||
|
||||
int parse_float(std::string const& val) {
|
||||
return std::stof(val);
|
||||
}
|
||||
|
||||
base_parser() : argument_map(), short_arguments(), long_arguments(), reverse_long_arguments(), reverse_short_arguments(), parsed_arguments(), program_name() {}
|
||||
|
||||
template<typename T_>
|
||||
void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, T_ const& action, bool required) {
|
||||
if (short_arguments.find(short_arg) != short_arguments.end() || long_arguments.find(long_arg) != long_arguments.end()) {
|
||||
throw std::runtime_error("The key already exists!");
|
||||
}
|
||||
|
||||
int id = __id_ref.fetch_add(1);
|
||||
|
||||
auto arg = argument(id, short_arg + "|" + long_arg, action);;
|
||||
arg.set_required(required);
|
||||
arg.set_help_text(help_text);
|
||||
|
||||
argument_map[id] = arg;
|
||||
short_arguments[short_arg] = id;
|
||||
reverse_short_arguments[id] = short_arg;
|
||||
long_arguments[long_arg] = id;
|
||||
reverse_long_arguments[id] = long_arg;
|
||||
}
|
||||
|
||||
public:
|
||||
argument& get_argument(conventions::parsed_argument const& arg) {
|
||||
if (arg.first == conventions::argument_type::LONG) {
|
||||
auto long_pos = long_arguments.find(arg.second);
|
||||
if (long_pos != long_arguments.end()) return argument_map[long_pos->second];
|
||||
} else if (arg.first == conventions::argument_type::SHORT) {
|
||||
auto short_pos = short_arguments.find(arg.second);
|
||||
if (short_pos != short_arguments.end()) return argument_map[short_pos->second];
|
||||
} else if (arg.first == conventions::argument_type::ERROR) {
|
||||
throw std::runtime_error{arg.second};
|
||||
}
|
||||
throw std::runtime_error("Unknown argument: " + arg.second);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string program_name;
|
||||
|
||||
inline static std::atomic_int __id_ref = std::atomic_int(0);
|
||||
|
||||
std::unordered_map<int, argument> argument_map;
|
||||
std::unordered_map<std::string, int> short_arguments;
|
||||
std::unordered_map<int, std::string> reverse_short_arguments;
|
||||
std::unordered_map<std::string, int> long_arguments;
|
||||
std::unordered_map<int, std::string> reverse_long_arguments;
|
||||
|
||||
std::vector<std::string> parsed_arguments;
|
||||
|
||||
friend class linux_parser;
|
||||
friend class windows_parser;
|
||||
friend class macos_parser;
|
||||
friend class fake_parser;
|
||||
friend class linux_parser;
|
||||
friend class windows_parser;
|
||||
friend class macos_parser;
|
||||
friend class fake_parser;
|
||||
};
|
||||
|
||||
namespace helpers {
|
||||
template<typename T>
|
||||
static parametered_action<T> make_parametered_action(std::function<void(const T&)> const& function) {
|
||||
return parametered_action<T>(function);
|
||||
}
|
||||
|
||||
static non_parametered_action make_non_parametered_action(std::function<void()> const& function) {
|
||||
return non_parametered_action(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ARGUMENT_PARSER_HPP
|
||||
#endif // ARGUMENT_PARSER_HPP
|
||||
52
include/parser/parsing_traits/traits.hpp
Normal file
52
include/parser/parsing_traits/traits.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#ifndef PARSING_TRAITS_HPP
|
||||
#define PARSING_TRAITS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace argument_parser::parsing_traits {
|
||||
template <typename T_>
|
||||
struct parser_trait {
|
||||
using type = T_;
|
||||
static T_ parse(const std::string& input);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct parser_trait<std::string> {
|
||||
static std::string parse(const std::string& input) {
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct parser_trait<bool> {
|
||||
static bool parse(const std::string& input) {
|
||||
if (input == "t" || input == "true" || input == "1") return true;
|
||||
if (input == "f" || input == "false" || input == "0") return false;
|
||||
throw std::runtime_error("Invalid boolean value: " + input);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct parser_trait<int> {
|
||||
static int parse(const std::string& input) {
|
||||
return std::stoi(input);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct parser_trait<float> {
|
||||
static float parse(const std::string& input) {
|
||||
return std::stof(input);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct parser_trait<double> {
|
||||
static double parse(const std::string& input) {
|
||||
return std::stod(input);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
25
src/main.cpp
25
src/main.cpp
@@ -10,6 +10,23 @@
|
||||
|
||||
using namespace argument_parser::conventions;
|
||||
|
||||
struct Point {
|
||||
int x, y;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct argument_parser::parsing_traits::parser_trait<Point> {
|
||||
static Point parse(const std::string& input) {
|
||||
auto comma_pos = input.find(',');
|
||||
if (comma_pos == std::string::npos) {
|
||||
throw std::runtime_error("Invalid Point format. Expected 'x,y'.");
|
||||
}
|
||||
int x = std::stoi(input.substr(0, comma_pos));
|
||||
int y = std::stoi(input.substr(comma_pos + 1));
|
||||
return {x, y};
|
||||
}
|
||||
};
|
||||
|
||||
const std::initializer_list<argument_parser::conventions::convention const* const> conventions = {
|
||||
&gnu_argument_convention,
|
||||
&gnu_equal_argument_convention
|
||||
@@ -19,6 +36,11 @@ const auto echo = argument_parser::helpers::make_parametered_action<std::string>
|
||||
std::cout << text << std::endl;
|
||||
});
|
||||
|
||||
|
||||
const auto echo_point = argument_parser::helpers::make_parametered_action<Point>([](Point const& point) {
|
||||
std::cout << "Point(" << point.x << ", " << point.y << ")" << std::endl;
|
||||
});
|
||||
|
||||
const auto cat = argument_parser::helpers::make_parametered_action<std::string>([](std::string const& file_name) {
|
||||
std::ifstream file(file_name);
|
||||
if (!file.is_open()) {
|
||||
@@ -86,10 +108,11 @@ auto make_grep_action(argument_parser::base_parser& parser) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::vector<std::string> fake_args = { "-g", "add", "-f", "src/main.cpp" };
|
||||
std::vector<std::string> fake_args = { "-g", "add", "-f", "src/main.cpp", "-ep", "1,2" };
|
||||
auto parser = argument_parser::fake_parser{"test", std::move(fake_args)};
|
||||
auto [file, grep] = make_grep_action(parser);
|
||||
parser.add_argument("e", "echo", "echoes given variable", echo, false);
|
||||
parser.add_argument("ep", "echo-point", "echoes given point", echo_point, false);
|
||||
parser.add_argument("f", "file", "File to grep, required only if using grep", file, false);
|
||||
parser.add_argument("g", "grep", "Grep pattern, required only if using grep", grep, false);
|
||||
parser.add_argument("c", "cat", "Prints the content of the file", cat, false);
|
||||
|
||||
Reference in New Issue
Block a user