From 7e2c09cbf98ca5346755fd07e35d31825a922c3c Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:05:13 +0400 Subject: [PATCH] feat: Introduce core argument parsing framework with platform-specific parsers, conventions, and type-based value parsing. --- .../gnu_argument_convention.hpp | 5 - .../windows_argument_convention.hpp | 7 - src/headers/parser/parsing_traits/traits.hpp | 59 ++-- .../parser/platform_headers/linux_parser.hpp | 20 +- .../platform_headers/windows_parser.hpp | 60 +--- src/main.cpp | 319 ++++++++---------- .../windows_argument_convention.cpp | 1 + src/source/parser/parsing_traits/traits.cpp | 28 ++ .../parser/platform_parsers/linux_parser.cpp | 18 + .../platform_parsers/windows_parser.cpp | 42 +++ 10 files changed, 258 insertions(+), 301 deletions(-) create mode 100644 src/source/parser/parsing_traits/traits.cpp create mode 100644 src/source/parser/platform_parsers/linux_parser.cpp create mode 100644 src/source/parser/platform_parsers/windows_parser.cpp diff --git a/src/headers/conventions/implementations/gnu_argument_convention.hpp b/src/headers/conventions/implementations/gnu_argument_convention.hpp index f48a133..986e6d0 100644 --- a/src/headers/conventions/implementations/gnu_argument_convention.hpp +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -10,15 +10,10 @@ namespace argument_parser::conventions::implementations { 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: diff --git a/src/headers/conventions/implementations/windows_argument_convention.hpp b/src/headers/conventions/implementations/windows_argument_convention.hpp index a8e7939..06e7a5d 100644 --- a/src/headers/conventions/implementations/windows_argument_convention.hpp +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -1,6 +1,5 @@ #pragma once #include "base_convention.hpp" -#include #ifndef WINDOWS_ARGUMENT_CONVENTION_HPP #define WINDOWS_ARGUMENT_CONVENTION_HPP @@ -13,17 +12,11 @@ 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; diff --git a/src/headers/parser/parsing_traits/traits.hpp b/src/headers/parser/parsing_traits/traits.hpp index 657cbeb..63189c3 100644 --- a/src/headers/parser/parsing_traits/traits.hpp +++ b/src/headers/parser/parsing_traits/traits.hpp @@ -3,51 +3,32 @@ #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 { + 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 std::string parse(const std::string &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 bool parse(const std::string &input); + }; - template <> - struct parser_trait { - static int parse(const std::string& input) { - return std::stoi(input); - } - }; + template <> struct parser_trait { + static int parse(const std::string &input); + }; - template <> - struct parser_trait { - static float parse(const std::string& input) { - return std::stof(input); - } - }; + template <> struct parser_trait { + static float parse(const std::string &input); + }; - template <> - struct parser_trait { - static double parse(const std::string& input) { - return std::stod(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 index 38e741d..7e06159 100644 --- a/src/headers/parser/platform_headers/linux_parser.hpp +++ b/src/headers/parser/platform_headers/linux_parser.hpp @@ -3,23 +3,15 @@ #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); - } - } - }; -} + class linux_parser : public base_parser { + public: + linux_parser(); + }; +} // namespace argument_parser -#endif +#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 index 12524f2..ad32c66 100644 --- a/src/headers/parser/platform_headers/windows_parser.hpp +++ b/src/headers/parser/platform_headers/windows_parser.hpp @@ -1,61 +1,11 @@ #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; - } -}; - -} + class windows_parser : public base_parser { + public: + windows_parser(); + }; +} // namespace argument_parser #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e4465b5..c164094 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,232 +2,189 @@ #include #define ALLOW_DASH_FOR_WINDOWS 0 -#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 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::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(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, +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 -}; + &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 = 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); + } -const auto echo_point = argument_parser::helpers::make_parametered_action([](Point const& point) { - std::cout << "Point(" << point.x << ", " << point.y << ")" << std::endl; -}); + for (std::string line; std::getline(file, line);) { + if (std::regex_search(line, pattern)) { + std::cout << line << 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(); + file.close(); } -void run_grep(argument_parser::base_parser const& parser) { - auto filename = parser.get_optional("file"); - auto pattern = parser.get_optional("grep"); +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); - } + 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{}; + 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("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("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; + 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; } - - - 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; + 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; } int v2Examples() { - using namespace argument_parser::v2::flags; - argument_parser::v2::macos_parser parser; + using namespace argument_parser::v2::flags; + argument_parser::v2::base_parser parser; - parser.add_argument({ - { ShortArgument, "e" }, - { LongArgument, "echo" }, - { Action, echo } - }); + parser.add_argument({{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}}); - parser.add_argument({ - { ShortArgument, "ep" }, - { LongArgument, "echo-point" }, - { Action, echo_point } - }); + parser.add_argument({{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_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"}, + // 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"}, + // same as 'file' flag + }); - parser.add_argument({ - { ShortArgument, "c" }, - { LongArgument, "cat" }, - { Action, cat } - }); + parser.add_argument({{ShortArgument, "c"}, {LongArgument, "cat"}, {Action, cat}}); - 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.handle_arguments(conventions); - return 0; + parser.handle_arguments(conventions); + return 0; } int main() { - return v2Examples(); + return v2Examples(); } \ 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 index 8f2a280..91c9771 100644 --- a/src/source/conventions/implementations/windows_argument_convention.cpp +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -1,4 +1,5 @@ #include "windows_argument_convention.hpp" +#include namespace argument_parser::conventions::implementations { windows_argument_convention::windows_argument_convention(bool accept_dash) : accept_dash_(accept_dash) {} 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..9463814 --- /dev/null +++ b/src/source/parser/platform_parsers/linux_parser.cpp @@ -0,0 +1,18 @@ +#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 argument_parser + +#endif 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..ea891b6 --- /dev/null +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -0,0 +1,42 @@ +#ifdef _WIN32 + +#include "windows_parser.hpp" + +#include +#include +#include + +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; +} + +namespace argument_parser { + windows_parser::windows_parser() { + LPWSTR *argv_w; + int argc_w; + argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); + if (argv_w == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed"); + } + for (int i = 0; i < argc_w; i++) { + std::string arg = utf8_from_wstring(argv_w[i]); + parsed_arguments.emplace_back(arg); + } + LocalFree(argv_w); + } +} // namespace argument_parser + +#endif \ No newline at end of file