From a8b7078949fae350623c8d7daaf72f9c00821cf9 Mon Sep 17 00:00:00 2001 From: killua Date: Mon, 16 Mar 2026 18:45:16 +0400 Subject: [PATCH] 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