From 3a8e919ad14536c6e98d742708f66131d46ee252 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 18:43:40 +0400 Subject: [PATCH 1/3] chore: set up library compilation --- CMakeLists.txt | 51 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a1cf353..d8b6ed5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,54 @@ cmake_minimum_required(VERSION 3.15) -project(argument_parser) +project(argument_parser VERSION 1.0) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + 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(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) - 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 +add_library(argument_parser ${SRC_FILES}) + +include(GNUInstallDirs) + +target_include_directories(argument_parser PUBLIC + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ +) + +install(TARGETS argument_parser + EXPORT argument_parserTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(DIRECTORY src/headers/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(EXPORT argument_parserTargets + FILE argument_parserTargets.cmake + NAMESPACE argument_parser:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/argument_parser +) + +add_executable(test src/main.cpp) +target_link_libraries(test PRIVATE argument_parser) \ No newline at end of file From a8b7078949fae350623c8d7daaf72f9c00821cf9 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 18:45:16 +0400 Subject: [PATCH 2/3] feat: introduce help. auto add help through parser generation like another action. --- src/headers/parser/argument_parser.hpp | 77 ++++++++++++++++++- src/headers/parser/parser_v2.hpp | 8 +- .../platform_headers/windows_parser.hpp | 1 + src/source/parser/argument_parser.cpp | 19 +++++ .../platform_parsers/windows_parser.cpp | 11 +++ 5 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp index 2061cc4..eb55d73 100644 --- a/src/headers/parser/argument_parser.hpp +++ b/src/headers/parser/argument_parser.hpp @@ -1,23 +1,72 @@ #pragma once -#include -#include -#include #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP + #include #include #include #include #include +#include #include +#include #include #include +#include #include +#include #include #include #include + namespace argument_parser { + namespace internal::atomic { + template class copyable_atomic { + public: + copyable_atomic() : value(std::make_shared>()) {} + copyable_atomic(T desired) : value(std::make_shared>(desired)) {} + + copyable_atomic(const copyable_atomic &other) : value(other.value) {} + copyable_atomic &operator=(const copyable_atomic &other) { + if (this != &other) { + value = other.value; + } + return *this; + } + + copyable_atomic(copyable_atomic &&other) noexcept = default; + copyable_atomic &operator=(copyable_atomic &&other) noexcept = default; + ~copyable_atomic() = default; + + T operator=(T desired) noexcept { + store(desired); + return desired; + } + + operator T() const noexcept { + return load(); + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + if (value) { + value->store(desired, order); + } + } + + T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { + return value ? value->load(order) : T{}; + } + + T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept { + return value ? value->exchange(desired, order) : T{}; + } + + private: + std::shared_ptr> value; + }; + } // namespace internal::atomic + class action_base { public: virtual ~action_base() = default; @@ -127,6 +176,13 @@ namespace argument_parser { } } // namespace helpers + /** + * @brief Base class for parsing arguments from the command line. + * + * Note: This class and its methods are NOT thread-safe. + * It must be instantiated and used from a single thread (typically the main thread), + * as operations such as argument processing and checking rely on thread-local or instance-specific state. + */ class base_parser { public: template @@ -177,6 +233,18 @@ namespace argument_parser { std::string program_name; std::vector parsed_arguments; + void reset_current_conventions() { + _current_conventions = {}; + } + + void current_conventions(std::initializer_list convention_types) { + _current_conventions = convention_types; + } + + [[nodiscard]] std::initializer_list current_conventions() const { + return _current_conventions; + } + 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); @@ -224,6 +292,9 @@ namespace argument_parser { std::unordered_map long_arguments; std::unordered_map reverse_long_arguments; + std::initializer_list _current_conventions; + internal::atomic::copyable_atomic creation_thread_id = std::this_thread::get_id(); + std::list> on_complete_events; friend class linux_parser; diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 0b94fa5..3fdbaac 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -77,9 +77,8 @@ namespace argument_parser::v2 { return base::get_optional(arg); } - void on_complete(std::function const &action) { - base::on_complete(action); - } + using argument_parser::base_parser::display_help; + using argument_parser::base_parser::on_complete; protected: void set_program_name(std::string p) { @@ -90,6 +89,9 @@ namespace argument_parser::v2 { return base::parsed_arguments; } + using argument_parser::base_parser::current_conventions; + using argument_parser::base_parser::reset_current_conventions; + private: template void add_argument_impl(ArgsMap const &argument_pairs) { diff --git a/src/headers/parser/platform_headers/windows_parser.hpp b/src/headers/parser/platform_headers/windows_parser.hpp index 17ad798..d180676 100644 --- a/src/headers/parser/platform_headers/windows_parser.hpp +++ b/src/headers/parser/platform_headers/windows_parser.hpp @@ -13,6 +13,7 @@ namespace argument_parser { class windows_parser : public v2::base_parser { public: windows_parser(); + using base_parser::display_help; }; } // namespace v2 } // namespace argument_parser diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index b6e471b..5e65982 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -2,6 +2,18 @@ #include #include +#include + +class deferred_exec { +public: + deferred_exec(std::function const &func) : func(func) {} + ~deferred_exec() { + func(); + } + +private: + std::function func; +}; bool contains(std::unordered_map const &map, std::string const &key) { return map.find(key) != map.end(); @@ -101,6 +113,13 @@ namespace argument_parser { } void base_parser::handle_arguments(std::initializer_list convention_types) { + if (std::this_thread::get_id() != this->creation_thread_id.load()) { + throw std::runtime_error("handle_arguments must be called from the main thread"); + } + + deferred_exec reset_current_conventions([this]() { this->reset_current_conventions(); }); + this->current_conventions(convention_types); + for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) { std::stringstream error_stream; bool arg_correctly_handled = false; diff --git a/src/source/parser/platform_parsers/windows_parser.cpp b/src/source/parser/platform_parsers/windows_parser.cpp index a44eca2..4b0003c 100644 --- a/src/source/parser/platform_parsers/windows_parser.cpp +++ b/src/source/parser/platform_parsers/windows_parser.cpp @@ -1,6 +1,9 @@ + #ifdef _WIN32 #include "windows_parser.hpp" +#include "argument_parser.hpp" +#include "parser_v2.hpp" #include #include @@ -96,6 +99,14 @@ namespace argument_parser::v2 { windows_parser::windows_parser() { parse_windows_arguments(ref_parsed_args(), [this](std::string const &program_name) { this->set_program_name(program_name); }); + + add_argument({{flags::ShortArgument, "h"}, + {flags::LongArgument, "help"}, + {flags::Action, helpers::make_non_parametered_action([this]() { + this->display_help(this->current_conventions()); + std::exit(0); + })}, + {flags::HelpText, "Prints this help text."}}); } } // namespace argument_parser::v2 From ddb2d0bf106bf2035efd897f0a58fb985cd8cb4a Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 18:46:47 +0400 Subject: [PATCH 3/3] feat: add help command to linux and macos parsers. --- src/headers/parser/platform_headers/linux_parser.hpp | 1 + src/headers/parser/platform_headers/macos_parser.hpp | 1 + src/source/parser/platform_parsers/linux_parser.cpp | 8 ++++++++ src/source/parser/platform_parsers/macos_parser.cpp | 8 ++++++++ 4 files changed, 18 insertions(+) diff --git a/src/headers/parser/platform_headers/linux_parser.hpp b/src/headers/parser/platform_headers/linux_parser.hpp index ff16822..9d9e4cd 100644 --- a/src/headers/parser/platform_headers/linux_parser.hpp +++ b/src/headers/parser/platform_headers/linux_parser.hpp @@ -17,6 +17,7 @@ namespace argument_parser { class linux_parser : public v2::base_parser { public: linux_parser(); + using base_parser::display_help; }; } // namespace v2 } // namespace argument_parser diff --git a/src/headers/parser/platform_headers/macos_parser.hpp b/src/headers/parser/platform_headers/macos_parser.hpp index f63c2f5..5703ab5 100644 --- a/src/headers/parser/platform_headers/macos_parser.hpp +++ b/src/headers/parser/platform_headers/macos_parser.hpp @@ -18,6 +18,7 @@ namespace argument_parser { class macos_parser : public v2::base_parser { public: macos_parser(); + using base_parser::display_help; }; } // namespace v2 } // namespace argument_parser diff --git a/src/source/parser/platform_parsers/linux_parser.cpp b/src/source/parser/platform_parsers/linux_parser.cpp index 2f7eff0..7d56d53 100644 --- a/src/source/parser/platform_parsers/linux_parser.cpp +++ b/src/source/parser/platform_parsers/linux_parser.cpp @@ -24,6 +24,14 @@ namespace argument_parser { for (std::string line; std::getline(command_line_file, line, '\0');) { parsed_arguments.emplace_back(line); } + + add_argument({{flags::ShortArgument, "h"}, + {flags::LongArgument, "help"}, + {flags::Action, helpers::make_non_parametered_action([this]() { + this->display_help(this->current_conventions()); + std::exit(0); + })}, + {flags::HelpText, "Prints this help text."}}); } } // namespace v2 } // namespace argument_parser diff --git a/src/source/parser/platform_parsers/macos_parser.cpp b/src/source/parser/platform_parsers/macos_parser.cpp index b869077..22ede10 100644 --- a/src/source/parser/platform_parsers/macos_parser.cpp +++ b/src/source/parser/platform_parsers/macos_parser.cpp @@ -24,6 +24,14 @@ namespace argument_parser { ref_parsed_args().emplace_back(argv[i]); } } + + add_argument({{flags::ShortArgument, "h"}, + {flags::LongArgument, "help"}, + {flags::Action, helpers::make_non_parametered_action([this]() { + this->display_help(this->current_conventions()); + std::exit(0); + })}, + {flags::HelpText, "Prints this help text."}}); } } // namespace v2 } // namespace argument_parser