diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..20865fd --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +UseTab: Always +TabWidth: 4 +IndentWidth: 4 +NamespaceIndentation: All +AccessModifierOffset: -4 +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Empty +ColumnLimit: 120 +... diff --git a/CMakeLists.txt b/CMakeLists.txt index b24122e..a1cf353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,20 @@ cmake_minimum_required(VERSION 3.15) project(argument_parser) -set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD 17) 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) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -include_directories(include) -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) +include_directories(src/headers) +include_directories(src/headers/parser) +include_directories(src/headers/conventions) +include_directories(src/headers/conventions/implementations) +include_directories(src/headers/parser/platform_headers) +include_directories(src/headers/parser/parsing_traits) -add_executable(test src/main.cpp) \ No newline at end of file +file(GLOB_RECURSE SRC_FILES "src/source/*.cpp" "src/source/**/*.cpp" "src/source/**/**/*.cpp") + +add_executable(test src/main.cpp ${SRC_FILES}) \ No newline at end of file diff --git a/compile_commands.json b/compile_commands.json index 7ec9780..0664a7f 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,8 +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" -} -] + { + "directory": "C:/Users/samet/Documents/CPP/argparser/argument-parser/build", + "command": "C:\\PROGRA~2\\MICROS~2\\18\\BUILDT~1\\VC\\Tools\\MSVC\\1450~1.357\\bin\\Hostx64\\x64\\cl.exe /nologo /TP -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers\\parser -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers\\conventions -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers\\conventions\\implementations -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers\\parser\\platform_headers -IC:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\headers\\parser\\parsing_traits /DWIN32 /D_WINDOWS /GR /EHsc /Zi /Ob0 /Od /RTC1 -std:c++17 -MDd /FoCMakeFiles\\test.dir\\src\\main.cpp.obj /FdCMakeFiles\\test.dir\\ /FS -c C:\\Users\\samet\\Documents\\CPP\\argparser\\argument-parser\\src\\main.cpp", + "file": "C:/Users/samet/Documents/CPP/argparser/argument-parser/src/main.cpp", + "output": "C:/Users/samet/Documents/CPP/argparser/argument-parser/build/CMakeFiles/test.dir/src/main.cpp.obj" + } +] \ No newline at end of file diff --git a/include/conventions/base_convention.hpp b/include/conventions/base_convention.hpp deleted file mode 100644 index 796e4ac..0000000 --- a/include/conventions/base_convention.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include -#include -#include - -#ifndef BASE_CONVENTION_HPP -#define BASE_CONVENTION_HPP - -namespace argument_parser::conventions { - enum class argument_type { - SHORT, - LONG, - POSITIONAL, - INTERCHANGABLE, - 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; -} - -namespace argument_parser::conventions::helpers { - static std::string to_lower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return std::tolower(c); }); - return s; - } - - static std::string to_upper(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), - [](unsigned char c) { return std::toupper(c); }); - return s; - } -} - -#endif \ No newline at end of file diff --git a/include/conventions/implementations/gnu_argument_convention.hpp b/include/conventions/implementations/gnu_argument_convention.hpp deleted file mode 100644 index 50064ba..0000000 --- a/include/conventions/implementations/gnu_argument_convention.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#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/conventions/implementations/windows_argument_convention.hpp b/include/conventions/implementations/windows_argument_convention.hpp deleted file mode 100644 index ab8f9c6..0000000 --- a/include/conventions/implementations/windows_argument_convention.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once -#include "base_convention.hpp" -#include - -#ifndef WINDOWS_ARGUMENT_CONVENTION_HPP -#define WINDOWS_ARGUMENT_CONVENTION_HPP - -#ifndef ALLOW_DASH_FOR_WINDOWS -#define ALLOW_DASH_FOR_WINDOWS 1 -#endif - -namespace argument_parser::conventions::implementations { - class windows_argument_convention : public base_convention { - public: - explicit windows_argument_convention(bool accept_dash = true) - : accept_dash_(accept_dash) { - } - - parsed_argument get_argument(std::string const& raw) const override { - if (raw.empty()) { - return { argument_type::ERROR, "Empty argument token." }; - } - const char c0 = raw[0]; - const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); - if (!ok_prefix) { - return { argument_type::ERROR, - accept_dash_ - ? "Windows-style expects options to start with '/' (or '-' in compat mode)." - : "Windows-style expects options to start with '/'." }; - } - - if (raw.find_first_of("=:") != std::string::npos) { - return { argument_type::ERROR, - "Inline values are not allowed in this convention; provide the value in the next token." }; - } - - std::string name = helpers::to_lower(raw.substr(1)); - if (name.empty()) { - return { argument_type::ERROR, "Option name cannot be empty after '/'." }; - } - - return { argument_type::INTERCHANGABLE, std::move(name) }; - } - - std::string extract_value(std::string const& /*raw*/) const override { - throw std::runtime_error("No inline value; value must be provided in the next token."); - } - - bool requires_next_token() const override { return true; } - - std::string name() const override { return "Windows style options (next-token values)"; } - std::string short_prec() const override { return accept_dash_ ? "-" : "/"; } - std::string long_prec() const override { return "/"; } - - static windows_argument_convention instance; - private: - bool accept_dash_; - }; - - class windows_kv_argument_convention : public base_convention { - public: - explicit windows_kv_argument_convention(bool accept_dash = true) - : accept_dash_(accept_dash) { - } - - parsed_argument get_argument(std::string const& raw) const override { - if (raw.empty()) { - return { argument_type::ERROR, "Empty argument token." }; - } - const char c0 = raw[0]; - const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); - if (!ok_prefix) { - return { argument_type::ERROR, - accept_dash_ - ? "Windows-style expects options to start with '/' (or '-' in compat mode)." - : "Windows-style expects options to start with '/'." }; - } - - const std::size_t sep = raw.find_first_of("=:"); - if (sep == std::string::npos) { - return { argument_type::ERROR, - "Expected an inline value using '=' or ':' (e.g., /opt=value or /opt:value)." }; - } - if (sep == 1) { - return { argument_type::ERROR, "Option name cannot be empty before '=' or ':'." }; - } - - std::string name = helpers::to_lower(raw.substr(1, sep - 1)); - return { argument_type::INTERCHANGABLE, std::move(name) }; - } - - std::string extract_value(std::string const& raw) const override { - const std::size_t sep = raw.find_first_of("=:"); - if (sep == std::string::npos || sep + 1 >= raw.size()) - throw std::runtime_error("Expected a value after '=' or ':'."); - return raw.substr(sep + 1); - } - - bool requires_next_token() const override { return false; } - - std::string name() const override { return "Windows style options (inline values via '=' or ':')"; } - std::string short_prec() const override { return accept_dash_ ? "-" : "/"; } - std::string long_prec() const override { return "/"; } - - static windows_kv_argument_convention instance; - private: - bool accept_dash_; - }; - - - inline windows_argument_convention windows_argument_convention::instance = windows_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); - inline windows_kv_argument_convention windows_kv_argument_convention::instance = windows_kv_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); -} - -namespace argument_parser::conventions { - static inline const implementations::windows_argument_convention windows_argument_convention = implementations::windows_argument_convention::instance; - static inline const implementations::windows_kv_argument_convention windows_equal_argument_convention = implementations::windows_kv_argument_convention::instance; -} - -#endif // WINDOWS_ARGUMENT_CONVENTION_HPP \ No newline at end of file diff --git a/include/parser/argument_parser.hpp b/include/parser/argument_parser.hpp deleted file mode 100644 index c78972a..0000000 --- a/include/parser/argument_parser.hpp +++ /dev/null @@ -1,365 +0,0 @@ -#pragma once -#include -#include -#include -#ifndef ARGUMENT_PARSER_HPP -#define ARGUMENT_PARSER_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace argument_parser { - class action_base { - public: - virtual ~action_base() = default; - [[nodiscard]] virtual bool expects_parameter() const = 0; - virtual void invoke() const = 0; - virtual void invoke_with_parameter(const std::string& param) const = 0; - [[nodiscard]] virtual std::unique_ptr clone() const = 0; - }; - - template - class parametered_action : public action_base { - public: - explicit parametered_action(std::function const& handler) : handler(handler) {} - - using parameter_type = T; - - void invoke(const T& arg) const { - handler(arg); - } - - [[nodiscard]] bool expects_parameter() const override { return true; } - - void invoke() const override { - throw std::runtime_error("Parametered action requires a parameter"); - } - - void invoke_with_parameter(const std::string& param) const override { - T parsed_value = parsing_traits::parser_trait::parse(param); - invoke(parsed_value); - } - - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique>(handler); - } - - private: - std::function handler; - }; - - class non_parametered_action : public action_base { - public: - explicit non_parametered_action(std::function const& handler) : handler(handler) {} - - void invoke() const override { - handler(); - } - - [[nodiscard]] bool expects_parameter() const override { return false; } - - void invoke_with_parameter(const std::string& param) const override { - invoke(); - } - - [[nodiscard]] std::unique_ptr clone() const override { - return std::make_unique(handler); - } - - private: - std::function handler; - }; - - class base_parser; - - class argument { - public: - argument() : id(0), name(), action(std::make_unique([](){})), required(false), invoked(false) {} - - template - argument(const int id, std::string name, ActionType const& action) - : id(id), name(std::move(name)), action(action.clone()), required(false), invoked(false) {} - - argument(const argument& other) - : id(other.id), name(other.name), action(other.action->clone()), - required(other.required), invoked(other.invoked), help_text(other.help_text) {} - - argument& operator=(const argument& other) { - if (this != &other) { - id = other.id; - name = other.name; - action = other.action->clone(); - required = other.required; - invoked = other.invoked; - help_text = other.help_text; - } - return *this; - } - - argument(argument&& other) noexcept = default; - argument& operator=(argument&& other) noexcept = default; - - [[nodiscard]] bool is_required() const { return required; } - [[nodiscard]] std::string get_name() const { return name; } - [[nodiscard]] bool is_invoked() const { return invoked; } - - [[nodiscard]] bool expects_parameter() const { - return action->expects_parameter(); - } - - 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; } - - friend class base_parser; - - int id; - std::string name; - std::unique_ptr action; - bool required; - bool invoked; - std::string help_text; - }; - - namespace helpers { - template - static parametered_action make_parametered_action(std::function const& function) { - return parametered_action(function); - } - - static non_parametered_action make_non_parametered_action(std::function const& function) { - return non_parametered_action(function); - } - } - - class base_parser { - public: - 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); - } - - template - void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { - base_add_argument(short_arg, long_arg, help_text, 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 add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { - base_add_argument(short_arg, long_arg, help_text, required); - } - - void on_complete(std::function const& action) { - on_complete_events.emplace_back(action); - } - - template - std::optional get_optional(std::string const& arg) const { - auto id = find_argument_id(arg); - if (id.has_value()) { - auto value = stored_arguments.find(id.value()); - if (value != stored_arguments.end() && value->second.has_value()) return std::any_cast(value->second); - } - return std::nullopt; - } - - [[nodiscard]] std::string build_help_text(std::initializer_list convention_types) const { - std::stringstream ss; - ss << "Usage: " << program_name << " [OPTIONS]...\n"; - - for (auto const& [id, arg] : argument_map) { - auto short_arg = reverse_short_arguments.at(id); - auto long_arg = reverse_long_arguments.at(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); - } else if (arg.first == conventions::argument_type::INTERCHANGABLE) { - auto long_pos = long_arguments.find(arg.second); - if (long_pos != long_arguments.end()) return argument_map.at(long_pos->second); - 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); - } - - [[nodiscard]] std::optional find_argument_id(std::string const& arg) const { - auto long_pos = long_arguments.find(arg); - auto short_post = short_arguments.find(arg); - - if (long_pos != long_arguments.end()) return long_pos->second; - if (short_post != short_arguments.end()) return short_post->second; - return std::nullopt; - } - - void handle_arguments(std::initializer_list convention_types) { - for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++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) { - error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n"; - continue; - } - - try { - argument& corresponding_argument = get_argument(extracted); - - if (corresponding_argument.expects_parameter()) { - 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); - corresponding_argument.action->invoke_with_parameter(value_raw); - } else { - corresponding_argument.action->invoke(); - } - - 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" + error_stream.str()); - } - } - check_for_required_arguments(convention_types); - fire_on_complete_events(); - } - - void display_help(std::initializer_list convention_types) const { - std::cout << build_help_text(convention_types); - } - - protected: - base_parser() = default; - - std::string program_name; - std::vector parsed_arguments; - - private: - void assert_argument_not_exist(std::string const& short_arg, std::string const& long_arg) const { - if (short_arguments.contains(short_arg) || long_arguments.contains(long_arg)) { - throw std::runtime_error("The key already exists!"); - } - } - - static void set_argument_status(bool is_required, std::string const& help_text, argument& arg) { - arg.set_required(is_required); - arg.set_help_text(help_text); - } - - void place_argument(int id, argument const& arg, std::string const& short_arg, std::string const& long_arg) { - 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; - } - - template - void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, ActionType const& action, bool required) { - assert_argument_not_exist(short_arg, long_arg); - int id = id_counter.fetch_add(1); - argument arg(id, short_arg + "|" + long_arg, action); - set_argument_status(required, help_text, arg); - place_argument(id, arg, short_arg, long_arg); - } - - template - void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, bool required) { - assert_argument_not_exist(short_arg, long_arg); - int id = id_counter.fetch_add(1); - if constexpr (std::is_same_v) { - auto action = helpers::make_non_parametered_action([id, this] { stored_arguments[id] = std::any{ true }; }); - argument arg(id, short_arg + "|" + long_arg, action); - set_argument_status(required, help_text, arg); - place_argument(id, arg, short_arg, long_arg); - } else { - auto action = helpers::make_parametered_action([id, this](StoreType const& value) { stored_arguments[id] = std::any{ value }; }); - argument arg(id, short_arg + "|" + long_arg, action); - set_argument_status(required, help_text, arg); - place_argument(id, arg, short_arg, long_arg); - } - } - - void check_for_required_arguments(std::initializer_list convention_types) { - std::vector> required_args; - for (const auto &arg: argument_map | std::views::values) { - 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 fire_on_complete_events() const { - for(auto const& event : on_complete_events) { - event(*this); - } - } - - inline static std::atomic_int id_counter = 0; - - std::unordered_map stored_arguments; - 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::list> on_complete_events; - - friend class linux_parser; - friend class windows_parser; - friend class macos_parser; - friend class fake_parser; - }; -} - -#endif // ARGUMENT_PARSER_HPP \ No newline at end of file diff --git a/include/parser/fake_parser.hpp b/include/parser/fake_parser.hpp deleted file mode 100644 index cba9de2..0000000 --- a/include/parser/fake_parser.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#ifndef FAKE_PARSER_HPP -#define FAKE_PARSER_HPP - -#include -#include -#include - -namespace argument_parser { - class fake_parser : public base_parser { - public: - fake_parser() = default; - - fake_parser(std::string program_name, std::vector const& arguments) { - this->program_name = std::move(program_name); - parsed_arguments = arguments; - } - - fake_parser(std::string const& program_name, std::vector&& arguments) { - this->program_name = program_name; - parsed_arguments = std::move(arguments); - } - - fake_parser(std::string const& program_name, std::initializer_list const& arguments) : - fake_parser(program_name, std::vector(arguments)) {} - - void set_program_name(std::string const& program_name) { - this->program_name = program_name; - } - - void set_parsed_arguments(std::vector const& parsed_arguments) { - this->parsed_arguments = parsed_arguments; - } - }; -} - -#endif \ No newline at end of file diff --git a/include/parser/parser_v2.hpp b/include/parser/parser_v2.hpp deleted file mode 100644 index c0d7b6a..0000000 --- a/include/parser/parser_v2.hpp +++ /dev/null @@ -1,249 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace argument_parser::v2 { - namespace internal { - static inline fake_parser fake_parser{}; - } - - enum class add_argument_flags { - ShortArgument, - LongArgument, - HelpText, - Action, - Required - }; - - namespace flags { - constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument; - constexpr static inline add_argument_flags LongArgument = add_argument_flags::LongArgument; - constexpr static inline add_argument_flags HelpText = add_argument_flags::HelpText; - constexpr static inline add_argument_flags Action = add_argument_flags::Action; - constexpr static inline add_argument_flags Required = add_argument_flags::Required; - } - - class base_parser : private argument_parser::base_parser { - public: - template - using typed_flag_value = std::variant, bool>; - using non_typed_flag_value = std::variant; - - template - using typed_argument_pair = std::pair>; - using non_typed_argument_pair = std::pair; - - template - void add_argument(std::unordered_map> const& argument_pairs) { - std::unordered_map found_params {{ - extended_add_argument_flags::IsTyped, true - }}; - - std::string short_arg, long_arg, help_text; - std::unique_ptr action; - bool required = false; - - if (argument_pairs.contains(add_argument_flags::ShortArgument)) { - found_params[extended_add_argument_flags::ShortArgument] = true; - short_arg = get_or_throw(argument_pairs.at(add_argument_flags::ShortArgument), "short"); - } - if (argument_pairs.contains(add_argument_flags::LongArgument)) { - found_params[extended_add_argument_flags::LongArgument] = true; - long_arg = get_or_throw(argument_pairs.at(add_argument_flags::LongArgument), "long"); - if (short_arg.empty()) short_arg = long_arg; - } else { - if (!short_arg.empty()) long_arg = short_arg; - } - - if (argument_pairs.contains(add_argument_flags::Action)) { - found_params[extended_add_argument_flags::Action] = true; - action = get_or_throw>(argument_pairs.at(add_argument_flags::Action), "action").clone(); - } - if (argument_pairs.contains(add_argument_flags::HelpText)) { - help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); - } else { - help_text = short_arg + ", " + long_arg; - } - if (argument_pairs.contains(add_argument_flags::Required) && get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { - required = true; - } - - auto suggested_add = suggest_candidate(found_params); - if (suggested_add == candidate_type::unknown) { - throw std::runtime_error("Could not match any add argument overload to given parameters. Are you missing some required parameter?"); - } - switch (suggested_add) { - case candidate_type::typed_action: - base::add_argument(short_arg, long_arg, help_text, *static_cast*>(&(*action)), required); - break; - case candidate_type::store_other: - base::add_argument(short_arg, long_arg, help_text, required); - break; - default: - throw std::runtime_error("Could not match the arguments against any overload."); - } - } - - template - void add_argument(std::initializer_list> const& pairs) { - std::unordered_map> args; - - for (auto& [k, v]: pairs) { - args[k] = v; - } - - add_argument(args); - } - - void add_argument(std::initializer_list const& pairs) { - std::unordered_map args; - - for (auto& [k, v] : pairs) { - args[k] = v; - } - - add_argument(args); - } - - void add_argument(std::unordered_map const& argument_pairs) { - std::unordered_map found_params {{ - extended_add_argument_flags::IsTyped, false - }}; - - std::string short_arg, long_arg, help_text; - std::unique_ptr action; - bool required = false; - - if (argument_pairs.contains(add_argument_flags::ShortArgument)) { - found_params[extended_add_argument_flags::ShortArgument] = true; - short_arg = get_or_throw(argument_pairs.at(add_argument_flags::ShortArgument), "short"); - } - if (argument_pairs.contains(add_argument_flags::LongArgument)) { - found_params[extended_add_argument_flags::LongArgument] = true; - long_arg = get_or_throw(argument_pairs.at(add_argument_flags::LongArgument), "long"); - if (short_arg.empty()) short_arg = long_arg; - } else { - if (!short_arg.empty()) long_arg = short_arg; - } - if (argument_pairs.contains(add_argument_flags::Action)) { - found_params[extended_add_argument_flags::Action] = true; - action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); - - } - if (argument_pairs.contains(add_argument_flags::HelpText)) { - help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); - } else { - help_text = short_arg + ", " + long_arg; - } - - if (argument_pairs.contains(add_argument_flags::Required) && get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { - required = true; - } - - auto suggested_add = suggest_candidate(found_params); - if (suggested_add == candidate_type::unknown) { - throw std::runtime_error("Could not match any add argument overload to given parameters. Are you missing some required parameter?"); - } - - switch (suggested_add) { - case candidate_type::non_typed_action: - base::add_argument(short_arg, long_arg, help_text, *static_cast(&(*action)), required); - break; - case candidate_type::store_boolean: - base::add_argument(short_arg, long_arg, help_text, required); - break; - default: - throw std::runtime_error("Could not match the arguments against any overload. The suggested candidate was: " + std::to_string((int(suggested_add)))); - } - } - - argument_parser::base_parser& to_v1() { - return *this; - } - - void handle_arguments(std::initializer_list convention_types) { - base::handle_arguments(convention_types); - } - - template - std::optional get_optional(std::string const& arg) { - return base::get_optional(arg); - } - - void on_complete(std::function const& action) { - base::on_complete(action); - } - - protected: - void set_program_name(std::string p) { - base::program_name = std::move(p); - } - - std::vector& ref_parsed_args() { - return base::parsed_arguments; - } - - private: - using base = argument_parser::base_parser; - enum class extended_add_argument_flags { - ShortArgument, - LongArgument, - Action, - IsTyped - }; - - enum class candidate_type { - store_boolean, - store_other, - typed_action, - non_typed_action, - unknown - }; - - template - bool satisfies_at_least_one(std::array const& arr, std::unordered_map const& map) { - for (const auto& req: arr) { - if (map.contains(req)) return true; - } - return false; - } - - candidate_type suggest_candidate(std::unordered_map const& available_vars) { - auto constexpr required_at_least_one = std::array { extended_add_argument_flags::ShortArgument, extended_add_argument_flags::LongArgument }; - if (!satisfies_at_least_one(required_at_least_one, available_vars)) return candidate_type::unknown; - - if (available_vars.contains(extended_add_argument_flags::Action)) { - if (available_vars.at(extended_add_argument_flags::IsTyped)) - return candidate_type::typed_action; - else return candidate_type::non_typed_action; - } - - if (available_vars.at(extended_add_argument_flags::IsTyped)) return candidate_type::store_other; - return candidate_type::store_boolean; - } - - template - T get_or_throw(typed_flag_value const& v, std::string_view key) { - if (auto p = std::get_if(&v)) return *p; - throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); - } - - template - T get_or_throw(non_typed_flag_value const& v, std::string_view key) { - if (auto p = std::get_if(&v)) return *p; - throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); - } - }; -} \ No newline at end of file diff --git a/include/parser/parsing_traits/traits.hpp b/include/parser/parsing_traits/traits.hpp deleted file mode 100644 index 657cbeb..0000000 --- a/include/parser/parsing_traits/traits.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#ifndef PARSING_TRAITS_HPP -#define PARSING_TRAITS_HPP - -#include -#include - -namespace argument_parser::parsing_traits { - template - struct parser_trait { - using type = T_; - static T_ parse(const std::string& input); - }; - - template <> - struct parser_trait { - static std::string parse(const std::string& input) { - return input; - } - }; - - template<> - struct parser_trait { - 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 { - static int parse(const std::string& input) { - return std::stoi(input); - } - }; - - template <> - struct parser_trait { - static float parse(const std::string& input) { - return std::stof(input); - } - }; - - template <> - struct parser_trait { - static double parse(const std::string& input) { - return std::stod(input); - } - }; -} - -#endif \ No newline at end of file diff --git a/include/parser/platform_headers/linux_parser.hpp b/include/parser/platform_headers/linux_parser.hpp deleted file mode 100644 index 38e741d..0000000 --- a/include/parser/platform_headers/linux_parser.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#ifdef __linux__ -#ifndef LINUX_PARSER_HPP -#define LINUX_PARSER_HPP -#include -#include - -#include - -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/parser/platform_headers/macos_parser.hpp b/include/parser/platform_headers/macos_parser.hpp deleted file mode 100644 index da55102..0000000 --- a/include/parser/platform_headers/macos_parser.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#ifdef __APPLE__ -#ifndef MACOS_PARSER_HPP -#define MACOS_PARSER_HPP - -#include -#include -#include -#include - -namespace argument_parser { - class macos_parser : public base_parser { - public: - macos_parser() { - const int argc = *_NSGetArgc(); - if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) { - program_name = (argv[0]); - for (int i = 1; i < argc; ++i) { - if (argv[i] != nullptr) parsed_arguments.emplace_back(argv[i]); - } - } - } - }; - - namespace v2 { - class macos_parser : public v2::base_parser { - public: - macos_parser() { - const int argc = *_NSGetArgc(); - if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) { - set_program_name(argv[0]); - for (int i = 1; i < argc; ++i) { - if (argv[i] != nullptr) ref_parsed_args().emplace_back(argv[i]); - } - } - } - }; - } -} - -#endif -#endif \ No newline at end of file diff --git a/include/parser/platform_headers/windows_parser.hpp b/include/parser/platform_headers/windows_parser.hpp deleted file mode 100644 index 12524f2..0000000 --- a/include/parser/platform_headers/windows_parser.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once -#ifdef _WIN32 -#include -#include -#include -#include -#include - -namespace argument_parser { - -class windows_parser : public base_parser { -public: - windows_parser() { - int nArgs = 0; - LPWSTR* raw = ::CommandLineToArgvW(::GetCommandLineW(), &nArgs); - if (!raw) { - throw std::runtime_error("CommandLineToArgvW failed (" + - std::to_string(::GetLastError()) + ")"); - } - std::unique_ptr argvW{ raw, [](LPWSTR* p){ if (p) ::LocalFree(p); } }; - - if (nArgs <= 0) { - throw std::runtime_error("No command line arguments found."); - } - - { - std::wstring w0 = argvW.get()[0]; - auto pos = w0.find_last_of(L"\\/"); - std::wstring base = (pos == std::wstring::npos) ? w0 : w0.substr(pos + 1); - program_name = utf8_from_wstring(base); - } - - parsed_arguments.reserve(static_cast(nArgs > 0 ? nArgs - 1 : 0)); - for (int i = 1; i < nArgs; ++i) { - parsed_arguments.emplace_back(utf8_from_wstring(argvW.get()[i])); - } - } - -private: - static std::string utf8_from_wstring(const std::wstring& w) { - if (w.empty()) return {}; - int needed = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, - w.c_str(), -1, nullptr, 0, nullptr, nullptr); - if (needed <= 0) { - throw std::runtime_error("WideCharToMultiByte sizing failed (" + - std::to_string(::GetLastError()) + ")"); - } - std::string out; - out.resize(static_cast(needed - 1)); - int written = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, - w.c_str(), -1, out.data(), needed - 1, nullptr, nullptr); - if (written <= 0) { - throw std::runtime_error("WideCharToMultiByte convert failed (" + - std::to_string(::GetLastError()) + ")"); - } - return out; - } -}; - -} -#endif \ No newline at end of file diff --git a/include/argparse b/src/headers/argparse similarity index 62% rename from include/argparse rename to src/headers/argparse index 3270284..4396e66 100644 --- a/include/argparse +++ b/src/headers/argparse @@ -6,17 +6,29 @@ #ifdef __linux__ #include namespace argument_parser { - using parser = linux_parser; + using parser = linux_parser; +} + +namespace argument_parser::v2 { + using parser = linux_parser; } #elif __APPLE__ #include namespace argument_parser { - using parser = macos_parser; + using parser = macos_parser; +} + +namespace argument_parser::v2 { + using parser = macos_parser; } #elif _WIN32 #include namespace argument_parser { - using parser = windows_parser; + using parser = windows_parser; +} + +namespace argument_parser::v2 { + using parser = windows_parser; } #else #error "Unsupported platform" diff --git a/src/headers/conventions/base_convention.hpp b/src/headers/conventions/base_convention.hpp new file mode 100644 index 0000000..96dc59e --- /dev/null +++ b/src/headers/conventions/base_convention.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +#ifndef BASE_CONVENTION_HPP +#define BASE_CONVENTION_HPP + +namespace argument_parser::conventions { + enum class argument_type { SHORT, LONG, POSITIONAL, INTERCHANGABLE, 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; +} // namespace argument_parser::conventions + +namespace argument_parser::conventions::helpers { + std::string to_lower(std::string s); + std::string to_upper(std::string s); +} // namespace argument_parser::conventions::helpers + +#endif \ No newline at end of file diff --git a/src/headers/conventions/implementations/gnu_argument_convention.hpp b/src/headers/conventions/implementations/gnu_argument_convention.hpp new file mode 100644 index 0000000..986e6d0 --- /dev/null +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "base_convention.hpp" + +#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; + std::string extract_value(std::string const & /*raw*/) const override; + bool requires_next_token() const override; + std::string name() const override; + std::string short_prec() const override; + std::string long_prec() const override; + 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; + std::string extract_value(std::string const &raw) const override; + bool requires_next_token() const override; + std::string name() const override; + std::string short_prec() const override; + std::string long_prec() const override; + 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::implementations + +namespace argument_parser::conventions { + static const implementations::gnu_argument_convention gnu_argument_convention = + implementations::gnu_argument_convention::instance; + static const implementations::gnu_equal_argument_convention gnu_equal_argument_convention = + implementations::gnu_equal_argument_convention::instance; +} // namespace argument_parser::conventions + +#endif \ No newline at end of file diff --git a/src/headers/conventions/implementations/windows_argument_convention.hpp b/src/headers/conventions/implementations/windows_argument_convention.hpp new file mode 100644 index 0000000..06e7a5d --- /dev/null +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "base_convention.hpp" + +#ifndef WINDOWS_ARGUMENT_CONVENTION_HPP +#define WINDOWS_ARGUMENT_CONVENTION_HPP + +#ifndef ALLOW_DASH_FOR_WINDOWS +#define ALLOW_DASH_FOR_WINDOWS 1 +#endif + +namespace argument_parser::conventions::implementations { + class windows_argument_convention : public base_convention { + public: + explicit windows_argument_convention(bool accept_dash = true); + parsed_argument get_argument(std::string const &raw) const override; + std::string extract_value(std::string const & /*raw*/) const override; + bool requires_next_token() const override; + std::string name() const override; + std::string short_prec() const override; + std::string long_prec() const override; + static windows_argument_convention instance; + + private: + bool accept_dash_; + }; + + class windows_kv_argument_convention : public base_convention { + public: + explicit windows_kv_argument_convention(bool accept_dash = true); + parsed_argument get_argument(std::string const &raw) const override; + std::string extract_value(std::string const &raw) const override; + bool requires_next_token() const override; + std::string name() const override; + std::string short_prec() const override; + std::string long_prec() const override; + static windows_kv_argument_convention instance; + + private: + bool accept_dash_; + }; + + inline windows_argument_convention windows_argument_convention::instance = + windows_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); + inline windows_kv_argument_convention windows_kv_argument_convention::instance = + windows_kv_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); +} // namespace argument_parser::conventions::implementations + +namespace argument_parser::conventions { + static inline const implementations::windows_argument_convention windows_argument_convention = + implementations::windows_argument_convention::instance; + static inline const implementations::windows_kv_argument_convention windows_equal_argument_convention = + implementations::windows_kv_argument_convention::instance; +} // namespace argument_parser::conventions + +#endif // WINDOWS_ARGUMENT_CONVENTION_HPP \ No newline at end of file diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp new file mode 100644 index 0000000..2061cc4 --- /dev/null +++ b/src/headers/parser/argument_parser.hpp @@ -0,0 +1,236 @@ +#pragma once +#include +#include +#include +#ifndef ARGUMENT_PARSER_HPP +#define ARGUMENT_PARSER_HPP +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace argument_parser { + class action_base { + public: + virtual ~action_base() = default; + [[nodiscard]] virtual bool expects_parameter() const = 0; + virtual void invoke() const = 0; + virtual void invoke_with_parameter(const std::string ¶m) const = 0; + [[nodiscard]] virtual std::unique_ptr clone() const = 0; + }; + + template class parametered_action : public action_base { + public: + explicit parametered_action(std::function const &handler) : handler(handler) {} + using parameter_type = T; + void invoke(const T &arg) const { + handler(arg); + } + + [[nodiscard]] bool expects_parameter() const override { + return true; + } + + void invoke() const override { + throw std::runtime_error("Parametered action requires a parameter"); + } + + void invoke_with_parameter(const std::string ¶m) const override { + T parsed_value = parsing_traits::parser_trait::parse(param); + invoke(parsed_value); + } + + [[nodiscard]] std::unique_ptr clone() const override { + return std::make_unique>(handler); + } + + private: + std::function handler; + }; + + class non_parametered_action : public action_base { + public: + explicit non_parametered_action(std::function const &handler) : handler(handler) {} + + void invoke() const override { + handler(); + } + + [[nodiscard]] bool expects_parameter() const override { + return false; + } + + void invoke_with_parameter(const std::string ¶m) const override { + invoke(); + } + + [[nodiscard]] std::unique_ptr clone() const override { + return std::make_unique(handler); + } + + private: + std::function handler; + }; + + class base_parser; + + class argument { + public: + argument(); + + template + argument(const int id, std::string name, ActionType const &action) + : id(id), name(std::move(name)), action(action.clone()), required(false), invoked(false) {} + + argument(const argument &other); + argument &operator=(const argument &other); + argument(argument &&other) noexcept = default; + argument &operator=(argument &&other) noexcept = default; + + [[nodiscard]] bool is_required() const; + [[nodiscard]] std::string get_name() const; + [[nodiscard]] bool is_invoked() const; + [[nodiscard]] bool expects_parameter() const; + [[nodiscard]] std::string get_help_text() const; + + private: + void set_required(bool val); + void set_invoked(bool val); + void set_help_text(std::string const &text); + + friend class base_parser; + + int id; + std::string name; + std::unique_ptr action; + bool required; + bool invoked; + std::string help_text; + }; + + namespace helpers { + template + static parametered_action make_parametered_action(std::function const &function) { + return parametered_action(function); + } + + static non_parametered_action make_non_parametered_action(std::function const &function) { + return non_parametered_action(function); + } + } // namespace helpers + + class base_parser { + public: + 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); + } + + template + void add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text, + bool required) { + base_add_argument(short_arg, long_arg, help_text, 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 add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text, + bool required) { + base_add_argument(short_arg, long_arg, help_text, required); + } + + void on_complete(std::function const &action); + + template std::optional get_optional(std::string const &arg) const { + auto id = find_argument_id(arg); + if (id.has_value()) { + auto value = stored_arguments.find(id.value()); + if (value != stored_arguments.end() && value->second.has_value()) { + return std::any_cast(value->second); + } + } + return std::nullopt; + } + + [[nodiscard]] std::string + build_help_text(std::initializer_list convention_types) const; + argument &get_argument(conventions::parsed_argument const &arg); + [[nodiscard]] std::optional find_argument_id(std::string const &arg) const; + void handle_arguments(std::initializer_list convention_types); + void display_help(std::initializer_list convention_types) const; + + protected: + base_parser() = default; + + std::string program_name; + std::vector parsed_arguments; + + private: + void assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const; + static void set_argument_status(bool is_required, std::string const &help_text, argument &arg); + void place_argument(int id, argument const &arg, std::string const &short_arg, std::string const &long_arg); + + template + void base_add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text, + ActionType const &action, bool required) { + assert_argument_not_exist(short_arg, long_arg); + int id = id_counter.fetch_add(1); + argument arg(id, short_arg + "|" + long_arg, action); + set_argument_status(required, help_text, arg); + place_argument(id, arg, short_arg, long_arg); + } + + template + void base_add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text, + bool required) { + assert_argument_not_exist(short_arg, long_arg); + int id = id_counter.fetch_add(1); + if constexpr (std::is_same_v) { + auto action = + helpers::make_non_parametered_action([id, this] { stored_arguments[id] = std::any{true}; }); + argument arg(id, short_arg + "|" + long_arg, action); + set_argument_status(required, help_text, arg); + place_argument(id, arg, short_arg, long_arg); + } else { + auto action = helpers::make_parametered_action( + [id, this](StoreType const &value) { stored_arguments[id] = std::any{value}; }); + argument arg(id, short_arg + "|" + long_arg, action); + set_argument_status(required, help_text, arg); + place_argument(id, arg, short_arg, long_arg); + } + } + + void check_for_required_arguments(std::initializer_list convention_types); + void fire_on_complete_events() const; + + inline static std::atomic_int id_counter = 0; + + std::unordered_map stored_arguments; + 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::list> on_complete_events; + + friend class linux_parser; + friend class windows_parser; + friend class macos_parser; + friend class fake_parser; + }; +} // namespace argument_parser + +#endif // ARGUMENT_PARSER_HPP \ No newline at end of file diff --git a/src/headers/parser/fake_parser.hpp b/src/headers/parser/fake_parser.hpp new file mode 100644 index 0000000..e4cb306 --- /dev/null +++ b/src/headers/parser/fake_parser.hpp @@ -0,0 +1,23 @@ +#pragma once + +#ifndef FAKE_PARSER_HPP +#define FAKE_PARSER_HPP + +#include +#include +#include + +namespace argument_parser { + class fake_parser : public base_parser { + public: + fake_parser() = default; + fake_parser(std::string program_name, std::vector const &arguments); + fake_parser(std::string const &program_name, std::vector &&arguments); + fake_parser(std::string const &program_name, std::initializer_list const &arguments); + + void set_program_name(std::string const &program_name); + void set_parsed_arguments(std::vector const &parsed_arguments); + }; +} // namespace argument_parser + +#endif \ No newline at end of file diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp new file mode 100644 index 0000000..0b94fa5 --- /dev/null +++ b/src/headers/parser/parser_v2.hpp @@ -0,0 +1,211 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace argument_parser::v2 { + namespace internal { + static inline fake_parser fake_parser{}; + } + + enum class add_argument_flags { ShortArgument, LongArgument, HelpText, Action, Required }; + + namespace flags { + constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument; + constexpr static inline add_argument_flags LongArgument = add_argument_flags::LongArgument; + constexpr static inline add_argument_flags HelpText = add_argument_flags::HelpText; + constexpr static inline add_argument_flags Action = add_argument_flags::Action; + constexpr static inline add_argument_flags Required = add_argument_flags::Required; + } // namespace flags + + class base_parser : private argument_parser::base_parser { + public: + template using typed_flag_value = std::variant, bool>; + using non_typed_flag_value = std::variant; + + template using typed_argument_pair = std::pair>; + using non_typed_argument_pair = std::pair; + + template + void add_argument(std::unordered_map> const &argument_pairs) { + add_argument_impl, T>(argument_pairs); + } + + template void add_argument(std::initializer_list> const &pairs) { + std::unordered_map> args; + + for (auto &[k, v] : pairs) { + args[k] = v; + } + + add_argument(args); + } + + void add_argument(std::initializer_list const &pairs) { + std::unordered_map args; + + for (auto &[k, v] : pairs) { + args[k] = v; + } + + add_argument(args); + } + + void add_argument(std::unordered_map const &argument_pairs) { + add_argument_impl(argument_pairs); + } + + argument_parser::base_parser &to_v1() { + return *this; + } + + void handle_arguments(std::initializer_list convention_types) { + base::handle_arguments(convention_types); + } + + template std::optional get_optional(std::string const &arg) { + return base::get_optional(arg); + } + + void on_complete(std::function const &action) { + base::on_complete(action); + } + + protected: + void set_program_name(std::string p) { + base::program_name = std::move(p); + } + + std::vector &ref_parsed_args() { + return base::parsed_arguments; + } + + private: + template + void add_argument_impl(ArgsMap const &argument_pairs) { + std::unordered_map found_params{ + {extended_add_argument_flags::IsTyped, IsTyped}}; + + std::string short_arg, long_arg, help_text; + std::unique_ptr action; + bool required = false; + + if (argument_pairs.find(add_argument_flags::ShortArgument) != argument_pairs.end()) { + found_params[extended_add_argument_flags::ShortArgument] = true; + short_arg = get_or_throw(argument_pairs.at(add_argument_flags::ShortArgument), "short"); + } + if (argument_pairs.find(add_argument_flags::LongArgument) != argument_pairs.end()) { + found_params[extended_add_argument_flags::LongArgument] = true; + long_arg = get_or_throw(argument_pairs.at(add_argument_flags::LongArgument), "long"); + if (short_arg.empty()) + short_arg = long_arg; + } else { + if (!short_arg.empty()) + long_arg = short_arg; + } + + if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { + found_params[extended_add_argument_flags::Action] = true; + action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); + } + if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { + help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); + } else { + help_text = short_arg + ", " + long_arg; + } + + if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && + get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { + required = true; + } + + auto suggested_add = suggest_candidate(found_params); + if (suggested_add == candidate_type::unknown) { + throw std::runtime_error("Could not match any add argument overload to given parameters. Are you " + "missing some required parameter?"); + } + + if constexpr (IsTyped) { + switch (suggested_add) { + case candidate_type::typed_action: + base::add_argument(short_arg, long_arg, help_text, *static_cast(&(*action)), + required); + break; + case candidate_type::store_other: + base::add_argument(short_arg, long_arg, help_text, required); + break; + default: + throw std::runtime_error("Could not match the arguments against any overload."); + } + } else { + switch (suggested_add) { + case candidate_type::non_typed_action: + base::add_argument(short_arg, long_arg, help_text, *static_cast(&(*action)), + required); + break; + case candidate_type::store_boolean: + base::add_argument(short_arg, long_arg, help_text, required); + break; + default: + throw std::runtime_error( + "Could not match the arguments against any overload. The suggested candidate was: " + + std::to_string((int(suggested_add)))); + } + } + } + + using base = argument_parser::base_parser; + enum class extended_add_argument_flags { ShortArgument, LongArgument, Action, IsTyped }; + + enum class candidate_type { store_boolean, store_other, typed_action, non_typed_action, unknown }; + + template + bool satisfies_at_least_one(std::array const &arr, std::unordered_map const &map) { + for (const auto &req : arr) { + if (map.find(req) != map.end()) + return true; + } + return false; + } + + candidate_type suggest_candidate(std::unordered_map const &available_vars) { + auto constexpr required_at_least_one = std::array{ + extended_add_argument_flags::ShortArgument, extended_add_argument_flags::LongArgument}; + if (!satisfies_at_least_one(required_at_least_one, available_vars)) + return candidate_type::unknown; + + if (available_vars.find(extended_add_argument_flags::Action) != available_vars.end()) { + if (available_vars.at(extended_add_argument_flags::IsTyped)) + return candidate_type::typed_action; + else + return candidate_type::non_typed_action; + } + + if (available_vars.at(extended_add_argument_flags::IsTyped)) + return candidate_type::store_other; + return candidate_type::store_boolean; + } + + template T get_or_throw(typed_flag_value const &v, std::string_view key) { + if (auto p = std::get_if(&v)) + return *p; + throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); + } + + template T get_or_throw(non_typed_flag_value const &v, std::string_view key) { + if (auto p = std::get_if(&v)) + return *p; + throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); + } + }; +} // namespace argument_parser::v2 \ No newline at end of file diff --git a/src/headers/parser/parsing_traits/traits.hpp b/src/headers/parser/parsing_traits/traits.hpp new file mode 100644 index 0000000..63189c3 --- /dev/null +++ b/src/headers/parser/parsing_traits/traits.hpp @@ -0,0 +1,34 @@ +#pragma once +#ifndef PARSING_TRAITS_HPP +#define PARSING_TRAITS_HPP + +#include + +namespace argument_parser::parsing_traits { + template struct parser_trait { + using type = T_; + static T_ parse(const std::string &input); + }; + + template <> struct parser_trait { + static std::string parse(const std::string &input); + }; + + template <> struct parser_trait { + static bool parse(const std::string &input); + }; + + template <> struct parser_trait { + static int parse(const std::string &input); + }; + + template <> struct parser_trait { + static float parse(const std::string &input); + }; + + template <> struct parser_trait { + static double parse(const std::string &input); + }; +} // namespace argument_parser::parsing_traits + +#endif \ No newline at end of file diff --git a/src/headers/parser/platform_headers/linux_parser.hpp b/src/headers/parser/platform_headers/linux_parser.hpp new file mode 100644 index 0000000..ff16822 --- /dev/null +++ b/src/headers/parser/platform_headers/linux_parser.hpp @@ -0,0 +1,25 @@ +#pragma once + +#ifdef __linux__ +#ifndef LINUX_PARSER_HPP +#define LINUX_PARSER_HPP + +#include +#include + +namespace argument_parser { + class linux_parser : public base_parser { + public: + linux_parser(); + }; + + namespace v2 { + class linux_parser : public v2::base_parser { + public: + linux_parser(); + }; + } // namespace v2 +} // namespace argument_parser + +#endif +#endif \ No newline at end of file diff --git a/src/headers/parser/platform_headers/macos_parser.hpp b/src/headers/parser/platform_headers/macos_parser.hpp new file mode 100644 index 0000000..f63c2f5 --- /dev/null +++ b/src/headers/parser/platform_headers/macos_parser.hpp @@ -0,0 +1,26 @@ +#pragma once +#ifdef __APPLE__ +#ifndef MACOS_PARSER_HPP +#define MACOS_PARSER_HPP + +#include +#include +#include +#include + +namespace argument_parser { + class macos_parser : public base_parser { + public: + macos_parser(); + }; + + namespace v2 { + class macos_parser : public v2::base_parser { + public: + macos_parser(); + }; + } // namespace v2 +} // namespace argument_parser + +#endif +#endif \ No newline at end of file diff --git a/src/headers/parser/platform_headers/windows_parser.hpp b/src/headers/parser/platform_headers/windows_parser.hpp new file mode 100644 index 0000000..17ad798 --- /dev/null +++ b/src/headers/parser/platform_headers/windows_parser.hpp @@ -0,0 +1,19 @@ +#pragma once +#ifdef _WIN32 +#include +#include + +namespace argument_parser { + class windows_parser : public base_parser { + public: + windows_parser(); + }; + + namespace v2 { + class windows_parser : public v2::base_parser { + public: + windows_parser(); + }; + } // namespace v2 +} // namespace argument_parser +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e4465b5..67727e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,233 +1,177 @@ -#include "macos_parser.hpp" #include #define ALLOW_DASH_FOR_WINDOWS 0 -#include #include -#include -#include #include +#include +#include #include +#include #include - struct Point { - int x, y; + int x, y; }; -template<> -struct argument_parser::parsing_traits::parser_trait { - 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}; - } -}; - -template<> -struct argument_parser::parsing_traits::parser_trait { - static std::regex parse(const std::string& input) { - return std::regex(input); - } -}; - -template<> -struct argument_parser::parsing_traits::parser_trait> { - static std::vector parse(const std::string& input) { - std::vector result; - std::stringstream ss(input); - std::string item; - while (std::getline(ss, item, ',')) { - result.push_back(std::stoi(item)); - } - return result; - } -}; - - -template<> -struct argument_parser::parsing_traits::parser_trait> { - static std::vector parse(const std::string& input) { - std::vector result; - auto copyInput = input; - if (input.starts_with("[")) { - copyInput = input.substr(1, input.size()); - if (copyInput.ends_with("]")) { - copyInput = copyInput.substr(0, copyInput.size() - 1); - } else { - throw std::runtime_error("Invalid vector format. Expected closing ']'."); - } - } - std::stringstream ss(copyInput); - std::string item; - while (std::getline(ss, item, ',')) { - result.push_back(item); - } - return result; - } -}; - -const std::initializer_list conventions = { - &argument_parser::conventions::gnu_argument_convention, - &argument_parser::conventions::gnu_equal_argument_convention, - &argument_parser::conventions::windows_argument_convention, - &argument_parser::conventions::windows_equal_argument_convention -}; - -const auto echo = argument_parser::helpers::make_parametered_action([](std::string const& text) { - std::cout << text << std::endl; -}); - - -const auto echo_point = argument_parser::helpers::make_parametered_action([](Point const& point) { - std::cout << "Point(" << point.x << ", " << point.y << ")" << std::endl; -}); - -const auto cat = argument_parser::helpers::make_parametered_action([](std::string const& file_name) { - std::ifstream file(file_name); - if (!file.is_open()) { - throw std::runtime_error("Could not open file"); - } - std::string line; - while (std::getline(file, line)) { - std::cout << line << std::endl; - } - - file.close(); -}); - -auto grep(argument_parser::base_parser const& parser, std::string const& filename, std::regex const& pattern) { - if (filename.empty()) { - std::cerr << "Missing filename" << std::endl; - parser.display_help(conventions); - exit(-1); - } - std::ifstream file(filename); - if (!file.is_open()) { - std::cerr << "Could not open file: \"" << filename << '"' << std::endl; - exit(-1); - } - - for (std::string line; std::getline(file, line);) { - if (std::regex_search(line, pattern)) { - std::cout << line << std::endl; - } - } - - file.close(); -} - -void run_grep(argument_parser::base_parser const& parser) { - auto filename = parser.get_optional("file"); - auto pattern = parser.get_optional("grep"); - - if (filename && pattern) { - grep(parser, filename.value(), pattern.value()); - } else if (filename) { - std::cerr << "Missing grep pattern" << std::endl; - parser.display_help(conventions); - exit(-1); - } else if (pattern) { - std::cerr << "Missing filename" << std::endl; - parser.display_help(conventions); - exit(-1); - } -} - - -int v1Examples() { - - auto parser = argument_parser::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", false); - parser.add_argument("g", "grep", "Grep pattern, required only if using grep", false); - parser.add_argument("c", "cat", "Prints the content of the file", cat, false); - parser.add_argument("h", "help", "Displays this help text.", argument_parser::helpers::make_non_parametered_action([&parser]{ - parser.display_help(conventions); - }), false); - - parser.add_argument("p", "point", "Test point", false); - - parser.add_argument>("t", "test", "Test vector", false); - parser.add_argument>("ts", "test-strings", "Test vector", false); - parser.on_complete(::run_grep); - try { - parser.handle_arguments(conventions); - } catch(std::exception const& e) { - std::cerr << "Error: " << e.what() << std::endl; - parser.display_help(conventions); - return -1; +template <> struct argument_parser::parsing_traits::parser_trait { + 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}; } - - - auto test = parser.get_optional>("test"); - if (test) { - for (auto const& item : test.value()) { - std::cout << item << std::endl; - } - } +}; - auto test_strings = parser.get_optional>("test-strings"); - if (test_strings) { - for (auto const& item : test_strings.value()) { - std::cout << item << std::endl; - } - } - return 0; +template <> struct argument_parser::parsing_traits::parser_trait { + static std::regex parse(const std::string &input) { + return std::regex(input); + } +}; + +template <> struct argument_parser::parsing_traits::parser_trait> { + static std::vector parse(const std::string &input) { + std::vector result; + std::stringstream ss{input}; + std::string item; + while (std::getline(ss, item, ',')) { + result.push_back(std::stoi(item)); + } + return result; + } +}; + +template <> struct argument_parser::parsing_traits::parser_trait> { + static std::vector parse(const std::string &input) { + std::vector result; + std::stringstream ss{input}; + std::string item; + while (std::getline(ss, item, ',')) { + result.push_back(item); + } + return result; + } +}; + +const std::initializer_list conventions = { + &argument_parser::conventions::gnu_argument_convention, + &argument_parser::conventions::gnu_equal_argument_convention, + &argument_parser::conventions::windows_argument_convention, + &argument_parser::conventions::windows_equal_argument_convention}; + +const auto echo = argument_parser::helpers::make_parametered_action( + [](std::string const &text) { std::cout << text << std::endl; }); + +const auto echo_point = argument_parser::helpers::make_parametered_action( + [](Point const &point) { std::cout << "Point(" << point.x << ", " << point.y << ")" << std::endl; }); + +const auto cat = argument_parser::helpers::make_parametered_action([](std::string const &file_name) { + std::ifstream file(file_name); + if (!file.is_open()) { + throw std::runtime_error("Could not open file"); + } + std::string line; + while (std::getline(file, line)) { + std::cout << line << std::endl; + } + + file.close(); +}); + +auto grep(argument_parser::base_parser const &parser, std::string const &filename, std::regex const &pattern) { + if (filename.empty()) { + std::cerr << "Missing filename" << std::endl; + parser.display_help(conventions); + exit(-1); + } + std::ifstream file(filename); + if (!file.is_open()) { + std::cerr << "Could not open file: \"" << filename << '"' << std::endl; + exit(-1); + } + + for (std::string line; std::getline(file, line);) { + if (std::regex_search(line, pattern)) { + std::cout << line << std::endl; + } + } + + file.close(); +} + +void run_grep(argument_parser::base_parser const &parser) { + auto filename = parser.get_optional("file"); + auto pattern = parser.get_optional("grep"); + + if (filename && pattern) { + grep(parser, filename.value(), pattern.value()); + } else if (filename) { + std::cerr << "Missing grep pattern" << std::endl; + parser.display_help(conventions); + exit(-1); + } else if (pattern) { + std::cerr << "Missing filename" << std::endl; + parser.display_help(conventions); + exit(-1); + } +} + +void run_store_point(argument_parser::base_parser const &parser) { + auto point = parser.get_optional("store-point"); + if (point) { + std::cout << "Point(" << point->x << ", " << point->y << ")" << std::endl; + } } int v2Examples() { - using namespace argument_parser::v2::flags; - argument_parser::v2::macos_parser parser; + using namespace argument_parser::v2::flags; + argument_parser::v2::parser parser; - parser.add_argument({ - { ShortArgument, "e" }, - { LongArgument, "echo" }, - { Action, echo } - }); + parser.add_argument( + {{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}, {HelpText, "echoes given variable"}}); - parser.add_argument({ - { ShortArgument, "ep" }, - { LongArgument, "echo-point" }, - { Action, echo_point } - }); + parser.add_argument( + {{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_point}, {HelpText, "echoes given point"}}); - parser.add_argument({ // stores string for f/file flag - { ShortArgument, "f" }, - { LongArgument, "file"}, - // if no action, falls to store operation with given type. - }); + parser.add_argument({ + // stores string for f/file flag + {ShortArgument, "f"}, + {LongArgument, "file"}, + {HelpText, "File to grep, required only if using grep"}, + // if no action, falls to store operation with given type. + }); - parser.add_argument({ // stores string for g/grep flag - { ShortArgument, "g" }, - { LongArgument, "grep" }, - // same as 'file' flag - }); + parser.add_argument({ + // stores string for g/grep flag + {ShortArgument, "g"}, + {LongArgument, "grep"}, + {HelpText, "Grep pattern, required only if using file"}, + // same as 'file' flag + }); - parser.add_argument({ - { ShortArgument, "c" }, - { LongArgument, "cat" }, - { Action, cat } - }); + parser.add_argument( + {{ShortArgument, "c"}, {LongArgument, "cat"}, {Action, cat}, {HelpText, "Prints the content of the file"}}); - parser.add_argument({ - // { ShortArgument, "sp" }, // now if ShortArgument or LongArgument is missing, it will use it for the other. - { LongArgument, "store-point" }, - { Required, true } // makes this flag required - }); + parser.add_argument({ + // { ShortArgument, "sp" }, // now if ShortArgument or LongArgument is missing, it will use it for the other. + {LongArgument, "store-point"}, + {Required, true} // makes this flag required + }); - parser.on_complete(::run_grep); + parser.on_complete(::run_grep); + parser.on_complete(::run_store_point); - parser.handle_arguments(conventions); - return 0; + parser.handle_arguments(conventions); + return 0; } int main() { - return v2Examples(); + try { + return v2Examples(); + } catch (std::exception const &e) { + std::cerr << "Error: " << e.what() << std::endl; + return -1; + } } \ No newline at end of file diff --git a/src/source/conventions/base_convention.cpp b/src/source/conventions/base_convention.cpp new file mode 100644 index 0000000..b8e483d --- /dev/null +++ b/src/source/conventions/base_convention.cpp @@ -0,0 +1,14 @@ +#include "base_convention.hpp" +#include + +namespace argument_parser::conventions::helpers { + std::string to_lower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + return s; + } + + std::string to_upper(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); }); + return s; + } +} // namespace argument_parser::conventions::helpers \ No newline at end of file diff --git a/src/source/conventions/implementations/gnu_argument_convention.cpp b/src/source/conventions/implementations/gnu_argument_convention.cpp new file mode 100644 index 0000000..85baa88 --- /dev/null +++ b/src/source/conventions/implementations/gnu_argument_convention.cpp @@ -0,0 +1,75 @@ +#include "gnu_argument_convention.hpp" +#include "base_convention.hpp" +#include + +bool starts_with(std::string const &s, std::string const &prefix) { + return s.rfind(prefix, 0) == 0; +} + +namespace argument_parser::conventions::implementations { + parsed_argument gnu_argument_convention::get_argument(std::string const &raw) const { + if (starts_with(raw, long_prec())) + return {argument_type::LONG, raw.substr(2)}; + else if (starts_with(raw, 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 gnu_argument_convention::extract_value(std::string const & /*raw*/) const { + throw std::runtime_error("No inline value in standard GNU convention."); + } + + bool gnu_argument_convention::requires_next_token() const { + return true; + } + + std::string gnu_argument_convention::name() const { + return "GNU-style long options"; + } + + std::string gnu_argument_convention::short_prec() const { + return "-"; + } + + std::string gnu_argument_convention::long_prec() const { + return "--"; + } +} // namespace argument_parser::conventions::implementations + +namespace argument_parser::conventions::implementations { + parsed_argument gnu_equal_argument_convention::get_argument(std::string const &raw) const { + auto pos = raw.find('='); + auto arg = pos != std::string::npos ? raw.substr(0, pos) : raw; + if (starts_with(arg, long_prec())) + return {argument_type::LONG, arg.substr(2)}; + else if (starts_with(arg, 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 gnu_equal_argument_convention::extract_value(std::string const &raw) const { + 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 gnu_equal_argument_convention::requires_next_token() const { + return false; + } + + std::string gnu_equal_argument_convention::name() const { + return "GNU-style long options (equal signed form)"; + } + + std::string gnu_equal_argument_convention::short_prec() const { + return "-"; + } + + std::string gnu_equal_argument_convention::long_prec() const { + return "--"; + } + +} // namespace argument_parser::conventions::implementations \ No newline at end of file diff --git a/src/source/conventions/implementations/windows_argument_convention.cpp b/src/source/conventions/implementations/windows_argument_convention.cpp new file mode 100644 index 0000000..91c9771 --- /dev/null +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -0,0 +1,104 @@ +#include "windows_argument_convention.hpp" +#include + +namespace argument_parser::conventions::implementations { + windows_argument_convention::windows_argument_convention(bool accept_dash) : accept_dash_(accept_dash) {} + + parsed_argument windows_argument_convention::get_argument(std::string const &raw) const { + if (raw.empty()) { + return {argument_type::ERROR, "Empty argument token."}; + } + const char c0 = raw[0]; + const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); + if (!ok_prefix) { + return {argument_type::ERROR, + accept_dash_ ? "Windows-style expects options to start with '/' (or '-' in compat mode)." + : "Windows-style expects options to start with '/'."}; + } + + if (raw.find_first_of("=:") != std::string::npos) { + return {argument_type::ERROR, + "Inline values are not allowed in this convention; provide the value in the next token."}; + } + + std::string name = helpers::to_lower(raw.substr(1)); + if (name.empty()) { + return {argument_type::ERROR, "Option name cannot be empty after '/'."}; + } + + return {argument_type::INTERCHANGABLE, std::move(name)}; + } + + std::string windows_argument_convention::extract_value(std::string const & /*raw*/) const { + throw std::runtime_error("No inline value; value must be provided in the next token."); + } + + bool windows_argument_convention::requires_next_token() const { + return true; + } + + std::string windows_argument_convention::name() const { + return "Windows style options (next-token values)"; + } + + std::string windows_argument_convention::short_prec() const { + return accept_dash_ ? "-" : "/"; + } + + std::string windows_argument_convention::long_prec() const { + return "/"; + } + +} // namespace argument_parser::conventions::implementations + +namespace argument_parser::conventions::implementations { + windows_kv_argument_convention::windows_kv_argument_convention(bool accept_dash) : accept_dash_(accept_dash) {} + + parsed_argument windows_kv_argument_convention::get_argument(std::string const &raw) const { + if (raw.empty()) { + return {argument_type::ERROR, "Empty argument token."}; + } + const char c0 = raw[0]; + const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); + if (!ok_prefix) { + return {argument_type::ERROR, + accept_dash_ ? "Windows-style expects options to start with '/' (or '-' in compat mode)." + : "Windows-style expects options to start with '/'."}; + } + + const std::size_t sep = raw.find_first_of("=:"); + if (sep == std::string::npos) { + return {argument_type::ERROR, + "Expected an inline value using '=' or ':' (e.g., /opt=value or /opt:value)."}; + } + if (sep == 1) { + return {argument_type::ERROR, "Option name cannot be empty before '=' or ':'."}; + } + + std::string name = helpers::to_lower(raw.substr(1, sep - 1)); + return {argument_type::INTERCHANGABLE, std::move(name)}; + } + + std::string windows_kv_argument_convention::extract_value(std::string const &raw) const { + const std::size_t sep = raw.find_first_of("=:"); + if (sep == std::string::npos || sep + 1 >= raw.size()) + throw std::runtime_error("Expected a value after '=' or ':'."); + return raw.substr(sep + 1); + } + + bool windows_kv_argument_convention::requires_next_token() const { + return false; + } + + std::string windows_kv_argument_convention::name() const { + return "Windows-style options (inline values via '=' or ':')"; + } + + std::string windows_kv_argument_convention::short_prec() const { + return accept_dash_ ? "-" : "/"; + } + + std::string windows_kv_argument_convention::long_prec() const { + return "/"; + } +} // namespace argument_parser::conventions::implementations \ No newline at end of file diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp new file mode 100644 index 0000000..b6e471b --- /dev/null +++ b/src/source/parser/argument_parser.cpp @@ -0,0 +1,208 @@ +#include "argument_parser.hpp" + +#include +#include + +bool contains(std::unordered_map const &map, std::string const &key) { + return map.find(key) != map.end(); +} + +namespace argument_parser { + argument::argument() + : id(0), name(), action(std::make_unique([]() {})), required(false), invoked(false) {} + + argument::argument(const argument &other) + : id(other.id), name(other.name), action(other.action->clone()), required(other.required), + invoked(other.invoked), help_text(other.help_text) {} + + argument &argument::operator=(const argument &other) { + if (this != &other) { + id = other.id; + name = other.name; + action = other.action->clone(); + required = other.required; + invoked = other.invoked; + help_text = other.help_text; + } + return *this; + } + + bool argument::expects_parameter() const { + return action->expects_parameter(); + } + + bool argument::is_required() const { + return required; + } + + bool argument::is_invoked() const { + return invoked; + } + + std::string argument::get_name() const { + return name; + } + + std::string argument::get_help_text() const { + return help_text; + } + + void argument::set_required(bool val) { + required = val; + } + + void argument::set_invoked(bool val) { + invoked = val; + } + + void argument::set_help_text(std::string const &text) { + help_text = text; + } + + void base_parser::on_complete(std::function const &handler) { + on_complete_events.emplace_back(handler); + } + + std::string + base_parser::build_help_text(std::initializer_list convention_types) const { + std::stringstream ss; + ss << "Usage: " << program_name << " [OPTIONS]...\n"; + + for (auto const &[id, arg] : argument_map) { + auto short_arg = reverse_short_arguments.at(id); + auto long_arg = reverse_long_arguments.at(id); + ss << "\t"; + for (auto const &convention : convention_types) { + ss << convention->short_prec() << short_arg << ", " << convention->long_prec() << long_arg << "\t"; + } + ss << arg.help_text << "\n"; + } + return ss.str(); + } + + argument &base_parser::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); + } else if (arg.first == conventions::argument_type::INTERCHANGABLE) { + auto long_pos = long_arguments.find(arg.second); + if (long_pos != long_arguments.end()) + return argument_map.at(long_pos->second); + 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 base_parser::handle_arguments(std::initializer_list convention_types) { + for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++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) { + error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second + << "\n"; + continue; + } + + try { + argument &corresponding_argument = get_argument(extracted); + if (corresponding_argument.expects_parameter()) { + 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); + corresponding_argument.action->invoke_with_parameter(value_raw); + } else { + corresponding_argument.action->invoke(); + } + + 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" + + error_stream.str()); + } + } + check_for_required_arguments(convention_types); + fire_on_complete_events(); + } + + void base_parser::display_help(std::initializer_list convention_types) const { + std::cout << build_help_text(convention_types); + } + + std::optional base_parser::find_argument_id(std::string const &arg) const { + auto long_pos = long_arguments.find(arg); + auto short_post = short_arguments.find(arg); + + if (long_pos != long_arguments.end()) + return long_pos->second; + if (short_post != short_arguments.end()) + return short_post->second; + return std::nullopt; + } + + void base_parser::assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const { + if (contains(short_arguments, short_arg) || contains(long_arguments, long_arg)) { + throw std::runtime_error("The key already exists!"); + } + } + + void base_parser::set_argument_status(bool is_required, std::string const &help_text, argument &arg) { + arg.set_required(is_required); + arg.set_help_text(help_text); + } + + void base_parser::place_argument(int id, argument const &arg, std::string const &short_arg, + std::string const &long_arg) { + 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 base_parser::check_for_required_arguments( + std::initializer_list convention_types) { + std::vector> required_args; + for (auto const &[key, arg] : argument_map) { + if (arg.is_required() && !arg.is_invoked()) { + required_args.emplace_back>( + {reverse_short_arguments[key], reverse_long_arguments[key]}); + } + } + + if (!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 base_parser::fire_on_complete_events() const { + for (auto const &event : on_complete_events) { + event(*this); + } + } +} // namespace argument_parser \ No newline at end of file diff --git a/src/source/parser/fake_parser.cpp b/src/source/parser/fake_parser.cpp new file mode 100644 index 0000000..3f7b287 --- /dev/null +++ b/src/source/parser/fake_parser.cpp @@ -0,0 +1,24 @@ +#include "fake_parser.hpp" + +namespace argument_parser { + fake_parser::fake_parser(std::string program_name, std::vector const &arguments) { + this->program_name = std::move(program_name); + parsed_arguments = arguments; + } + + fake_parser::fake_parser(std::string const &program_name, std::vector &&arguments) { + this->program_name = program_name; + parsed_arguments = std::move(arguments); + } + + fake_parser::fake_parser(std::string const &program_name, std::initializer_list const &arguments) + : fake_parser(program_name, std::vector(arguments)) {} + + void fake_parser::set_program_name(std::string const &program_name) { + this->program_name = program_name; + } + + void fake_parser::set_parsed_arguments(std::vector const &parsed_arguments) { + this->parsed_arguments = parsed_arguments; + } +} // namespace argument_parser \ No newline at end of file diff --git a/src/source/parser/parsing_traits/traits.cpp b/src/source/parser/parsing_traits/traits.cpp new file mode 100644 index 0000000..fb4ed89 --- /dev/null +++ b/src/source/parser/parsing_traits/traits.cpp @@ -0,0 +1,28 @@ +#include "traits.hpp" +#include + +namespace argument_parser::parsing_traits { + std::string parser_trait::parse(const std::string &input) { + return input; + } + + bool parser_trait::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); + } + + int parser_trait::parse(const std::string &input) { + return std::stoi(input); + } + + float parser_trait::parse(const std::string &input) { + return std::stof(input); + } + + double parser_trait::parse(const std::string &input) { + return std::stod(input); + } +} // namespace argument_parser::parsing_traits \ No newline at end of file diff --git a/src/source/parser/platform_parsers/linux_parser.cpp b/src/source/parser/platform_parsers/linux_parser.cpp new file mode 100644 index 0000000..2f7eff0 --- /dev/null +++ b/src/source/parser/platform_parsers/linux_parser.cpp @@ -0,0 +1,31 @@ +#ifdef __linux__ + +#include "linux_parser.hpp" + +#include +#include + +namespace argument_parser { + linux_parser::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); + } + } + + namespace v2 { + linux_parser::linux_parser() { + std::ifstream command_line_file{"/proc/self/cmdline"}; + std::string program_name; + std::getline(command_line_file, program_name, '\0'); + set_program_name(program_name); + + for (std::string line; std::getline(command_line_file, line, '\0');) { + parsed_arguments.emplace_back(line); + } + } + } // namespace v2 +} // namespace argument_parser + +#endif diff --git a/src/source/parser/platform_parsers/macos_parser.cpp b/src/source/parser/platform_parsers/macos_parser.cpp new file mode 100644 index 0000000..b869077 --- /dev/null +++ b/src/source/parser/platform_parsers/macos_parser.cpp @@ -0,0 +1,31 @@ +#ifdef __APPLE__ + +#include "macos_parser.hpp" + +namespace argument_parser { + macos_parser::macos_parser() { + const int argc = *_NSGetArgc(); + if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) { + program_name = (argv[0]); + for (int i = 1; i < argc; ++i) { + if (argv[i] != nullptr) + parsed_arguments.emplace_back(argv[i]); + } + } + } + + namespace v2 { + macos_parser::macos_parser() { + const int argc = *_NSGetArgc(); + if (char **argv = *_NSGetArgv(); argc > 0 && argv != nullptr && argv[0] != nullptr) { + set_program_name(argv[0]); + for (int i = 1; i < argc; ++i) { + if (argv[i] != nullptr) + ref_parsed_args().emplace_back(argv[i]); + } + } + } + } // namespace v2 +} // namespace argument_parser + +#endif \ No newline at end of file diff --git a/src/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp new file mode 100644 index 0000000..a44eca2 --- /dev/null +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -0,0 +1,102 @@ +#ifdef _WIN32 + +#include "windows_parser.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +struct local_free_deleter { + void operator()(void *ptr) const { + if (ptr == nullptr) { + return; + } + LocalFree(static_cast(ptr)); + } +}; + +std::string windows_error_message(DWORD error_code) { + LPSTR messageBuffer = nullptr; + + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&messageBuffer), 0, nullptr); + + if (size == 0 || !messageBuffer) { + return "Unknown Error ("s + std::to_string(error_code) + ")"; + } + + std::unique_ptr smartBuffer(messageBuffer); + std::string result(smartBuffer.get(), size); + result.erase(result.find_last_not_of(" \n\r\t") + 1); + + return result; +} + +std::string utf8_from_wstring(const std::wstring &w) { + if (w.empty()) + return {}; + + int needed = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w.data(), static_cast(w.size()), nullptr, 0, + nullptr, nullptr); + if (needed <= 0) { + throw std::runtime_error("WideCharToMultiByte sizing failed ("s + windows_error_message(::GetLastError()) + + ")"); + } + std::string out; + out.resize(needed); + int written = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w.data(), static_cast(w.size()), out.data(), + needed, nullptr, nullptr); + if (written <= 0) { + throw std::runtime_error( + "WideCharToMultiByte convert failed, Error("s + windows_error_message(::GetLastError()) + ")" + + " Size (Needed): " + std::to_string(needed) + " Size (Written): " + std::to_string(written) + + " Size (Allocated): " + std::to_string(out.size())); + } + return out; +} + +void parse_windows_arguments(std::vector &parsed_arguments, + std::function const &setProgramName) { + int argc_w; + std::unique_ptr argv_w(CommandLineToArgvW(GetCommandLineW(), &argc_w)); + if (argv_w == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed"); + } + + if (argc_w <= 0) { + return; + } + + setProgramName(utf8_from_wstring(argv_w[0])); + + for (int i = 1; i < argc_w; i++) { + try { + std::string arg = utf8_from_wstring(argv_w[i]); + parsed_arguments.emplace_back(arg); + } catch (std::runtime_error e) { + std::cerr << "Error: " << e.what() << std::endl; + } + } +} + +namespace argument_parser { + windows_parser::windows_parser() { + parse_windows_arguments(parsed_arguments, + [this](std::string const &program_name) { this->program_name = program_name; }); + } +} // namespace argument_parser + +namespace argument_parser::v2 { + windows_parser::windows_parser() { + parse_windows_arguments(ref_parsed_args(), + [this](std::string const &program_name) { this->set_program_name(program_name); }); + } +} // namespace argument_parser::v2 + +#endif \ No newline at end of file