commit 5ff911f351d07c38189a65e32185e54be2ae6264 Author: killua Date: Tue Jun 24 18:35:50 2025 +0400 first diff --git a/.cache/clangd/index/main.cpp.A8BD3C045BDF1C93.idx b/.cache/clangd/index/main.cpp.A8BD3C045BDF1C93.idx new file mode 100644 index 0000000..2bd2a7b Binary files /dev/null and b/.cache/clangd/index/main.cpp.A8BD3C045BDF1C93.idx differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5b3503 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin +build +.vscode +.cache \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5a54468 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "configurations": [ + { + "name": "Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/bin/test", + "args": ["-re", "hello, world"], + "cwd": "${workspaceFolder}", + "stopOnEntry": false + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9cc53b5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.15) + +project(argument_parser) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_Release ${CMAKE_CURRENT_SOURCE_DIR}/bin/release) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_Debug ${CMAKE_CURRENT_SOURCE_DIR}/bin/debug) + +add_executable(test src/main.cpp) \ No newline at end of file diff --git a/include/argument_parser.hpp b/include/argument_parser.hpp new file mode 100644 index 0000000..73f2378 --- /dev/null +++ b/include/argument_parser.hpp @@ -0,0 +1,506 @@ +#pragma once +#include "base_convention.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef ARGUMENT_PARSER_HPP +#define ARGUMENT_PARSER_HPP + +#include +#include + +namespace argument_parser { + namespace function { + template + concept no_param = std::is_invocable_r_v; + + template + concept with_param = std::is_invocable_r_v; + + template + concept string_parameter = with_param; + + template + concept integral_parameter = with_param && std::is_integral_v; + + template + using parametered_action = std::function; + + using non_parametered_action = std::function; + } + + namespace type { + enum class parameter_type { + NONE, + INT, + FLOAT, + BOOL, + STRING + }; + + template + struct param_type_map; + + template<> struct param_type_map { static constexpr parameter_type value = parameter_type::INT; }; + template<> struct param_type_map { static constexpr parameter_type value = parameter_type::FLOAT; }; + template<> struct param_type_map { static constexpr parameter_type value = parameter_type::BOOL; }; + template<> struct param_type_map { static constexpr parameter_type value = parameter_type::STRING; }; + + template + concept supported_parameter_type = requires { + { param_type_map::value } -> std::convertible_to; + }; + } + + namespace error { + enum class type { + missing_parameter, + key_redefined + }; + + constexpr type missing_parameter = type::missing_parameter; + constexpr type key_redefined = type::key_redefined; + } + + template class parametered_action; + class non_parametered_action; + + class base_action {}; + + template + class action_impl : public base_action { + public: + void invoke() { + static_cast(this)->invoke_impl(); + } + + template + void invoke(T_ const& var) { + if (!validate_type(parametered_action{})) + throw std::runtime_error("this action does not take any parameter, or given parameters do not match the required parameter type."); + static_cast(this)->invoke_impl(var); + } + + std::type_info const& get_type() const { + return static_cast(this)->get_type_impl(); + } + + template + bool validate_type(action_impl const& other) { + return get_type() == other.get_type(); + } + }; + + template + class parametered_action : public action_impl> { + public: + parametered_action() {} + parametered_action(function::parametered_action const& function) : handler(function) {} + template + static std::type_info const& type() { + return typeid(parametered_action); + } + + std::type_info const& get_type_impl() const { + return type(); + } + private: + function::parametered_action handler; + template + void invoke_impl(arg_type const& arg) { + handler(arg); + } + + friend class action_impl>; + }; + + class non_parametered_action : public action_impl { + public: + non_parametered_action(std::function 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 handler; + void invoke_impl() { + this->handler(); + } + + friend class action_impl; + }; + + class helpers { + public: + template + static std::shared_ptr> make_parametered_action_ptr(function::parametered_action const& function) { + return std::make_shared>(parametered_action(function)); + } + + template + static parametered_action make_parametered_action(function::parametered_action const& function) { + return parametered_action(function); + } + + static non_parametered_action make_non_parametered_action(function::non_parametered_action const& function) { + return non_parametered_action(function); + } + + static std::shared_ptr make_non_parametered_action_ptr(function::non_parametered_action const& function) { + return std::make_shared(non_parametered_action(function)); + } + }; + + class argument { + using parameter_type = type::parameter_type; + public: + argument() : name(), id(0) {} + argument(std::string const& name, int id) : name(name), id(id) {} + + // parametered action group + template + argument(int id, std::string const& name, parametered_action const& action) : argument(name, id) { + type = extract_type(); + this->action = std::make_shared>(action); + } + + template + argument(int id, std::string const& name, std::shared_ptr> const& action) : argument(name, id) { + type = extract_type(); + this->action = action; + } + + template + argument(std::string const& name, parametered_action const& action) : argument(name, 0) { + type = extract_type(); + this->action = std::make_shared>(action); + } + + template + argument(std::string const& name, std::shared_ptr> const& action) : argument(name, 0) { + type = extract_type(); + 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(action); + } + + argument(int id, std::string const& name, std::shared_ptr 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(action); + } + + argument(std::string const& name, std::shared_ptr 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) { + 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 expects_parameter() const { + return type != parameter_type::NONE; + } + + bool is_invoked() const { + return invoked; + } + + template + 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.get())->invoke(argument); + } + + void invoke() { + if (type != parameter_type::NONE) throw std::runtime_error("this argument expects parameter."); + invoked = true; + static_cast*>(action.get())->invoke(); + } + + 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 + constexpr type::parameter_type extract_type() { + if constexpr (std::is_same_v) + return type::parameter_type::INT; + else if constexpr (std::is_same_v || std::is_convertible_v) + return type::parameter_type::FLOAT; + else if constexpr (std::is_same_v) + return type::parameter_type::BOOL; + else if constexpr (std::is_same_v || std::is_convertible_v) + return type::parameter_type::STRING; + } + + + int const id; + std::string const name; + std::shared_ptr action; + bool required = false; + parameter_type type; + bool invoked = false; + std::string help_text; + }; + + class base_parser { + public: + template + void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action) { + base_add_argument(short_arg, long_arg, long_arg, action, false); + } + + template + void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action, bool required) { + base_add_argument(short_arg, long_arg, long_arg, action, required); + } + + template + void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action 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); + } + + 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); + } + + void handle_arguments(std::initializer_list 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); + if (extracted.first == conventions::argument_type::ERROR) { + ss << "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(value); + arg_correctly_handled = true; + break; + } + else if (type == type::parameter_type::INT) { + auto value = parse_int(value_raw); + coresponding_argument.invoke(value); + arg_correctly_handled = true; + break; + } + else if (type == type::parameter_type::FLOAT) { + auto value = parse_float(value_raw); + coresponding_argument.invoke(value); + arg_correctly_handled = true; + break; + } + else if (type == type::parameter_type::STRING) { + coresponding_argument.invoke(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; + } + } + if (!arg_correctly_handled) { + throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + ss.str()); + } + } + + std::vector> required_args; + for (auto const& [_, arg] : argument_map) { + if (arg.is_required() and not arg.is_invoked()) { + required_args.emplace_back>({ + reverse_short_arguments[arg.id], + reverse_long_arguments[arg.id] + }); + } + } + + if (not required_args.empty()) { + std::cerr << "These arguments were expected but not provided: "; + for (auto const& [s, l] : required_args) { + std::cerr << "[-" << s << ", --" << l << "] "; + } + std::cerr << "\n"; + display_help(convention_types); + } + } + + void display_help(std::initializer_list convention_types) { + std::cout << build_help_text(convention_types); + } + + std::string build_help_text(std::initializer_list 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 + 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 argument_map; + std::unordered_map short_arguments; + std::unordered_map reverse_short_arguments; + std::unordered_map long_arguments; + std::unordered_map reverse_long_arguments; + + std::vector parsed_arguments; + + friend class linux_parser; + friend class windows_parser; + }; +} + +#endif // ARGUMENT_PARSER_HPP \ No newline at end of file diff --git a/include/base_convention.hpp b/include/base_convention.hpp new file mode 100644 index 0000000..67643ef --- /dev/null +++ b/include/base_convention.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +#ifndef BASE_CONVENTION_HPP +#define BASE_CONVENTION_HPP + +namespace argument_parser::conventions { + enum class argument_type { + SHORT, + LONG, + ERROR + }; + + using parsed_argument = std::pair; + + class base_convention { + public: + virtual std::string extract_value(std::string const&) const = 0; + virtual parsed_argument get_argument(std::string const&) const = 0; + virtual bool requires_next_token() const = 0; + virtual std::string name() const = 0; + virtual std::string short_prec() const = 0; + virtual std::string long_prec() const = 0; + protected: + base_convention() = default; + ~base_convention() = default; + }; + using convention = base_convention; +} + +#endif \ No newline at end of file diff --git a/include/gnu_argument_convention.hpp b/include/gnu_argument_convention.hpp new file mode 100644 index 0000000..50064ba --- /dev/null +++ b/include/gnu_argument_convention.hpp @@ -0,0 +1,98 @@ +#pragma once +#include "base_convention.hpp" +#include + +#ifndef GNU_ARGUMENT_CONVENTION_HPP +#define GNU_ARGUMENT_CONVENTION_HPP + +namespace argument_parser::conventions::implementations { + + class gnu_argument_convention : public base_convention { + public: + parsed_argument get_argument(std::string const& raw) const override { + if (raw.starts_with(long_prec())) + return {argument_type::LONG, raw.substr(2)}; + else if (raw.starts_with(short_prec())) + return {argument_type::SHORT, raw.substr(1)}; + else + return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; + } + + std::string extract_value(std::string const& /*raw*/) const override { + // In non-equal GNU, value comes in next token + throw std::runtime_error("No inline value in standard GNU convention."); + } + + bool requires_next_token() const override { + return true; + } + + std::string name() const override { + return "GNU-style long options"; + } + + std::string short_prec() const override { + return "-"; + } + + std::string long_prec() const override { + return "--"; + } + + static gnu_argument_convention instance; + private: + gnu_argument_convention() = default; + }; + + class gnu_equal_argument_convention : public base_convention { + public: + parsed_argument get_argument(std::string const& raw) const override { + auto pos = raw.find('='); + auto arg = pos != std::string::npos ? raw.substr(0, pos) : raw; + if (arg.starts_with(long_prec())) + return {argument_type::LONG, arg.substr(2) }; + else if (arg.starts_with(short_prec())) + return {argument_type::SHORT, arg.substr(1) }; + else + return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; + } + + std::string extract_value(std::string const& raw) const override { + auto pos = raw.find('='); + if (pos == std::string::npos || pos + 1 >= raw.size()) + throw std::runtime_error("Expected value after '='."); + return raw.substr(pos + 1); + } + + bool requires_next_token() const override { + return false; + } + + std::string name() const override { + return "GNU-style long options (equal signed form)"; + } + + std::string short_prec() const override { + return "-"; + } + + std::string long_prec() const override { + return "--"; + } + + static gnu_equal_argument_convention instance; + private: + gnu_equal_argument_convention() = default; + }; + + inline gnu_argument_convention gnu_argument_convention::instance{}; + inline gnu_equal_argument_convention gnu_equal_argument_convention::instance{}; +} + +namespace argument_parser::conventions { + static inline const implementations::gnu_argument_convention gnu_argument_convention = implementations::gnu_argument_convention::instance; + static inline const implementations::gnu_equal_argument_convention gnu_equal_argument_convention = implementations::gnu_equal_argument_convention::instance; +} + + +#endif \ No newline at end of file diff --git a/include/linux_parser.hpp b/include/linux_parser.hpp new file mode 100644 index 0000000..5b9c0fb --- /dev/null +++ b/include/linux_parser.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +#ifdef __linux__ +#ifndef LINUX_PARSER_HPP + +#include "argument_parser.hpp" + +namespace argument_parser { + class linux_parser : public base_parser { + public: + linux_parser() { + std::ifstream command_line_file{"/proc/self/cmdline"}; + std::getline(command_line_file, program_name, '\0'); + for(std::string line; std::getline(command_line_file, line, '\0');) { + parsed_arguments.emplace_back(line); + } + } + }; +} + +#endif +#endif \ No newline at end of file diff --git a/include/windows_parser.hpp b/include/windows_parser.hpp new file mode 100644 index 0000000..ef94f93 --- /dev/null +++ b/include/windows_parser.hpp @@ -0,0 +1,18 @@ +#pragma once +#ifdef _WIN32 +#ifndef WINDOWS_PARSER_HPP + +#include "argument_parser.hpp" +#include +#include + +namespace argument_parser { + class windows_parser : public base_parser { + public: + windows_parser() { + } + }; +} + +#endif +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ad3c39e --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,32 @@ +#include "../include/argument_parser.hpp" +#include "../include/linux_parser.hpp" +#include "../include/gnu_argument_convention.hpp" +#include +#include + +using namespace argument_parser::conventions; + +int main() { + auto parametered_action = argument_parser::helpers::make_parametered_action_ptr([](std::string const& test) { + std::cout << test << std::endl; + }); + + auto parser = argument_parser::linux_parser{}; + parser.add_argument("e", "echo", "echoes given variable", *parametered_action, false); + parser.add_argument("re", "required_echo", "required echo", *parametered_action, true); + + std::initializer_list conventions = { + &gnu_argument_convention, + &gnu_equal_argument_convention + }; + + parser.add_argument("h", "help", "Displays this help text.", argument_parser::helpers::make_non_parametered_action([&parser, conventions]{ + parser.display_help(conventions); + }), false); + + parser.handle_arguments(conventions); + + + + return 0; +} \ No newline at end of file