From a1dc3c0149fadfe60aa1d676e195d5a30d9bbb4a Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 22:55:23 +0400 Subject: [PATCH 01/13] feat: Introduce a modular argument convention system for GNU and Windows styles, add .clang-format, and update CMake configuration to C++17. --- .clang-format | 12 ++ CMakeLists.txt | 14 +- compile_commands.json | 14 +- include/conventions/base_convention.hpp | 49 ------- .../gnu_argument_convention.hpp | 98 -------------- .../windows_argument_convention.hpp | 120 ------------------ {include => src/headers}/argparse | 6 +- src/headers/conventions/base_convention.hpp | 35 +++++ .../gnu_argument_convention.hpp | 53 ++++++++ .../windows_argument_convention.hpp | 62 +++++++++ .../headers}/parser/argument_parser.hpp | 0 .../headers}/parser/fake_parser.hpp | 0 {include => src/headers}/parser/parser_v2.hpp | 0 .../headers}/parser/parsing_traits/traits.hpp | 0 .../parser/platform_headers/linux_parser.hpp | 0 .../parser/platform_headers/macos_parser.hpp | 0 .../platform_headers/windows_parser.hpp | 0 src/source/conventions/base_convention.cpp | 14 ++ .../gnu_argument_convention.cpp | 75 +++++++++++ .../windows_argument_convention.cpp | 103 +++++++++++++++ 20 files changed, 371 insertions(+), 284 deletions(-) create mode 100644 .clang-format delete mode 100644 include/conventions/base_convention.hpp delete mode 100644 include/conventions/implementations/gnu_argument_convention.hpp delete mode 100644 include/conventions/implementations/windows_argument_convention.hpp rename {include => src/headers}/argparse (81%) create mode 100644 src/headers/conventions/base_convention.hpp create mode 100644 src/headers/conventions/implementations/gnu_argument_convention.hpp create mode 100644 src/headers/conventions/implementations/windows_argument_convention.hpp rename {include => src/headers}/parser/argument_parser.hpp (100%) rename {include => src/headers}/parser/fake_parser.hpp (100%) rename {include => src/headers}/parser/parser_v2.hpp (100%) rename {include => src/headers}/parser/parsing_traits/traits.hpp (100%) rename {include => src/headers}/parser/platform_headers/linux_parser.hpp (100%) rename {include => src/headers}/parser/platform_headers/macos_parser.hpp (100%) rename {include => src/headers}/parser/platform_headers/windows_parser.hpp (100%) create mode 100644 src/source/conventions/base_convention.cpp create mode 100644 src/source/conventions/implementations/gnu_argument_convention.cpp create mode 100644 src/source/conventions/implementations/windows_argument_convention.cpp 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..fc3a00b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,18 @@ 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 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/argparse b/src/headers/argparse similarity index 81% rename from include/argparse rename to src/headers/argparse index 3270284..12059b2 100644 --- a/include/argparse +++ b/src/headers/argparse @@ -6,17 +6,17 @@ #ifdef __linux__ #include namespace argument_parser { - using parser = linux_parser; + using parser = linux_parser; } #elif __APPLE__ #include namespace argument_parser { - using parser = macos_parser; + using parser = macos_parser; } #elif _WIN32 #include namespace argument_parser { - using parser = windows_parser; + 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..fc7d555 --- /dev/null +++ b/src/headers/conventions/base_convention.hpp @@ -0,0 +1,35 @@ +#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 { + static std::string to_lower(std::string s); + + static 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..f48a133 --- /dev/null +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -0,0 +1,53 @@ +#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..a8e7939 --- /dev/null +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -0,0 +1,62 @@ +#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); + + 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/include/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp similarity index 100% rename from include/parser/argument_parser.hpp rename to src/headers/parser/argument_parser.hpp diff --git a/include/parser/fake_parser.hpp b/src/headers/parser/fake_parser.hpp similarity index 100% rename from include/parser/fake_parser.hpp rename to src/headers/parser/fake_parser.hpp diff --git a/include/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp similarity index 100% rename from include/parser/parser_v2.hpp rename to src/headers/parser/parser_v2.hpp diff --git a/include/parser/parsing_traits/traits.hpp b/src/headers/parser/parsing_traits/traits.hpp similarity index 100% rename from include/parser/parsing_traits/traits.hpp rename to src/headers/parser/parsing_traits/traits.hpp diff --git a/include/parser/platform_headers/linux_parser.hpp b/src/headers/parser/platform_headers/linux_parser.hpp similarity index 100% rename from include/parser/platform_headers/linux_parser.hpp rename to src/headers/parser/platform_headers/linux_parser.hpp diff --git a/include/parser/platform_headers/macos_parser.hpp b/src/headers/parser/platform_headers/macos_parser.hpp similarity index 100% rename from include/parser/platform_headers/macos_parser.hpp rename to src/headers/parser/platform_headers/macos_parser.hpp diff --git a/include/parser/platform_headers/windows_parser.hpp b/src/headers/parser/platform_headers/windows_parser.hpp similarity index 100% rename from include/parser/platform_headers/windows_parser.hpp rename to src/headers/parser/platform_headers/windows_parser.hpp diff --git a/src/source/conventions/base_convention.cpp b/src/source/conventions/base_convention.cpp new file mode 100644 index 0000000..debbfda --- /dev/null +++ b/src/source/conventions/base_convention.cpp @@ -0,0 +1,14 @@ +#include "base_convention.hpp" +#include + +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; + } +} // 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..8f2a280 --- /dev/null +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -0,0 +1,103 @@ +#include "windows_argument_convention.hpp" + +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 From 7e2c09cbf98ca5346755fd07e35d31825a922c3c Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:05:13 +0400 Subject: [PATCH 02/13] 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 From c7304aa36efb23124809bf1155ec63f571215661 Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:32:18 +0400 Subject: [PATCH 03/13] chore: separate definitions from declarations for argument parser where possible. --- src/headers/parser/argument_parser.hpp | 485 +++++++----------- .../parser/platform_headers/macos_parser.hpp | 38 +- src/main.cpp | 5 +- src/source/parser/argument_parser.cpp | 205 ++++++++ .../parser/platform_parsers/macos_parser.cpp | 31 ++ 5 files changed, 428 insertions(+), 336 deletions(-) create mode 100644 src/source/parser/argument_parser.cpp create mode 100644 src/source/parser/platform_parsers/macos_parser.cpp diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp index c78972a..e752aed 100644 --- a/src/headers/parser/argument_parser.hpp +++ b/src/headers/parser/argument_parser.hpp @@ -5,361 +5,232 @@ #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP -#include -#include +#include #include +#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; - }; + 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); - } + 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); - } + [[nodiscard]] bool expects_parameter() const override { + return true; + } - private: - std::function handler; - }; + void invoke() const override { + throw std::runtime_error("Parametered action requires a parameter"); + } - class non_parametered_action : public action_base { - public: - explicit non_parametered_action(std::function const& handler) : handler(handler) {} - - void invoke() const override { - handler(); - } + void invoke_with_parameter(const std::string ¶m) const override { + T parsed_value = parsing_traits::parser_trait::parse(param); + invoke(parsed_value); + } - [[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); - } + [[nodiscard]] std::unique_ptr clone() const override { + return std::make_unique>(handler); + } - private: - std::function handler; - }; + private: + std::function handler; + }; - class base_parser; + class non_parametered_action : public action_base { + public: + explicit non_parametered_action(std::function const &handler) : handler(handler) {} - class argument { - public: - argument() : id(0), name(), action(std::make_unique([](){})), required(false), invoked(false) {} + void invoke() const override { + handler(); + } - template - argument(const int id, std::string name, ActionType const& action) - : id(id), name(std::move(name)), action(action.clone()), required(false), invoked(false) {} + [[nodiscard]] bool expects_parameter() const override { + return 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) {} + void invoke_with_parameter(const std::string ¶m) const override { + invoke(); + } - 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; - } + [[nodiscard]] std::unique_ptr clone() const override { + return std::make_unique(handler); + } - argument(argument&& other) noexcept = default; - argument& operator=(argument&& other) noexcept = default; + private: + std::function handler; + }; - [[nodiscard]] bool is_required() const { return required; } - [[nodiscard]] std::string get_name() const { return name; } - [[nodiscard]] bool is_invoked() const { return invoked; } + class base_parser; - [[nodiscard]] bool expects_parameter() const { - return action->expects_parameter(); - } + class argument { + public: + argument(); - private: - void set_required(bool val) { required = val; } - void set_invoked(bool val) { invoked = val; } - void set_help_text(std::string const& text) { help_text = text; } + template + argument(const int id, std::string name, ActionType const &action) + : id(id), name(std::move(name)), action(action.clone()), required(false), invoked(false) {} - friend class base_parser; + argument(const argument &other); + argument &operator=(const argument &other); + argument(argument &&other) noexcept = default; + argument &operator=(argument &&other) noexcept = default; - int id; - std::string name; - std::unique_ptr action; - bool required; - bool invoked; - std::string help_text; - }; + [[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; - namespace helpers { - template - static parametered_action make_parametered_action(std::function const& function) { - return parametered_action(function); - } + private: + void set_required(bool val); + void set_invoked(bool val); + void set_help_text(std::string const &text); - static non_parametered_action make_non_parametered_action(std::function const& function) { - return non_parametered_action(function); - } - } + friend class base_parser; - 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); - } + int id; + std::string name; + std::unique_ptr action; + bool required; + bool invoked; + std::string help_text; + }; - 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); - } + namespace helpers { + template + static parametered_action make_parametered_action(std::function const &function) { + return parametered_action(function); + } - 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); - } + static non_parametered_action make_non_parametered_action(std::function const &function) { + return non_parametered_action(function); + } + } // namespace helpers - 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); - } + 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); + } - void on_complete(std::function const& action) { - on_complete_events.emplace_back(action); - } + 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); + } - 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; - } + 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); + } - [[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(); - } + 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); + } - argument& get_argument(conventions::parsed_argument const& arg) { + void on_complete(std::function const &action); - 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); - } + 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::optional find_argument_id(std::string const& arg) const { - auto long_pos = long_arguments.find(arg); - auto short_post = short_arguments.find(arg); + [[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; - 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; + protected: + base_parser() = default; - 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; - } + std::string program_name; + std::vector parsed_arguments; - 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(); - } + 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); - corresponding_argument.set_invoked(true); - arg_correctly_handled = true; - break; // Convention succeeded, move to the next argument token + 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); + } - } catch (const std::runtime_error& e) { - error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; - } - } + 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); + } + } - 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 check_for_required_arguments(std::initializer_list convention_types); + void fire_on_complete_events() const; - void display_help(std::initializer_list convention_types) const { - std::cout << build_help_text(convention_types); - } + inline static std::atomic_int id_counter = 0; - protected: - base_parser() = default; + 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::string program_name; - std::vector parsed_arguments; + std::list> on_complete_events; - 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; - }; -} + 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/platform_headers/macos_parser.hpp b/src/headers/parser/platform_headers/macos_parser.hpp index da55102..f63c2f5 100644 --- a/src/headers/parser/platform_headers/macos_parser.hpp +++ b/src/headers/parser/platform_headers/macos_parser.hpp @@ -9,34 +9,18 @@ #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]); - } - } - } - }; + class macos_parser : public base_parser { + public: + macos_parser(); + }; - 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]); - } - } - } - }; - } -} + 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/main.cpp b/src/main.cpp index c164094..a33bd5c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,3 @@ -#include "macos_parser.hpp" #include #define ALLOW_DASH_FOR_WINDOWS 0 @@ -8,8 +7,10 @@ #include #include #include +#include #include + struct Point { int x, y; }; @@ -35,7 +36,7 @@ template <> struct argument_parser::parsing_traits::parser_trait { template <> struct argument_parser::parsing_traits::parser_trait> { static std::vector parse(const std::string &input) { std::vector result; - std::stringstream ss(input); + std::stringstream ss{input}; std::string item; while (std::getline(ss, item, ',')) { result.push_back(std::stoi(item)); diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp new file mode 100644 index 0000000..dcbbf61 --- /dev/null +++ b/src/source/parser/argument_parser.cpp @@ -0,0 +1,205 @@ +#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::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() and not arg.is_invoked()) { + required_args.emplace_back>( + {reverse_short_arguments[key], reverse_long_arguments[key]}); + } + } + + 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 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/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 From 609a730e8faa7d9bcf9ec0d9afa8c16f6f15fef2 Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:35:21 +0400 Subject: [PATCH 04/13] chore: separate definition from decleration for the fake_parser where possible. --- src/headers/parser/fake_parser.hpp | 35 +++++++++--------------------- src/source/parser/fake_parser.cpp | 24 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 src/source/parser/fake_parser.cpp diff --git a/src/headers/parser/fake_parser.hpp b/src/headers/parser/fake_parser.hpp index cba9de2..e4cb306 100644 --- a/src/headers/parser/fake_parser.hpp +++ b/src/headers/parser/fake_parser.hpp @@ -8,31 +8,16 @@ #include namespace argument_parser { - class fake_parser : public base_parser { - public: - fake_parser() = default; + 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); - 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; - } - }; -} + 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/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 From 6ac10dda3385d9e24ac94501ec265a649a02fe34 Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:38:08 +0400 Subject: [PATCH 05/13] chore: clean up c++20/23 features to move to 17. --- src/headers/parser/parser_v2.hpp | 402 +++++++++++++++---------------- 1 file changed, 198 insertions(+), 204 deletions(-) diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index c0d7b6a..99fcef1 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -1,10 +1,9 @@ -#pragma once +#pragma once #include +#include #include -#include #include #include -#include #include #include #include @@ -15,235 +14,230 @@ #include namespace argument_parser::v2 { - namespace internal { - static inline fake_parser fake_parser{}; - } + namespace internal { + static inline fake_parser fake_parser{}; + } - enum class add_argument_flags { - ShortArgument, - LongArgument, - HelpText, - Action, - Required - }; + 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 { + 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; + 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 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 - }}; + 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; + 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::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; - } + 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; + 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."); + } + } - for (auto& [k, v]: pairs) { - args[k] = v; - } + template void add_argument(std::initializer_list> const &pairs) { + std::unordered_map> args; - add_argument(args); - } + for (auto &[k, v] : pairs) { + args[k] = v; + } - void add_argument(std::initializer_list const& pairs) { - std::unordered_map args; - - for (auto& [k, v] : pairs) { - args[k] = v; - } + add_argument(args); + } - add_argument(args); - } + void add_argument(std::initializer_list const &pairs) { + std::unordered_map args; - void add_argument(std::unordered_map const& argument_pairs) { - std::unordered_map found_params {{ - extended_add_argument_flags::IsTyped, false - }}; + for (auto &[k, v] : pairs) { + args[k] = v; + } - std::string short_arg, long_arg, help_text; - std::unique_ptr action; - bool required = false; + add_argument(args); + } - 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; - } + void add_argument(std::unordered_map const &argument_pairs) { + std::unordered_map found_params{ + {extended_add_argument_flags::IsTyped, false}}; - if (argument_pairs.contains(add_argument_flags::Required) && get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { - required = true; - } + std::string short_arg, long_arg, help_text; + std::unique_ptr action; + bool required = false; - 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 (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; + } - 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)))); - } - } + if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && + get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { + required = true; + } - argument_parser::base_parser& to_v1() { - return *this; - } + 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?"); + } - void handle_arguments(std::initializer_list convention_types) { - base::handle_arguments(convention_types); - } + 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)))); + } + } - template - std::optional get_optional(std::string const& arg) { - return base::get_optional(arg); - } + argument_parser::base_parser &to_v1() { + return *this; + } - void on_complete(std::function const& action) { - base::on_complete(action); - } + void handle_arguments(std::initializer_list convention_types) { + base::handle_arguments(convention_types); + } - protected: - void set_program_name(std::string p) { - base::program_name = std::move(p); - } + template std::optional get_optional(std::string const &arg) { + return base::get_optional(arg); + } - std::vector& ref_parsed_args() { - return base::parsed_arguments; - } + void on_complete(std::function const &action) { + base::on_complete(action); + } - private: - using base = argument_parser::base_parser; - enum class extended_add_argument_flags { - ShortArgument, - LongArgument, - Action, - IsTyped - }; + protected: + void set_program_name(std::string p) { + base::program_name = std::move(p); + } - enum class candidate_type { - store_boolean, - store_other, - typed_action, - non_typed_action, - unknown - }; + std::vector &ref_parsed_args() { + return base::parsed_arguments; + } - 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; - } + private: + using base = argument_parser::base_parser; + enum class extended_add_argument_flags { ShortArgument, LongArgument, Action, IsTyped }; - 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; - } + enum class candidate_type { store_boolean, store_other, typed_action, non_typed_action, unknown }; - 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 + 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; + } - 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 + 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 From 9e646622db7a54ae7fea7f07d5a20022661c4aa4 Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:43:33 +0400 Subject: [PATCH 06/13] chore: dedup the add_argument implementation. also remove the cpp 20/23 features from parser v2 --- src/headers/parser/parser_v2.hpp | 182 +++++++++++++------------------ 1 file changed, 75 insertions(+), 107 deletions(-) diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 99fcef1..3f64ca0 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -38,58 +38,7 @@ namespace argument_parser::v2 { 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."); - } + add_argument_impl>(argument_pairs); } template void add_argument(std::initializer_list> const &pairs) { @@ -113,61 +62,7 @@ namespace argument_parser::v2 { } 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.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?"); - } - - 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)))); - } + add_argument_impl(argument_pairs); } argument_parser::base_parser &to_v1() { @@ -196,6 +91,79 @@ namespace argument_parser::v2 { } 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 }; From 3552d34dacb89f9556c24f119ee7b3031de28fbe Mon Sep 17 00:00:00 2001 From: killua Date: Sun, 15 Mar 2026 23:55:06 +0400 Subject: [PATCH 07/13] chore: add missing definitions to the files. remove static qualifier from the headers. add help text to the actions. --- CMakeLists.txt | 4 +++- src/headers/conventions/base_convention.hpp | 5 ++--- src/main.cpp | 25 ++++++++++++++++----- src/source/conventions/base_convention.cpp | 4 ++-- src/source/parser/argument_parser.cpp | 8 +++++-- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc3a00b..a1cf353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,4 +16,6 @@ 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/src/headers/conventions/base_convention.hpp b/src/headers/conventions/base_convention.hpp index fc7d555..96dc59e 100644 --- a/src/headers/conventions/base_convention.hpp +++ b/src/headers/conventions/base_convention.hpp @@ -27,9 +27,8 @@ namespace argument_parser::conventions { } // namespace argument_parser::conventions namespace argument_parser::conventions::helpers { - static std::string to_lower(std::string s); - - static std::string to_upper(std::string s); + 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/main.cpp b/src/main.cpp index a33bd5c..51722d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,6 @@ #define ALLOW_DASH_FOR_WINDOWS 0 #include -#include #include #include #include @@ -10,7 +9,6 @@ #include #include - struct Point { int x, y; }; @@ -45,6 +43,18 @@ template <> struct argument_parser::parsing_traits::parser_trait 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, @@ -154,14 +164,17 @@ int v2Examples() { 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}, {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"}, + {HelpText, "File to grep, required only if using grep"}, // if no action, falls to store operation with given type. }); @@ -169,10 +182,12 @@ int v2Examples() { // 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. diff --git a/src/source/conventions/base_convention.cpp b/src/source/conventions/base_convention.cpp index debbfda..b8e483d 100644 --- a/src/source/conventions/base_convention.cpp +++ b/src/source/conventions/base_convention.cpp @@ -2,12 +2,12 @@ #include namespace argument_parser::conventions::helpers { - static std::string to_lower(std::string s) { + 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::string to_upper(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); }); return s; } diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index dcbbf61..c4eaa73 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -27,6 +27,10 @@ namespace argument_parser { return *this; } + bool argument::expects_parameter() const { + return action->expects_parameter(); + } + bool argument::is_required() const { return required; } @@ -181,13 +185,13 @@ namespace argument_parser { std::initializer_list convention_types) { std::vector> required_args; for (auto const &[key, arg] : argument_map) { - if (arg.is_required() and not arg.is_invoked()) { + if (arg.is_required() && !arg.is_invoked()) { required_args.emplace_back>( {reverse_short_arguments[key], reverse_long_arguments[key]}); } } - if (not required_args.empty()) { + if (!required_args.empty()) { std::cerr << "These arguments were expected but not provided: "; for (auto const &[s, l] : required_args) { std::cerr << "[-" << s << ", --" << l << "] "; From 8befc60016f574d3cfac3afd718aceca6f60ac6a Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 16:50:42 +0400 Subject: [PATCH 08/13] chore: introduce Windows-specific argument parsing to v2 namespace. --- .../platform_headers/windows_parser.hpp | 8 ++++ .../platform_parsers/windows_parser.cpp | 41 ++++++++++++++----- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/headers/parser/platform_headers/windows_parser.hpp b/src/headers/parser/platform_headers/windows_parser.hpp index ad32c66..17ad798 100644 --- a/src/headers/parser/platform_headers/windows_parser.hpp +++ b/src/headers/parser/platform_headers/windows_parser.hpp @@ -1,11 +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/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp index ea891b6..2ea087c 100644 --- a/src/source/parser/platform_parsers/windows_parser.cpp +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -3,6 +3,7 @@ #include "windows_parser.hpp" #include +#include #include #include @@ -23,20 +24,38 @@ std::string utf8_from_wstring(const std::wstring &w) { return out; } +struct local_free_deleter { + void operator()(void *ptr) const { + if (ptr == nullptr) { + return; + } + LocalFree(static_cast(ptr)); + } +}; + +void parse_windows_arguments(std::vector &parsed_arguments) { + int argc_w; + std::unique_ptr 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); + } +} + 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); + parse_windows_arguments(parsed_arguments); } } // namespace argument_parser +namespace argument_parser::v2 { + windows_parser::windows_parser() { + parse_windows_arguments(ref_parsed_args()); + } +} // namespace argument_parser::v2 + #endif \ No newline at end of file From 182974d61a41ee82a0559d454a2c2d7858f443a3 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 17:41:45 +0400 Subject: [PATCH 09/13] fix: windows parsing --- src/headers/argparse | 12 +++ src/headers/parser/argument_parser.hpp | 4 +- src/headers/parser/parser_v2.hpp | 8 +- src/main.cpp | 17 +++- src/source/parser/argument_parser.cpp | 1 - .../platform_parsers/windows_parser.cpp | 84 ++++++++++++++----- 6 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/headers/argparse b/src/headers/argparse index 12059b2..4396e66 100644 --- a/src/headers/argparse +++ b/src/headers/argparse @@ -8,16 +8,28 @@ namespace argument_parser { using parser = linux_parser; } + +namespace argument_parser::v2 { + using parser = linux_parser; +} #elif __APPLE__ #include namespace argument_parser { using parser = macos_parser; } + +namespace argument_parser::v2 { + using parser = macos_parser; +} #elif _WIN32 #include namespace argument_parser { using parser = windows_parser; } + +namespace argument_parser::v2 { + using parser = windows_parser; +} #else #error "Unsupported platform" #endif diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp index e752aed..2061cc4 100644 --- a/src/headers/parser/argument_parser.hpp +++ b/src/headers/parser/argument_parser.hpp @@ -4,7 +4,6 @@ #include #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP - #include #include #include @@ -158,8 +157,9 @@ namespace argument_parser { 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()) + if (value != stored_arguments.end() && value->second.has_value()) { return std::any_cast(value->second); + } } return std::nullopt; } diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 3f64ca0..0b94fa5 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -38,7 +38,7 @@ namespace argument_parser::v2 { template void add_argument(std::unordered_map> const &argument_pairs) { - add_argument_impl>(argument_pairs); + add_argument_impl, T>(argument_pairs); } template void add_argument(std::initializer_list> const &pairs) { @@ -62,7 +62,7 @@ namespace argument_parser::v2 { } void add_argument(std::unordered_map const &argument_pairs) { - add_argument_impl(argument_pairs); + add_argument_impl(argument_pairs); } argument_parser::base_parser &to_v1() { @@ -91,7 +91,7 @@ namespace argument_parser::v2 { } private: - template + template void add_argument_impl(ArgsMap const &argument_pairs) { std::unordered_map found_params{ {extended_add_argument_flags::IsTyped, IsTyped}}; @@ -142,7 +142,7 @@ namespace argument_parser::v2 { required); break; case candidate_type::store_other: - base::add_argument(short_arg, long_arg, help_text, required); + base::add_argument(short_arg, long_arg, help_text, required); break; default: throw std::runtime_error("Could not match the arguments against any overload."); diff --git a/src/main.cpp b/src/main.cpp index 51722d8..bdd829c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -160,9 +160,16 @@ int v1Examples() { return 0; } +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::base_parser parser; + argument_parser::v2::parser parser; parser.add_argument( {{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}, {HelpText, "echoes given variable"}}); @@ -196,11 +203,17 @@ int v2Examples() { }); parser.on_complete(::run_grep); + parser.on_complete(::run_store_point); 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/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index c4eaa73..b6e471b 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -115,7 +115,6 @@ namespace argument_parser { 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); diff --git a/src/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp index 2ea087c..fe865e9 100644 --- a/src/source/parser/platform_parsers/windows_parser.cpp +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -3,26 +3,13 @@ #include "windows_parser.hpp" #include +#include #include #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; -} +using namespace std::string_literals; struct local_free_deleter { void operator()(void *ptr) const { @@ -33,28 +20,81 @@ struct local_free_deleter { } }; -void parse_windows_arguments(std::vector &parsed_arguments) { +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.c_str(), -1, 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 - 1); + int written = + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w.c_str(), -1, 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"); } - for (int i = 0; i < argc_w; i++) { - std::string arg = utf8_from_wstring(argv_w[i]); - parsed_arguments.emplace_back(arg); + 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); + 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()); + parse_windows_arguments(ref_parsed_args(), + [this](std::string const &program_name) { this->set_program_name(program_name); }); } } // namespace argument_parser::v2 From a1ada5a792f6446b5e80b64e67f1b3c8fd5eb647 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 17:43:20 +0400 Subject: [PATCH 10/13] fix: overwrite on null terminator --- src/source/parser/platform_parsers/windows_parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp index fe865e9..c8cbf83 100644 --- a/src/source/parser/platform_parsers/windows_parser.cpp +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -48,9 +48,9 @@ std::string utf8_from_wstring(const std::wstring &w) { ")"); } std::string out; - out.resize(needed - 1); + out.resize(needed); int written = - ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w.c_str(), -1, out.data(), needed, nullptr, nullptr); + ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, w.c_str(), -1, out.data(), out.size(), nullptr, nullptr); if (written <= 0) { throw std::runtime_error( "WideCharToMultiByte convert failed, Error("s + windows_error_message(::GetLastError()) + ")" + From fed2eb998c2e443d9b865c1f24b40591b9427fe4 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 17:52:54 +0400 Subject: [PATCH 11/13] chore: add v2 version of the linux parser --- .../parser/platform_headers/linux_parser.hpp | 8 ++++++++ src/source/parser/platform_parsers/linux_parser.cpp | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/headers/parser/platform_headers/linux_parser.hpp b/src/headers/parser/platform_headers/linux_parser.hpp index 7e06159..ff16822 100644 --- a/src/headers/parser/platform_headers/linux_parser.hpp +++ b/src/headers/parser/platform_headers/linux_parser.hpp @@ -5,12 +5,20 @@ #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 diff --git a/src/source/parser/platform_parsers/linux_parser.cpp b/src/source/parser/platform_parsers/linux_parser.cpp index 9463814..2f7eff0 100644 --- a/src/source/parser/platform_parsers/linux_parser.cpp +++ b/src/source/parser/platform_parsers/linux_parser.cpp @@ -13,6 +13,19 @@ namespace argument_parser { 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 From d8a515ea37c19f4d40bcd85c829f9b843353ac19 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 17:53:31 +0400 Subject: [PATCH 12/13] chore: remove v1 examples --- src/main.cpp | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index bdd829c..67727e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,48 +118,6 @@ void run_grep(argument_parser::base_parser const &parser) { } } -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; - } - - 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; -} - void run_store_point(argument_parser::base_parser const &parser) { auto point = parser.get_optional("store-point"); if (point) { From a12840b2412baf651413ff46a0ec39b5527db3b1 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 18:04:40 +0400 Subject: [PATCH 13/13] fix: null terminator --- src/source/parser/platform_parsers/windows_parser.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp index c8cbf83..a44eca2 100644 --- a/src/source/parser/platform_parsers/windows_parser.cpp +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -42,15 +42,16 @@ 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); + 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.c_str(), -1, out.data(), out.size(), nullptr, nullptr); + 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()) + ")" +